diff --git a/.changeset/angry-sites-fold.md b/.changeset/angry-sites-fold.md
new file mode 100644
index 0000000000..8124ddaf47
--- /dev/null
+++ b/.changeset/angry-sites-fold.md
@@ -0,0 +1,30 @@
+---
+'@backstage/plugin-auth-backend': patch
+---
+
+Added the configuration flag `auth.omitIdentityTokenOwnershipClaim` that causes issued user tokens to no longer contain the `ent` claim that represents the ownership references of the user.
+
+The benefit of this new flag is that issued user tokens will be much smaller in
+size, but they will no longer be self-contained. This means that any consumers
+of the token that require access to the ownership claims now need to call the
+`/api/auth/v1/userinfo` endpoint instead. Within the Backstage ecosystem this is
+done automatically, as clients will still receive the full set of claims during
+authentication, while plugin backends will need to use the `UserInfoService`
+which already calls the user info endpoint if necessary.
+
+When enabling this flag, it is important that any custom sign-in resolvers directly return the result of the sign-in method. For example, the following would not work:
+
+```ts
+const { token } = await ctx.issueToken({
+ claims: { sub: entityRef, ent: [entityRef] },
+});
+return { token }; // WARNING: This will not work with the flag enabled
+```
+
+Instead, the sign-in resolver should directly return the result:
+
+```ts
+return ctx.issueToken({
+ claims: { sub: entityRef, ent: [entityRef] },
+});
+```
diff --git a/.changeset/beige-kiwis-flow.md b/.changeset/beige-kiwis-flow.md
new file mode 100644
index 0000000000..500095b44b
--- /dev/null
+++ b/.changeset/beige-kiwis-flow.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-kubernetes-node': minor
+---
+
+**BREAKING**: The `PinnipedHelper` class now expects a regular `LoggerService` instance from the new backend system, instead of a Winston logger.
diff --git a/.changeset/brave-eggs-mate.md b/.changeset/brave-eggs-mate.md
new file mode 100644
index 0000000000..73eb8e363a
--- /dev/null
+++ b/.changeset/brave-eggs-mate.md
@@ -0,0 +1,5 @@
+---
+'@backstage/theme': patch
+---
+
+Show arrow when MuiTableSortLabel receives focus
diff --git a/.changeset/brave-toes-switch.md b/.changeset/brave-toes-switch.md
new file mode 100644
index 0000000000..27b39cccd3
--- /dev/null
+++ b/.changeset/brave-toes-switch.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-msgraph': minor
+---
+
+Add new `userGroupMember.path`, `user.path` and, `group.path` option to each query type to allow more complex msgraph queries
diff --git a/.changeset/bright-moles-sort.md b/.changeset/bright-moles-sort.md
new file mode 100644
index 0000000000..9796729d1a
--- /dev/null
+++ b/.changeset/bright-moles-sort.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-dynamic-feature-service': minor
+---
+
+**BREAKING** Removed support for the legacy backend, please migrate to the new backend system
diff --git a/.changeset/calm-toys-occur.md b/.changeset/calm-toys-occur.md
new file mode 100644
index 0000000000..ea03aaf4f8
--- /dev/null
+++ b/.changeset/calm-toys-occur.md
@@ -0,0 +1,17 @@
+---
+'@backstage/plugin-bitbucket-cloud-common': minor
+---
+
+Update Bitbucket Cloud schema and models.
+
+The latest schema was fetched from Bitbucket Cloud and stored locally.
+Based on the updated schema, the models got regenerated.
+
+**BREAKING:**
+
+Due to the schema changes, the model update includes one breaking change:
+
+- `Account.username` was removed.
+
+Additionally, there were a couple of compatible changes including the addition of
+`BaseCommit.committer` and others.
diff --git a/.changeset/chatty-showers-cheat.md b/.changeset/chatty-showers-cheat.md
new file mode 100644
index 0000000000..24c2720fb6
--- /dev/null
+++ b/.changeset/chatty-showers-cheat.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': minor
+---
+
+**BREAKING ALPHA**: You can no longer import the catalog plugin from the `/alpha` export; please use the regular root default export instead.
diff --git a/.changeset/chilly-trams-cheer.md b/.changeset/chilly-trams-cheer.md
new file mode 100644
index 0000000000..0e14423ef8
--- /dev/null
+++ b/.changeset/chilly-trams-cheer.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-kubernetes-react': patch
+---
+
+Add headlamp formatter
diff --git a/.changeset/chubby-cougars-run.md b/.changeset/chubby-cougars-run.md
new file mode 100644
index 0000000000..deb954ef6b
--- /dev/null
+++ b/.changeset/chubby-cougars-run.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+This patch addresses an issue identified in Backstage when configured with a MySQL database. If an entity of type location
+(e..all.yaml) has more than 70 referenced entities, clicking "Refresh" does not update the referenced entities as expected. This occurs because the TEXT type in MySQL has a limit of 65,535 bytes, which is insufficient to store all the referenced entities, causing the refresh operation to fail.
diff --git a/.changeset/cold-humans-check.md b/.changeset/cold-humans-check.md
new file mode 100644
index 0000000000..5bcb6caa4b
--- /dev/null
+++ b/.changeset/cold-humans-check.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-user-settings': patch
+---
+
+plugin-user-settingsgs support i18n
diff --git a/.changeset/common-goats-raise.md b/.changeset/common-goats-raise.md
new file mode 100644
index 0000000000..c2d797b719
--- /dev/null
+++ b/.changeset/common-goats-raise.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend-module-github': patch
+---
+
+Use action context logger in Octokit client
diff --git a/.changeset/cool-cities-grab.md b/.changeset/cool-cities-grab.md
new file mode 100644
index 0000000000..372ca9887f
--- /dev/null
+++ b/.changeset/cool-cities-grab.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+`LogViewer` now supports a `textWrap` prop that wraps log lines to the next line for overflowing content instead of using horizontal scroll
diff --git a/.changeset/cool-knives-design.md b/.changeset/cool-knives-design.md
new file mode 100644
index 0000000000..63197edd16
--- /dev/null
+++ b/.changeset/cool-knives-design.md
@@ -0,0 +1,7 @@
+---
+'@backstage/frontend-plugin-api': patch
+---
+
+The `id` option of `createFrontendPlugin` has been renamed to `pluginId` in order to better align with similar APIs in the frontend and backend systems.
+
+The old `id` option is deprecated and will be removed in a future release.
diff --git a/.changeset/create-app-1745325336.md b/.changeset/create-app-1745325336.md
new file mode 100644
index 0000000000..b50d431d4b
--- /dev/null
+++ b/.changeset/create-app-1745325336.md
@@ -0,0 +1,5 @@
+---
+'@backstage/create-app': patch
+---
+
+Bumped create-app version.
diff --git a/.changeset/create-app-1745936753.md b/.changeset/create-app-1745936753.md
new file mode 100644
index 0000000000..b50d431d4b
--- /dev/null
+++ b/.changeset/create-app-1745936753.md
@@ -0,0 +1,5 @@
+---
+'@backstage/create-app': patch
+---
+
+Bumped create-app version.
diff --git a/.changeset/dirty-grapes-vanish.md b/.changeset/dirty-grapes-vanish.md
new file mode 100644
index 0000000000..b78660d21e
--- /dev/null
+++ b/.changeset/dirty-grapes-vanish.md
@@ -0,0 +1,7 @@
+---
+'@backstage/backend-defaults': minor
+---
+
+**BREAKING**: The `DefaultSchedulerService` constructor options now requires `RootLifecycleService`, `HttpRouterService`, and `PluginMetadataService` fields.
+
+The scheduler will register a REST API for listing and triggering tasks. Please see [the scheduler documentation](https://backstage.io/docs/backend-system/core-services/scheduler) for more details about this API.
diff --git a/.changeset/dry-carpets-hope.md b/.changeset/dry-carpets-hope.md
new file mode 100644
index 0000000000..569e089a66
--- /dev/null
+++ b/.changeset/dry-carpets-hope.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-backstage-openapi': patch
+---
+
+Do not swallow errors; instead allow them to bubble up to the task scheduler for better tracking and logging.
diff --git a/.changeset/dull-doodles-trade.md b/.changeset/dull-doodles-trade.md
new file mode 100644
index 0000000000..eb9399fcc7
--- /dev/null
+++ b/.changeset/dull-doodles-trade.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-kubernetes-backend': patch
+---
+
+Changed logging of cluster details to debug to minimise log clutter.
diff --git a/.changeset/early-colts-accept.md b/.changeset/early-colts-accept.md
new file mode 100644
index 0000000000..ff583144f0
--- /dev/null
+++ b/.changeset/early-colts-accept.md
@@ -0,0 +1,6 @@
+---
+'@backstage/backend-dynamic-feature-service': patch
+---
+
+Fixed various typos.
+`FrontendRemoteResolver`'s misspelled `getAdditionaRemoteInfo` has been deprecated. Use the correct spelling `getAdditionalRemoteInfo` instead.
diff --git a/.changeset/early-dryers-teach.md b/.changeset/early-dryers-teach.md
new file mode 100644
index 0000000000..d747fbc730
--- /dev/null
+++ b/.changeset/early-dryers-teach.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-node': minor
+---
+
+Added `parseEntityYaml` from `@backstage/plugin-catalog-backend`, to make it more easily usable by custom plugins and modules
diff --git a/.changeset/eight-toys-feel.md b/.changeset/eight-toys-feel.md
new file mode 100644
index 0000000000..c0f5a12c40
--- /dev/null
+++ b/.changeset/eight-toys-feel.md
@@ -0,0 +1,35 @@
+---
+'@backstage/backend-app-api': patch
+---
+
+Added a configuration to permit backend plugin module failures on startup:
+
+```yaml
+backend:
+ ...
+ startup:
+ plugins:
+ plugin-x:
+ modules:
+ module-y:
+ onPluginModuleBootFailure: continue
+```
+
+This configuration permits `plugin-x` with `module-y` to fail on startup. Omitting the
+`onPluginModuleBootFailure` configuration matches the previous behavior, wherein any
+individual plugin module failure is forwarded to the plugin and aborts backend startup.
+
+The default can also be changed, so that continuing on failure is the default
+unless otherwise specified:
+
+```yaml
+backend:
+ startup:
+ default:
+ onPluginModuleBootFailure: continue
+ plugins:
+ catalog:
+ modules:
+ github:
+ onPluginModuleBootFailure: abort
+```
diff --git a/.changeset/eleven-ghosts-strive.md b/.changeset/eleven-ghosts-strive.md
new file mode 100644
index 0000000000..ac48c19524
--- /dev/null
+++ b/.changeset/eleven-ghosts-strive.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Full support in EntityPicker (and derivatives) for default EntityPresentationApi
diff --git a/.changeset/famous-cities-stand.md b/.changeset/famous-cities-stand.md
new file mode 100644
index 0000000000..86ba727997
--- /dev/null
+++ b/.changeset/famous-cities-stand.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-auth-node': patch
+---
+
+Added the `identity` property to `BackstageSignInResult`.
+
+The `prepareBackstageIdentityResponse` function will now also forward the `identity` to the response if present in the provided sign-in result.
diff --git a/.changeset/fancy-frogs-like.md b/.changeset/fancy-frogs-like.md
new file mode 100644
index 0000000000..ed3a9fbf81
--- /dev/null
+++ b/.changeset/fancy-frogs-like.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Internal code cleanup
diff --git a/.changeset/floppy-days-tell.md b/.changeset/floppy-days-tell.md
new file mode 100644
index 0000000000..2eb184f6fe
--- /dev/null
+++ b/.changeset/floppy-days-tell.md
@@ -0,0 +1,5 @@
+---
+'@backstage/canon': patch
+---
+
+For improved a11y, clicking a Select component label now focuses the Select trigger element, and the TextField component's label is now styled to indicate it's interactive.
diff --git a/.changeset/four-peaches-talk.md b/.changeset/four-peaches-talk.md
new file mode 100644
index 0000000000..4b0d2e8a47
--- /dev/null
+++ b/.changeset/four-peaches-talk.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Internal refactor of opaque type handling.
diff --git a/.changeset/fruity-bags-flow.md b/.changeset/fruity-bags-flow.md
new file mode 100644
index 0000000000..bc6034a489
--- /dev/null
+++ b/.changeset/fruity-bags-flow.md
@@ -0,0 +1,23 @@
+---
+'@backstage/plugin-catalog-unprocessed-entities': patch
+'@backstage/frontend-test-utils': patch
+'@backstage/core-compat-api': patch
+'@backstage/plugin-app-visualizer': patch
+'@backstage/plugin-catalog-import': patch
+'@backstage/plugin-catalog-graph': patch
+'@backstage/plugin-notifications': patch
+'@backstage/plugin-user-settings': patch
+'@backstage/plugin-kubernetes': patch
+'@backstage/plugin-scaffolder': patch
+'@backstage/plugin-api-docs': patch
+'@backstage/plugin-devtools': patch
+'@backstage/plugin-techdocs': patch
+'@backstage/plugin-catalog': patch
+'@backstage/plugin-signals': patch
+'@backstage/plugin-search': patch
+'@backstage/plugin-home': patch
+'@backstage/plugin-app': patch
+'@backstage/plugin-org': patch
+---
+
+Internal update to use the new `pluginId` option of `createFrontendPlugin`.
diff --git a/.changeset/good-islands-drive.md b/.changeset/good-islands-drive.md
new file mode 100644
index 0000000000..5b909dc84f
--- /dev/null
+++ b/.changeset/good-islands-drive.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Added `info` object to the context of the alpha CLI.
diff --git a/.changeset/heavy-baths-rule.md b/.changeset/heavy-baths-rule.md
new file mode 100644
index 0000000000..09f26e8b57
--- /dev/null
+++ b/.changeset/heavy-baths-rule.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-github': minor
+---
+
+**BREAKING** The `GithubLocationAnalyzer` now requires the `AuthService` and the `CatalogService` when being constructed and the `TokenManger` has been removed.
diff --git a/.changeset/heavy-onions-swim.md b/.changeset/heavy-onions-swim.md
new file mode 100644
index 0000000000..42ba056c1e
--- /dev/null
+++ b/.changeset/heavy-onions-swim.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': minor
+---
+
+Added `workspace:template` and `workspace:template:file` actions to complement respective `fetch:*` actions
diff --git a/.changeset/icy-mugs-glow.md b/.changeset/icy-mugs-glow.md
new file mode 100644
index 0000000000..553daff24a
--- /dev/null
+++ b/.changeset/icy-mugs-glow.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-bitbucket-cloud-common': patch
+---
+
+Add support for `repo:updated` events as `Events.RepoUpdatedEvent`.
diff --git a/.changeset/large-experts-sort.md b/.changeset/large-experts-sort.md
new file mode 100644
index 0000000000..6d58836db7
--- /dev/null
+++ b/.changeset/large-experts-sort.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-events-backend-module-github': minor
+---
+
+**BREAKING**: Removed the `createGithubSignatureValidator` export.
+
+Added support webhook validation based on `integrations.github.[].apps.[].webhookSecret`.
diff --git a/.changeset/large-lemons-clap.md b/.changeset/large-lemons-clap.md
new file mode 100644
index 0000000000..cf1a3eed5d
--- /dev/null
+++ b/.changeset/large-lemons-clap.md
@@ -0,0 +1,7 @@
+---
+'@backstage/backend-plugin-api': patch
+'@backstage/backend-defaults': patch
+'@backstage/integration': patch
+---
+
+update documentation
diff --git a/.changeset/lazy-tires-show.md b/.changeset/lazy-tires-show.md
new file mode 100644
index 0000000000..3ccf5f155e
--- /dev/null
+++ b/.changeset/lazy-tires-show.md
@@ -0,0 +1,7 @@
+---
+'@backstage/plugin-permission-backend': minor
+'@backstage/plugin-permission-common': minor
+'@backstage/plugin-permission-node': minor
+---
+
+Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests.
diff --git a/.changeset/lovely-cats-take.md b/.changeset/lovely-cats-take.md
new file mode 100644
index 0000000000..ab1ee1c249
--- /dev/null
+++ b/.changeset/lovely-cats-take.md
@@ -0,0 +1,11 @@
+---
+'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch
+---
+
+Support Bitbucket Cloud's `repo:updated` events at `BitbucketCloudEntityProvider`.
+
+To make use of the new event type, you have to configure your webhook or add a new ones
+that delivers this event type to Backstage similar to `repo:push` before.
+
+Only `repo:updated` events that modify a repository's URL (e.g., due to a name change)
+will cause changes (removing the "old", adding the "new" repository).
diff --git a/.changeset/mighty-carrots-decide.md b/.changeset/mighty-carrots-decide.md
new file mode 100644
index 0000000000..68f210da48
--- /dev/null
+++ b/.changeset/mighty-carrots-decide.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-search-react': minor
+---
+
+Allow search filters to provide labels and values separately, and not only values
diff --git a/.changeset/nice-vans-vanish.md b/.changeset/nice-vans-vanish.md
new file mode 100644
index 0000000000..c52fc42488
--- /dev/null
+++ b/.changeset/nice-vans-vanish.md
@@ -0,0 +1,5 @@
+---
+'@backstage/canon': minor
+---
+
+**Breaking Change** Icons on Button and IconButton now need to be imported and placed like this: } />
diff --git a/.changeset/old-crews-serve.md b/.changeset/old-crews-serve.md
new file mode 100644
index 0000000000..db6bb4cf23
--- /dev/null
+++ b/.changeset/old-crews-serve.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend-module-incremental-ingestion': minor
+---
+
+**BREAKING** Removed support for the legacy backend, please [migrate to the new backend system](https://github.com/backstage/backstage/tree/v1.38.0/plugins/catalog-backend-module-incremental-ingestion#installation). Also, if you were importing from the `/alpha` export of this package, you should remove the `/alpha` part.
diff --git a/.changeset/open-ghosts-fix.md b/.changeset/open-ghosts-fix.md
new file mode 100644
index 0000000000..144f24dea0
--- /dev/null
+++ b/.changeset/open-ghosts-fix.md
@@ -0,0 +1,5 @@
+---
+'@backstage/canon': patch
+---
+
+Fixed an issue with Canon's DataTable.Pagination component showing the wrong number for the "to" count.
diff --git a/.changeset/pre.json b/.changeset/pre.json
index 7e1a48a83a..e980d2a115 100644
--- a/.changeset/pre.json
+++ b/.changeset/pre.json
@@ -197,7 +197,85 @@
"@backstage/plugin-techdocs-react": "1.2.16",
"@backstage/plugin-user-settings": "0.8.21",
"@backstage/plugin-user-settings-backend": "0.3.1",
- "@backstage/plugin-user-settings-common": "0.0.1"
+ "@backstage/plugin-user-settings-common": "0.0.1",
+ "@backstage/plugin-events-backend-module-google-pubsub": "0.0.0"
},
- "changesets": []
+ "changesets": [
+ "angry-sites-fold",
+ "beige-kiwis-flow",
+ "brave-eggs-mate",
+ "brave-toes-switch",
+ "breezy-hotels-deny",
+ "bright-moles-sort",
+ "bumpy-showers-design",
+ "calm-toys-occur",
+ "chatty-showers-cheat",
+ "chilly-trams-cheer",
+ "chubby-cougars-run",
+ "chubby-needles-vanish",
+ "cold-humans-check",
+ "common-goats-raise",
+ "cool-cities-grab",
+ "cool-knives-design",
+ "create-app-1745325336",
+ "create-app-1745936753",
+ "cruel-lights-sip",
+ "cyan-pots-appear",
+ "dirty-grapes-vanish",
+ "dry-carpets-hope",
+ "dull-doodles-trade",
+ "early-colts-accept",
+ "early-dryers-teach",
+ "eight-toys-feel",
+ "eleven-ghosts-strive",
+ "famous-cities-stand",
+ "fancy-frogs-like",
+ "floppy-days-tell",
+ "four-peaches-talk",
+ "fruity-bags-flow",
+ "good-islands-drive",
+ "green-trainers-float",
+ "heavy-baths-rule",
+ "heavy-onions-swim",
+ "huge-olives-do",
+ "icy-mugs-glow",
+ "large-experts-sort",
+ "large-lemons-clap",
+ "lazy-tires-show",
+ "lovely-cats-take",
+ "mighty-carrots-decide",
+ "new-hands-scream",
+ "nice-vans-vanish",
+ "old-crews-serve",
+ "open-ghosts-fix",
+ "pretty-corners-speak",
+ "pretty-seas-hug",
+ "public-socks-agree",
+ "real-rings-smoke",
+ "real-sheep-chew",
+ "renovate-4fc113d",
+ "renovate-68baea0",
+ "renovate-e32145b",
+ "renovate-ee17247",
+ "sad-showers-begin",
+ "sad-taxes-bake",
+ "shaggy-stingrays-check",
+ "sharp-ligers-beg",
+ "sharp-numbers-doubt",
+ "shiny-symbols-grow",
+ "short-teeth-juggle",
+ "silent-clubs-roll",
+ "slick-brooms-start",
+ "slimy-peas-post",
+ "small-eggs-develop",
+ "spicy-steaks-swim",
+ "spotty-doors-design",
+ "stale-symbols-joke",
+ "ten-tables-build",
+ "true-breads-rhyme",
+ "twenty-kiwis-punch",
+ "warm-llamas-return",
+ "wise-pillows-smile",
+ "yellow-cows-tickle"
+ ]
}
diff --git a/.changeset/pretty-corners-speak.md b/.changeset/pretty-corners-speak.md
new file mode 100644
index 0000000000..8e24d212fe
--- /dev/null
+++ b/.changeset/pretty-corners-speak.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-test-utils': minor
+---
+
+Add a functional `mockServices.events()`
diff --git a/.changeset/pretty-seas-hug.md b/.changeset/pretty-seas-hug.md
new file mode 100644
index 0000000000..216f9af18f
--- /dev/null
+++ b/.changeset/pretty-seas-hug.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-react': patch
+---
+
+Fixed an issue causing entities of kind user and group to be empty when an owner was selected
diff --git a/.changeset/public-socks-agree.md b/.changeset/public-socks-agree.md
new file mode 100644
index 0000000000..7febb17cc0
--- /dev/null
+++ b/.changeset/public-socks-agree.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-node-test-utils': patch
+---
+
+Include optional `user` in `createMockActionContext`
diff --git a/.changeset/real-rings-smoke.md b/.changeset/real-rings-smoke.md
new file mode 100644
index 0000000000..832d7e3c6e
--- /dev/null
+++ b/.changeset/real-rings-smoke.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-org': patch
+---
+
+display entity-ref in GroupProfileCard so groups can easily determine their Group ID
diff --git a/.changeset/real-sheep-chew.md b/.changeset/real-sheep-chew.md
new file mode 100644
index 0000000000..9ae5b5ae9e
--- /dev/null
+++ b/.changeset/real-sheep-chew.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Fix the hidden sidebar's sub-menu when the sidebar is scrollable
diff --git a/.changeset/renovate-4fc113d.md b/.changeset/renovate-4fc113d.md
new file mode 100644
index 0000000000..abec879c27
--- /dev/null
+++ b/.changeset/renovate-4fc113d.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Updated dependency `@octokit/request` to `^8.0.0`.
diff --git a/.changeset/renovate-68baea0.md b/.changeset/renovate-68baea0.md
new file mode 100644
index 0000000000..6370fc8834
--- /dev/null
+++ b/.changeset/renovate-68baea0.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Updated dependency `react-refresh` to `^0.17.0`.
diff --git a/.changeset/renovate-e32145b.md b/.changeset/renovate-e32145b.md
new file mode 100644
index 0000000000..e1fdbcf02d
--- /dev/null
+++ b/.changeset/renovate-e32145b.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-compat-api': patch
+---
+
+Updated dependency `@backstage-community/plugin-puppetdb` to `^0.6.0`.
diff --git a/.changeset/renovate-ee17247.md b/.changeset/renovate-ee17247.md
new file mode 100644
index 0000000000..a97a63d138
--- /dev/null
+++ b/.changeset/renovate-ee17247.md
@@ -0,0 +1,9 @@
+---
+'@backstage/plugin-kubernetes-backend': patch
+'@backstage/plugin-kubernetes-common': patch
+'@backstage/plugin-kubernetes-node': patch
+'@backstage/plugin-kubernetes-react': patch
+'@backstage/plugin-kubernetes': patch
+---
+
+Updated dependency `@kubernetes/client-node` to `1.1.2`.
diff --git a/.changeset/sad-showers-begin.md b/.changeset/sad-showers-begin.md
new file mode 100644
index 0000000000..7fce23a2ab
--- /dev/null
+++ b/.changeset/sad-showers-begin.md
@@ -0,0 +1,66 @@
+---
+'@backstage/plugin-catalog-backend': major
+---
+
+**BREAKING**: Removed all deprecated exports, and removed support for the old backend system.
+
+It also removes the `CodeOwnersProcessor` from the default set of processors, because it is expensive to run and has vague semantics. You need to update your backend to add it to the `catalogProcessingExtensionPoint` if you wish to continue using it.
+
+The following removed exports are available from `@backstage/plugin-catalog-node`:
+
+- `locationSpecToMetadataName`
+- `locationSpecToLocationEntity`
+- `processingResult`
+- `EntitiesSearchFilter`
+- `EntityFilter`
+- `DeferredEntity`
+- `EntityRelationSpec`
+- `CatalogProcessor`
+- `CatalogProcessorParser`
+- `CatalogProcessorCache`
+- `CatalogProcessorEmit`
+- `CatalogProcessorLocationResult`
+- `CatalogProcessorEntityResult`
+- `CatalogProcessorRelationResult`
+- `CatalogProcessorErrorResult`
+- `CatalogProcessorRefreshKeysResult`
+- `CatalogProcessorResult`
+- `EntityProvider`
+- `EntityProviderConnection`
+- `EntityProviderMutation`
+- `AnalyzeOptions`
+- `LocationAnalyzer`
+- `ScmLocationAnalyzer`
+- `PlaceholderResolver`
+- `PlaceholderResolverParams`
+- `PlaceholderResolverRead`
+- `PlaceholderResolverResolveUrl`
+- `parseEntityYaml`
+
+The following removed exports are available from `@backstage/plugin-catalog-common`:
+
+- `LocationSpec`
+- `AnalyzeLocationRequest`
+- `AnalyzeLocationResponse`
+- `AnalyzeLocationExistingEntity`
+- `AnalyzeLocationGenerateEntity`
+- `AnalyzeLocationEntityField`
+
+The following removed exports are instead implemented in the new backend system by `@backstage/plugin-search-backend-module-catalog`:
+
+- `defaultCatalogCollatorEntityTransformer`
+- `CatalogCollatorEntityTransformer`
+- `DefaultCatalogCollator`
+
+The following exports are removed without a direct replacement:
+
+- `DefaultCatalogCollatorFactory`
+- `DefaultCatalogCollatorFactoryOptions`
+- `LocationEntityProcessor`
+- `LocationEntityProcessorOptions`
+- `CatalogBuilder`
+- `CatalogEnvironment`
+- `CatalogPermissionRuleInput`
+- `CatalogProcessingEngine`
+- `createRandomProcessingInterval`
+- `ProcessingIntervalFunction`
diff --git a/.changeset/sad-taxes-bake.md b/.changeset/sad-taxes-bake.md
new file mode 100644
index 0000000000..61a843a265
--- /dev/null
+++ b/.changeset/sad-taxes-bake.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Fixed bug in fs:delete that prevented wildcard patterns from matching paths starting with "."
diff --git a/.changeset/shaggy-stingrays-check.md b/.changeset/shaggy-stingrays-check.md
new file mode 100644
index 0000000000..74e0013224
--- /dev/null
+++ b/.changeset/shaggy-stingrays-check.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-org': patch
+---
+
+Fixed missing spec.profile field on MyGroupsSidebarItem.tsx so the group spec.profile.displayName is shown on the sidebar"
diff --git a/.changeset/sharp-ligers-beg.md b/.changeset/sharp-ligers-beg.md
new file mode 100644
index 0000000000..0582ed72d2
--- /dev/null
+++ b/.changeset/sharp-ligers-beg.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': minor
+---
+
+Adds the ability to disable the default entity processors using a new boolean app config item `catalog.disableDefaultProcessors`.
diff --git a/.changeset/short-teeth-juggle.md b/.changeset/short-teeth-juggle.md
new file mode 100644
index 0000000000..e0f1073db3
--- /dev/null
+++ b/.changeset/short-teeth-juggle.md
@@ -0,0 +1,49 @@
+---
+'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch
+'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch
+'@backstage/plugin-catalog-backend-module-backstage-openapi': patch
+'@backstage/plugin-scaffolder-backend-module-bitbucket': patch
+'@backstage/plugin-search-backend-module-elasticsearch': patch
+'@backstage/plugin-catalog-backend-module-github-org': patch
+'@backstage/plugin-scaffolder-backend-module-gerrit': patch
+'@backstage/plugin-scaffolder-backend-module-github': patch
+'@backstage/plugin-scaffolder-backend-module-gitlab': patch
+'@backstage/plugin-catalog-backend-module-msgraph': patch
+'@backstage/plugin-catalog-backend-module-gitlab': patch
+'@backstage/plugin-catalog-backend-module-logs': patch
+'@backstage/plugin-catalog-backend-module-aws': patch
+'@backstage/frontend-plugin-api': patch
+'@backstage/backend-plugin-api': patch
+'@backstage/plugin-kubernetes-backend': patch
+'@backstage/plugin-kubernetes-cluster': patch
+'@backstage/plugin-scaffolder-backend': patch
+'@backstage/backend-defaults': patch
+'@backstage/frontend-app-api': patch
+'@backstage/plugin-kubernetes-common': patch
+'@backstage/plugin-permission-common': patch
+'@backstage/backend-app-api': patch
+'@backstage/core-compat-api': patch
+'@backstage/core-components': patch
+'@backstage/plugin-kubernetes-react': patch
+'@backstage/plugin-scaffolder-react': patch
+'@backstage/plugin-techdocs-backend': patch
+'@backstage/plugin-kubernetes-node': patch
+'@backstage/plugin-scaffolder-node': patch
+'@backstage/config-loader': patch
+'@backstage/plugin-techdocs-react': patch
+'@backstage/plugin-catalog-graph': patch
+'@backstage/plugin-techdocs-node': patch
+'@backstage/plugin-auth-backend': patch
+'@backstage/repo-tools': patch
+'@backstage/plugin-auth-react': patch
+'@backstage/plugin-kubernetes': patch
+'@backstage/plugin-scaffolder': patch
+'@backstage/plugin-api-docs': patch
+'@backstage/plugin-techdocs': patch
+'@backstage/canon': patch
+'@backstage/cli': patch
+'@backstage/plugin-home': patch
+'@backstage/plugin-org': patch
+---
+
+Removed various typos
diff --git a/.changeset/slick-brooms-start.md b/.changeset/slick-brooms-start.md
new file mode 100644
index 0000000000..dec874a867
--- /dev/null
+++ b/.changeset/slick-brooms-start.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-events-backend-module-github': patch
+---
+
+Correct README installation instructions.
diff --git a/.changeset/slimy-peas-post.md b/.changeset/slimy-peas-post.md
new file mode 100644
index 0000000000..6a0a80e323
--- /dev/null
+++ b/.changeset/slimy-peas-post.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-plugin-api': patch
+---
+
+Minor doc comment update
diff --git a/.changeset/small-eggs-develop.md b/.changeset/small-eggs-develop.md
new file mode 100644
index 0000000000..618b877fa7
--- /dev/null
+++ b/.changeset/small-eggs-develop.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-scaffolder-react': minor
+'@backstage/plugin-scaffolder': minor
+---
+
+add templating extensions page
diff --git a/.changeset/spicy-steaks-swim.md b/.changeset/spicy-steaks-swim.md
new file mode 100644
index 0000000000..b2323c9b34
--- /dev/null
+++ b/.changeset/spicy-steaks-swim.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend-module-github': patch
+---
+
+Added optional assignees parameter to `publish:github:pull-request` action
diff --git a/.changeset/spotty-doors-design.md b/.changeset/spotty-doors-design.md
new file mode 100644
index 0000000000..22ee61c9b0
--- /dev/null
+++ b/.changeset/spotty-doors-design.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-notifications-backend-module-slack': patch
+---
+
+Added email-based Slack User ID lookup if `metadata.annotations.slack.com/bot-notify` is missing from user entity
diff --git a/.changeset/stale-symbols-joke.md b/.changeset/stale-symbols-joke.md
new file mode 100644
index 0000000000..ea4f172c1c
--- /dev/null
+++ b/.changeset/stale-symbols-joke.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-user-settings': patch
+---
+
+Uppercase language name in language select
diff --git a/.changeset/ten-tables-build.md b/.changeset/ten-tables-build.md
new file mode 100644
index 0000000000..9ddae808fa
--- /dev/null
+++ b/.changeset/ten-tables-build.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-backend': patch
+---
+
+The `static` key store now issues tokens with the same structure as other key stores. Tokens now include the `typ` field in the header and the `uip` (user identity proof) in the payload.
diff --git a/.changeset/thick-hotels-rhyme.md b/.changeset/thick-hotels-rhyme.md
new file mode 100644
index 0000000000..b7047dcd71
--- /dev/null
+++ b/.changeset/thick-hotels-rhyme.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs': patch
+---
+
+Add hover and focus styling to the "copy to clipboard" button within codeblocks in techdocs. Also added an aria-label to the button for accessibility.
diff --git a/.changeset/true-breads-rhyme.md b/.changeset/true-breads-rhyme.md
new file mode 100644
index 0000000000..d54f6c18d2
--- /dev/null
+++ b/.changeset/true-breads-rhyme.md
@@ -0,0 +1,6 @@
+---
+'@backstage/backend-test-utils': minor
+'@backstage/backend-defaults': patch
+---
+
+Added Valkey support alongside Redis in backend-defaults cache clients, using the new Keyv Valkey package. Also extended backend-test-utils to support Valkey in tests.
diff --git a/.changeset/twenty-kiwis-punch.md b/.changeset/twenty-kiwis-punch.md
new file mode 100644
index 0000000000..6c54760247
--- /dev/null
+++ b/.changeset/twenty-kiwis-punch.md
@@ -0,0 +1,5 @@
+---
+'@backstage/backend-defaults': patch
+---
+
+Bug fix: Pass user provided token through to gitlab url resolvers
diff --git a/.changeset/warm-llamas-return.md b/.changeset/warm-llamas-return.md
new file mode 100644
index 0000000000..743ad0a5bb
--- /dev/null
+++ b/.changeset/warm-llamas-return.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-events-backend-module-google-pubsub': minor
+---
+
+Added a module that is able to transfer messages from Google Pub/Sub subscriptions into the Backstage events system.
diff --git a/.changeset/wise-pillows-smile.md b/.changeset/wise-pillows-smile.md
new file mode 100644
index 0000000000..febdeab2c4
--- /dev/null
+++ b/.changeset/wise-pillows-smile.md
@@ -0,0 +1,5 @@
+---
+'yarn-plugin-backstage': patch
+---
+
+Add both `npm:` and `backstage:` ranges to the lockfile to ensure compatibility with tools that parse the lockfile and ensure dependencies stay locked when building dist workspaces.
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 76a6afe4d6..4c51c8f111 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -15,6 +15,8 @@ yarn.lock @backstage/maintainers @backst
/docs/dls @backstage/design-system-maintainers
/docs/features/techdocs @backstage/techdocs-maintainers
/docs/plugins/integrating-search-into-plugins.md @backstage/search-maintainers
+/microsite @backstage/documentation-maintainers
+/microsite/data/plugins @backstage/maintainers
/packages/cli/src/commands/onboard @backstage/sharks
/packages/backend-openapi-utils @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers
/packages/canon @backstage/design-system-maintainers
diff --git a/.github/ISSUE_TEMPLATE/.common.yaml b/.github/ISSUE_TEMPLATE/.common.yaml
new file mode 100644
index 0000000000..c870b21b94
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/.common.yaml
@@ -0,0 +1,93 @@
+# This file contains common fields for all issue templates
+# Each field is identified by its ID, and are synced with the `sync.js` script
+#
+# WARNING: After updating this file, run `yarn sync-issue-templates`
+#
+
+body:
+ - id: issue-labels
+ type: checkboxes
+ attributes:
+ label: '📜 Issue Labels'
+ options:
+ - label: 'Please familiarize yourself with the issue labels used in this project: [LABELS.md](https://github.com/backstage/backstage/blob/master/LABELS.md)'
+ required: true
+ - id: search-terms
+ type: textarea
+ attributes:
+ label: '🔎 Search Terms'
+ render: plain
+ description: |
+ What search terms did you use when trying to find similar issues?
+ placeholder: |
+ List of keywords you searched for before creating this issue. Write them down here so that others can find this issue more easily and help provide feedback.
+
+ e.g. "catalog github rate limited", "http root service configurer", "scaffolder template include", "entity card title"
+ validations:
+ required: true
+ - id: project-area
+ type: dropdown
+ attributes:
+ label: '🗃️ Project Area'
+ description: Which project area is this issue most closely related to? This will help find an owner for the issue faster.
+ options:
+ - Unknown
+ - Auditor
+ - Auth
+ - Catalog
+ - CLI Tooling
+ - Core Framework
+ - Design System
+ - Documentation
+ - Events System
+ - Home
+ - Kubernetes Plugin
+ - Microsite
+ - Notifications
+ - OpenAPI Tooling
+ - Permission Framework
+ - Search
+ - Software Templates
+ - TechDocs
+ validations:
+ required: true
+ - type: dropdown
+ id: integration
+ attributes:
+ label: '🔗 External Integration'
+ description: Is this issue related to an integration with any particular external system?
+ options:
+ - N/A
+ - AWS
+ - Azure
+ - Azure DevOps
+ - Bitbucket Cloud
+ - Bitbucket Server
+ - GCP
+ - Gerrit
+ - Gitea
+ - GitHub
+ - GitLab
+ - Other
+ validations:
+ required: true
+ - id: read-code-of-conduct
+ type: checkboxes
+ attributes:
+ label: 'Have you read the Code of Conduct?'
+ options:
+ - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)'
+ required: true
+ - id: willing-to-submit-pr
+ type: dropdown
+ attributes:
+ label: Are you willing to submit a PR?
+ description: This is absolutely not required, but we are happy to guide you in the contribution process.
+ options:
+ - Undecided
+ - Yes, and I have enough information to get started
+ - Yes, but I would like some more guidance
+ - No, but I'm happy to collaborate on a PR with someone else
+ - No, I don't have time to work on this right now
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/01_bug.yaml b/.github/ISSUE_TEMPLATE/01_bug.yaml
new file mode 100644
index 0000000000..ccaebe8bff
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/01_bug.yaml
@@ -0,0 +1,162 @@
+name: '🪲 Report a bug'
+description: 'Report a bug that you have encountered'
+labels:
+ - type:bug
+ - needs:triage
+body:
+ - type: markdown
+ attributes:
+ value: |
+ Please fill in each section completely, and [search in GitHub](https://github.com/backstage/backstage/search?type=Issues)
+ before reporting a new bug to avoid duplicates. Thank you!
+ # This field is managed by .common.yaml
+ - id: issue-labels
+ type: checkboxes
+ attributes:
+ label: '📜 Issue Labels'
+ options:
+ - label: 'Please familiarize yourself with the issue labels used in this project: [LABELS.md](https://github.com/backstage/backstage/blob/master/LABELS.md)'
+ required: true
+ # This field is managed by .common.yaml
+ - id: search-terms
+ type: textarea
+ attributes:
+ label: '🔎 Search Terms'
+ render: plain
+ description: |
+ What search terms did you use when trying to find similar issues?
+ placeholder: |
+ List of keywords you searched for before creating this issue. Write them down here so that others can find this issue more easily and help provide feedback.
+
+ e.g. "catalog github rate limited", "http root service configurer", "scaffolder template include", "entity card title"
+ validations:
+ required: true
+ - type: markdown
+ attributes:
+ value: ' ' # empty space
+ - type: markdown
+ attributes:
+ value: |
+ ## Defining the bug
+
+ Defining the bug accurately will help ensure that it is identified and fixed correctly.
+ # This field is managed by .common.yaml
+ - id: project-area
+ type: dropdown
+ attributes:
+ label: '🗃️ Project Area'
+ description: Which project area is this issue most closely related to? This will help find an owner for the issue faster.
+ options:
+ - Unknown
+ - Auditor
+ - Auth
+ - Catalog
+ - CLI Tooling
+ - Core Framework
+ - Design System
+ - Documentation
+ - Events System
+ - Home
+ - Kubernetes Plugin
+ - Microsite
+ - Notifications
+ - OpenAPI Tooling
+ - Permission Framework
+ - Search
+ - Software Templates
+ - TechDocs
+ validations:
+ required: true
+ # This field is managed by .common.yaml
+ - type: dropdown
+ id: integration
+ attributes:
+ label: '🔗 External Integration'
+ description: Is this issue related to an integration with any particular external system?
+ options:
+ - N/A
+ - AWS
+ - Azure
+ - Azure DevOps
+ - Bitbucket Cloud
+ - Bitbucket Server
+ - GCP
+ - Gerrit
+ - Gitea
+ - GitHub
+ - GitLab
+ - Other
+ validations:
+ required: true
+ - type: textarea
+ id: description
+ validations:
+ required: true
+ attributes:
+ label: '📝 Description & Context'
+ placeholder: |
+ Describe the observed behavior that you believe to be incorrect, as well as any additional context that might be useful in understanding the bug.
+
+ e.g. "When I navigate to the catalog index page, it loads without any issues. This might be related to https://xkcd.com/1172"
+ - type: textarea
+ id: expected-behavior
+ validations:
+ required: true
+ attributes:
+ label: '👍 Expected Behavior'
+ placeholder: |
+ Describe the behavior or result that you expected instead of the observed one.
+
+ e.g. "I expect the catalog index page to overheat my CPU"
+ - type: markdown
+ attributes:
+ value: ' ' # empty space
+ - type: markdown
+ attributes:
+ value: |
+ ## Reproducing the bug
+
+ Being able to easily reproduce the bug will significantly increase the likelihood that it can be identified and fixed. Please provide a repository where the bug can be reproduced, unless this is a critical bug that needs immediate attention.
+
+ If you are unable to provide a reproduction repository, please explain the reproduction steps in as much detail as possible, and include the output of `yarn backstage-cli info` if relevant.
+
+ If inadequate or no reproduction steps are provided, the bug report is likely to be labeled as needing reproduction steps before it can be progressed further.
+ - type: input
+ id: reproduction-repo
+ attributes:
+ label: '📦 Reproduction Repo'
+ description: A link to a GitHub repository where the issue can be reproduced
+ placeholder: https://github.com/ghost/my-repro-repo
+ validations:
+ required: false
+ - type: textarea
+ id: steps-to-reproduce
+ attributes:
+ label: '🥾 Reproduction steps'
+ description: 'How do you trigger this bug? Please walk us through it step by step.'
+ placeholder: |
+ 1. Go to '...'
+ 2. Click on '....'
+ 3. Scroll down to '....'"
+ validations:
+ required: false
+ # This field is managed by .common.yaml
+ - id: read-code-of-conduct
+ type: checkboxes
+ attributes:
+ label: 'Have you read the Code of Conduct?'
+ options:
+ - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)'
+ required: true
+ - type: dropdown
+ attributes:
+ label: Are you willing to submit PR?
+ description: This is absolutely not required, but we are happy to guide you in the contribution process.
+ options:
+ - Undecided
+ - Yes, and I have enough information to get started
+ - Yes, but I would like some more guidance
+ - No, but I'm happy to collaborate on a PR with someone else
+ - No, I don't have time to work on this right now
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/02_documentation.yaml b/.github/ISSUE_TEMPLATE/02_documentation.yaml
new file mode 100644
index 0000000000..dc9ede2c53
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/02_documentation.yaml
@@ -0,0 +1,98 @@
+name: '📖 Report an issue in the documentation'
+description: 'Report an issue in the Backstage documentation'
+labels:
+ - domain:docs
+ - type:documentation
+ - needs:triage
+body:
+ - type: markdown
+ attributes:
+ value: |
+ This template is used to report issues in the Backstage documentation.
+
+ The documentation at [backstage.io](https://backstage.io) is only updated when a new release is made. Before reporting an issue here, please verify that the issue also exists in the ["next"](https://backstage.io/docs/next/overview/what-is-backstage) version of the documentation, which you can access using the top-right dropdown menu.
+ # This field is managed by .common.yaml
+ - id: issue-labels
+ type: checkboxes
+ attributes:
+ label: '📜 Issue Labels'
+ options:
+ - label: 'Please familiarize yourself with the issue labels used in this project: [LABELS.md](https://github.com/backstage/backstage/blob/master/LABELS.md)'
+ required: true
+ # This field is managed by .common.yaml
+ - id: project-area
+ type: dropdown
+ attributes:
+ label: '🗃️ Project Area'
+ description: Which project area is this issue most closely related to? This will help find an owner for the issue faster.
+ options:
+ - Unknown
+ - Auditor
+ - Auth
+ - Catalog
+ - CLI Tooling
+ - Core Framework
+ - Design System
+ - Documentation
+ - Events System
+ - Home
+ - Kubernetes Plugin
+ - Microsite
+ - Notifications
+ - OpenAPI Tooling
+ - Permission Framework
+ - Search
+ - Software Templates
+ - TechDocs
+ validations:
+ required: true
+ # This field is managed by .common.yaml
+ - type: dropdown
+ id: integration
+ attributes:
+ label: '🔗 External Integration'
+ description: Is this issue related to an integration with any particular external system?
+ options:
+ - N/A
+ - AWS
+ - Azure
+ - Azure DevOps
+ - Bitbucket Cloud
+ - Bitbucket Server
+ - GCP
+ - Gerrit
+ - Gitea
+ - GitHub
+ - GitLab
+ - Other
+ validations:
+ required: true
+ - type: textarea
+ id: description
+ validations:
+ required: true
+ attributes:
+ label: '📝 Description'
+ description: Describe the problem you have found in the documentation.
+ placeholder: |
+ Please provide links to the specific pages and sections that need to be corrected, and describe the problem you have found.
+ # This field is managed by .common.yaml
+ - id: read-code-of-conduct
+ type: checkboxes
+ attributes:
+ label: 'Have you read the Code of Conduct?'
+ options:
+ - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)'
+ required: true
+ - type: dropdown
+ attributes:
+ label: Are you willing to submit PR?
+ description: This is absolutely not required, but we are happy to guide you in the contribution process.
+ options:
+ - Undecided
+ - Yes, and I have enough information to get started
+ - Yes, but I would like some more guidance
+ - No, but I'm happy to collaborate on a PR with someone else
+ - No, I don't have time to work on this right now
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/03_suggestion.yaml b/.github/ISSUE_TEMPLATE/03_suggestion.yaml
new file mode 100644
index 0000000000..cafdd7066f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/03_suggestion.yaml
@@ -0,0 +1,114 @@
+name: '💡 Suggest a change'
+description: 'Suggest an idea for a new feature or change to Backstage'
+labels:
+ - type:suggestion
+ - needs:triage
+body:
+ - type: markdown
+ attributes:
+ value: |
+ This template helps you propose new features, enhancements, or other ideas for Backstage. You can keep your proposal lightweight, but please provide enough detail for reviewers to understand and respond effectively.
+
+ For complex additions, consider creating a [Backstage Enhancement Proposal](https://github.com/backstage/backstage/blob/master/beps/README.md) (BEP) instead, as this process streamlines discussions and iteration. Don't worry - you can always convert this issue to a BEP later.
+ # This field is managed by .common.yaml
+ - id: issue-labels
+ type: checkboxes
+ attributes:
+ label: '📜 Issue Labels'
+ options:
+ - label: 'Please familiarize yourself with the issue labels used in this project: [LABELS.md](https://github.com/backstage/backstage/blob/master/LABELS.md)'
+ required: true
+ # This field is managed by .common.yaml
+ - id: search-terms
+ type: textarea
+ attributes:
+ label: '🔎 Search Terms'
+ render: plain
+ description: |
+ What search terms did you use when trying to find similar issues?
+ placeholder: |
+ List of keywords you searched for before creating this issue. Write them down here so that others can find this issue more easily and help provide feedback.
+
+ e.g. "catalog github rate limited", "http root service configurer", "scaffolder template include", "entity card title"
+ validations:
+ required: true
+ # This field is managed by .common.yaml
+ - id: project-area
+ type: dropdown
+ attributes:
+ label: '🗃️ Project Area'
+ description: Which project area is this issue most closely related to? This will help find an owner for the issue faster.
+ options:
+ - Unknown
+ - Auditor
+ - Auth
+ - Catalog
+ - CLI Tooling
+ - Core Framework
+ - Design System
+ - Documentation
+ - Events System
+ - Home
+ - Kubernetes Plugin
+ - Microsite
+ - Notifications
+ - OpenAPI Tooling
+ - Permission Framework
+ - Search
+ - Software Templates
+ - TechDocs
+ validations:
+ required: true
+ - type: textarea
+ id: need
+ validations:
+ required: true
+ attributes:
+ label: '🔖 Need'
+ description: What is the rationale for this change, and who will benefit from it?
+ placeholder: |
+ Explain why this change is important and who will benefit from it. Provide context for the change, such as highlighting relevant past work in this area or specific use cases.
+ - type: textarea
+ id: proposal
+ validations:
+ required: true
+ attributes:
+ label: '📝 Proposal'
+ description: Describe the change that you are suggesting.
+ placeholder: |
+ Provide sufficient detail for reviewers to give concrete feedback.
+
+ Including a proposed implementation is optional, but if you do, consider adding design details such as TypeScript examples, database schema, or sequence diagrams.
+
+ If the change requires particular care when being rolled out, it can be helpful to include a plan for a phased release.
+ - type: textarea
+ id: alternatives
+ attributes:
+ label: '🔄 Alternatives'
+ description: Did you consider any other approaches?
+ placeholder: |
+ Did you consider any other approaches, and if so, why did you rule them out?
+
+ These do not need to be as detailed as the main proposal, but should include enough information to understand the idea and why it wasn't suitable.
+ # This field is managed by .common.yaml
+ - id: read-code-of-conduct
+ type: checkboxes
+ attributes:
+ label: 'Have you read the Code of Conduct?'
+ options:
+ - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)'
+ required: true
+ # This field is managed by .common.yaml
+ - id: willing-to-submit-pr
+ type: dropdown
+ attributes:
+ label: Are you willing to submit a PR?
+ description: This is absolutely not required, but we are happy to guide you in the contribution process.
+ options:
+ - Undecided
+ - Yes, and I have enough information to get started
+ - Yes, but I would like some more guidance
+ - No, but I'm happy to collaborate on a PR with someone else
+ - No, I don't have time to work on this right now
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/04_maintenance.yaml b/.github/ISSUE_TEMPLATE/04_maintenance.yaml
new file mode 100644
index 0000000000..bf81efa5f6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/04_maintenance.yaml
@@ -0,0 +1,101 @@
+name: '🚧 Track a maintenance task'
+description: 'Track a maintenance task'
+labels:
+ - type:maintenance
+ - needs:triage
+body:
+ - type: markdown
+ attributes:
+ value: |
+ This template helps you track maintenance tasks that require special consideration, such as complex version updates or migrations.
+ # This field is managed by .common.yaml
+ - id: issue-labels
+ type: checkboxes
+ attributes:
+ label: '📜 Issue Labels'
+ options:
+ - label: 'Please familiarize yourself with the issue labels used in this project: [LABELS.md](https://github.com/backstage/backstage/blob/master/LABELS.md)'
+ required: true
+ # This field is managed by .common.yaml
+ - id: search-terms
+ type: textarea
+ attributes:
+ label: '🔎 Search Terms'
+ render: plain
+ description: |
+ What search terms did you use when trying to find similar issues?
+ placeholder: |
+ List of keywords you searched for before creating this issue. Write them down here so that others can find this issue more easily and help provide feedback.
+
+ e.g. "catalog github rate limited", "http root service configurer", "scaffolder template include", "entity card title"
+ validations:
+ required: true
+ # This field is managed by .common.yaml
+ - id: project-area
+ type: dropdown
+ attributes:
+ label: '🗃️ Project Area'
+ description: Which project area is this issue most closely related to? This will help find an owner for the issue faster.
+ options:
+ - Unknown
+ - Auditor
+ - Auth
+ - Catalog
+ - CLI Tooling
+ - Core Framework
+ - Design System
+ - Documentation
+ - Events System
+ - Home
+ - Kubernetes Plugin
+ - Microsite
+ - Notifications
+ - OpenAPI Tooling
+ - Permission Framework
+ - Search
+ - Software Templates
+ - TechDocs
+ validations:
+ required: true
+ - type: textarea
+ id: task
+ validations:
+ required: true
+ attributes:
+ label: '🛠️ Task'
+ description: Describe the task that requires attention
+ placeholder: |
+ Explain what needs to be accomplished, including any relevant context such as dependencies or related components.
+ - type: textarea
+ id: proposal
+ validations:
+ required: true
+ attributes:
+ label: '📊 Priority & Impact'
+ description: Explain the urgency and impact of this task
+ placeholder: |
+ Explain what level of prioritization this task should receive. Are there any particular benefits provided in newer versions in case of a version update, or any security vulnerabilities that can be addressed?
+
+ Also list any important dates or deadlines, such as current versions reaching end of life, or release dates for stable releases.
+ # This field is managed by .common.yaml
+ - id: read-code-of-conduct
+ type: checkboxes
+ attributes:
+ label: 'Have you read the Code of Conduct?'
+ options:
+ - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)'
+ required: true
+ # This field is managed by .common.yaml
+ - id: willing-to-submit-pr
+ type: dropdown
+ attributes:
+ label: Are you willing to submit a PR?
+ description: This is absolutely not required, but we are happy to guide you in the contribution process.
+ options:
+ - Undecided
+ - Yes, and I have enough information to get started
+ - Yes, but I would like some more guidance
+ - No, but I'm happy to collaborate on a PR with someone else
+ - No, I don't have time to work on this right now
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/05_other.yaml b/.github/ISSUE_TEMPLATE/05_other.yaml
new file mode 100644
index 0000000000..2238c47b6f
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/05_other.yaml
@@ -0,0 +1,24 @@
+name: '❓ Other'
+description: 'Something not captured by any other template'
+labels:
+ - needs:triage
+body:
+ - type: markdown
+ attributes:
+ value: |
+ This template can be used to create issues that don't fit any of the other templates. Valid examples include meta issues or other task tracking issues to help coordinate work, or informative issues that are kept for search purposes.
+
+ Do not use this template to submit support or help requests, as they will be closed. Please use other forums such as our [Community Discord](https://discord.gg/backstage-687207715902193673) instead.
+ - type: checkboxes
+ id: acknowledgement
+ attributes:
+ label: '📝 Acknowledgement'
+ options:
+ - label: I acknowledge that issues using this template may be closed without further explanation.
+ required: true
+ - type: textarea
+ id: contents
+ attributes:
+ label: Content
+ validations:
+ required: true
diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml
deleted file mode 100644
index 57595621e4..0000000000
--- a/.github/ISSUE_TEMPLATE/bug.yaml
+++ /dev/null
@@ -1,84 +0,0 @@
-name: '🐛 Bug Report'
-description: 'Submit a bug report to help us improve'
-title: '🐛 Bug Report:
'
-labels:
- - bug
-body:
- - type: markdown
- attributes:
- value: We value your time and effort to submit this bug report. 🙏
- - type: textarea
- id: description
- validations:
- required: true
- attributes:
- label: '📜 Description'
- description: 'A clear and concise description of what the bug is.'
- placeholder: 'It bugs out when ...'
- - type: textarea
- id: expected-behavior
- validations:
- required: true
- attributes:
- label: '👍 Expected behavior'
- description: 'What did you think should happen?'
- placeholder: 'It should ...'
- - type: textarea
- id: actual-behavior
- validations:
- required: true
- attributes:
- label: '👎 Actual Behavior with Screenshots'
- description: 'What did actually happen? Add screenshots, if applicable.'
- placeholder: 'It actually ...'
- - type: textarea
- id: steps-to-reproduce
- validations:
- required: true
- attributes:
- label: '👟 Reproduction steps'
- description: 'How do you trigger this bug? Please walk us through it step by step.'
- placeholder:
- "Provide a link to a live example, or an unambiguous set of steps to reproduce this bug. Include code or configuration to reproduce, if relevant.\n
- 1. Go to '...'\n
- 2. Click on '....'\n
- 3. Scroll down to '....'"
- - type: textarea
- id: context
- validations:
- required: false
- attributes:
- label: '📃 Provide the context for the Bug.'
- description: 'How has this issue affected you? What are you trying to accomplish?'
- placeholder: 'Providing context (e.g. links to configuration settings, stack trace or log data) helps us come up with a solution that is most useful in the real world.'
- - type: textarea
- id: environment
- validations:
- required: false
- attributes:
- label: '🖥️ Your Environment'
- description: 'Provide Browser Information
- Provide Output of `yarn backstage-cli info`'
- placeholder: 'Include as many relevant details about the environment you experienced the bug in.'
- - type: checkboxes
- id: no-duplicate-issues
- attributes:
- label: '👀 Have you spent some time to check if this bug has been raised before?'
- options:
- - label: "I checked and didn't find similar issue"
- required: true
- - type: checkboxes
- id: read-code-of-conduct
- attributes:
- label: '🏢 Have you read the Code of Conduct?'
- options:
- - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)'
- required: true
- - type: dropdown
- attributes:
- label: Are you willing to submit PR?
- description: This is absolutely not required, but we are happy to guide you in the contribution process.
- options:
- - Yes I am willing to submit a PR!
- - No, but I'm happy to collaborate on a PR with someone else
- - No, I don't have time to work on this right now
diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml
deleted file mode 100644
index b2d5a6be81..0000000000
--- a/.github/ISSUE_TEMPLATE/feature.yaml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: 🚀 Feature
-description: 'Submit a proposal for a new feature'
-title: '🚀 Feature: '
-labels: [enhancement]
-body:
- - type: markdown
- attributes:
- value: |
- We value your time and efforts to submit this Feature request form. 🙏
- - type: textarea
- id: feature-description
- validations:
- required: true
- attributes:
- label: '🔖 Feature description'
- description: 'A clear and concise description of what the feature is.'
- placeholder: 'You should add ...'
- - type: textarea
- id: context
- validations:
- required: true
- attributes:
- label: '🎤 Context'
- description: 'Please explain why this feature should be implemented and how it would be used. Add examples, if applicable.'
- placeholder: 'In my use-case, ...'
- - type: textarea
- id: implementation
- attributes:
- label: '✌️ Possible Implementation'
- description: 'A clear and concise description of what you want to happen.'
- placeholder: 'Not obligatory, but ideas as to the implementation of the addition or change'
- - type: checkboxes
- id: no-duplicate-issues
- attributes:
- label: '👀 Have you spent some time to check if this feature request has been raised before?'
- options:
- - label: "I checked and didn't find similar issue"
- required: true
- - type: checkboxes
- id: read-code-of-conduct
- attributes:
- label: '🏢 Have you read the Code of Conduct?'
- options:
- - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)'
- required: true
- - type: dropdown
- id: willing-to-submit-pr
- attributes:
- label: Are you willing to submit PR?
- description: This is absolutely not required, but we are happy to guide you in the contribution process.
- options:
- - Yes I am willing to submit a PR!
- - No, but I'm happy to collaborate on a PR with someone else
- - No, I don't have time to work on this right now
diff --git a/.github/ISSUE_TEMPLATE/rfc.yaml b/.github/ISSUE_TEMPLATE/rfc.yaml
deleted file mode 100644
index 8e73992dc8..0000000000
--- a/.github/ISSUE_TEMPLATE/rfc.yaml
+++ /dev/null
@@ -1,51 +0,0 @@
-name: 💬 RFC
-description: 'Request For Comments (RFC) from the community'
-title: '💬 RFC: '
-labels: [rfc]
-body:
- - type: markdown
- attributes:
- value: |
- We value your time and efforts to submit this RFC form. 🙏
- - type: textarea
- id: rfc-need
- validations:
- required: true
- attributes:
- label: '🔖 Need'
- description: 'Let us know why are you proposing this change'
- placeholder: 'The problem we’re trying to address and the benefits/impact we expect to get from this are ...'
- - type: textarea
- id: proposal
- validations:
- required: true
- attributes:
- label: '🎉 Proposal'
- description: 'Describe the proposal in as much detail as needed for reviewers to give concrete feedback.'
- placeholder: 'Take special care in this section to describe any implications on data privacy or security.'
- - type: textarea
- id: alternatives
- attributes:
- label: '〽️ Alternatives'
- description: 'What alternatives to the proposed solution were considered?'
- placeholder: 'What criteria/data was used to discard these?'
- - type: textarea
- id: risk
- attributes:
- label: '❌ Risks'
- description: 'What other things happening could conflict or compete (for example for resources) with the proposal?'
- placeholder: 'What risk are there and how do we plan to handle them?'
- - type: checkboxes
- id: no-duplicate-issues
- attributes:
- label: '👀 Have you spent some time to check if this RFC has been raised before?'
- options:
- - label: "I checked and didn't find similar issue"
- required: true
- - type: checkboxes
- id: read-code-of-conduct
- attributes:
- label: '🏢 Have you read the Code of Conduct?'
- options:
- - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)'
- required: true
diff --git a/.github/ISSUE_TEMPLATE/sync.js b/.github/ISSUE_TEMPLATE/sync.js
new file mode 100755
index 0000000000..b9252c3407
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/sync.js
@@ -0,0 +1,54 @@
+#!/usr/bin/env node
+
+const fs = require('node:fs');
+const path = require('node:path');
+const yaml = require('yaml');
+
+const TEMPLATES_DIR = path.join(__dirname);
+const COMMON_FILE = path.join(TEMPLATES_DIR, '.common.yaml');
+
+// Read the common fields from .common.yaml
+const commonDoc = yaml.parseDocument(fs.readFileSync(COMMON_FILE, 'utf8'));
+const commonFields = new Map(
+ commonDoc.get('body').items.map(field => {
+ field.commentBefore = ' This field is managed by .common.yaml';
+ return [field.get('id'), field];
+ }),
+);
+
+// Get all YAML files in the templates directory except .common.yaml
+const templateFiles = fs
+ .readdirSync(TEMPLATES_DIR)
+ .filter(file => file.endsWith('.yaml') && file !== '.common.yaml');
+
+// Process each template file
+for (const templateFile of templateFiles) {
+ const templatePath = path.join(TEMPLATES_DIR, templateFile);
+ const templateDoc = yaml.parseDocument(fs.readFileSync(templatePath, 'utf8'));
+
+ if (templateDoc.get('body')) {
+ const body = templateDoc.get('body');
+ for (let i = 0; i < body.items.length; i++) {
+ const field = body.items[i];
+
+ const commonField = commonFields.get(field.get('id'));
+ if (commonField) {
+ body.items[i] = commonField;
+ }
+ }
+
+ // Write the updated template back to file with matching style
+ fs.writeFileSync(
+ templatePath,
+ templateDoc.toString({
+ indent: 2,
+ lineWidth: 0,
+ minContentWidth: 0,
+ singleQuote: true,
+ }),
+ );
+ console.log(`Updated ${templateFile}`);
+ }
+}
+
+console.log('Sync complete!');
diff --git a/.github/ISSUE_TEMPLATE/ux_component.yaml b/.github/ISSUE_TEMPLATE/ux_component.yaml
deleted file mode 100644
index c528356eb3..0000000000
--- a/.github/ISSUE_TEMPLATE/ux_component.yaml
+++ /dev/null
@@ -1,53 +0,0 @@
-name: 🦄 UX Component
-description: 'For designers to request UX components to be added to the Backstage Storybook'
-title: '🦄 UX Component: '
-labels: [design]
-body:
- - type: markdown
- attributes:
- value: |
- We value your time and efforts to submit this RFC form. 🙏
- - type: textarea
- id: ux-general
- validations:
- required: true
- attributes:
- label: '🔖 General'
- description: 'Write a nice note to the community requesting the creation of a new component.'
- placeholder: 'Include an image of your component. Bonus points for a GIF!'
- - type: textarea
- id: usage
- validations:
- required: true
- attributes:
- label: '💻 Usage'
- description: "Tell us what's the point of this component/pattern is."
- placeholder: 'How does it help? How should it work? Any rules?'
- - type: textarea
- id: specs
- validations:
- required: true
- attributes:
- label: '📐 Specs'
- description: 'Include images that detail the redlines for your component.'
- placeholder: "Once we get our Figma workspace set up, we'll be posting the Figma files rather than doing specs by hand."
- - type: textarea
- id: future
- attributes:
- label: '🔮 Future'
- description: 'List out any upcoming, exciting functionality for this component.'
- placeholder: 'The component will have ...'
- - type: checkboxes
- id: no-duplicate-issues
- attributes:
- label: '👀 Have you spent some time to check if this UX Component request has been raised before?'
- options:
- - label: "I checked and didn't find similar issue"
- required: true
- - type: checkboxes
- id: read-code-of-conduct
- attributes:
- label: '🏢 Have you read the Code of Conduct?'
- options:
- - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)'
- required: true
diff --git a/.github/advanced-issue-labeler.yml b/.github/advanced-issue-labeler.yml
new file mode 100644
index 0000000000..4148b4236c
--- /dev/null
+++ b/.github/advanced-issue-labeler.yml
@@ -0,0 +1,58 @@
+policy:
+ - section:
+ - id: ['project-area']
+ block-list: []
+ label:
+ - name: 'area:auditor'
+ keys: ['Auditor']
+ - name: 'area:auth'
+ keys: ['Auth']
+ - name: 'area:catalog'
+ keys: ['Catalog']
+ - name: 'area:core'
+ keys: ['Core Framework', 'CLI Tooling']
+ - name: 'area:design-system'
+ keys: ['Design System']
+ - name: 'area:documentation'
+ keys: ['Documentation', 'Microsite']
+ - name: 'area:events'
+ keys: ['Events System']
+ - name: 'area:home'
+ keys: ['Home']
+ - name: 'area:kubernetes'
+ keys: ['Kubernetes Plugin']
+ - name: 'area:notifications'
+ keys: ['Notifications']
+ - name: 'area:openapi-tooling'
+ keys: ['OpenAPI Tooling']
+ - name: 'area:permission'
+ keys: ['Permission Framework']
+ - name: 'area:search'
+ keys: ['Search']
+ - name: 'area:scaffolder'
+ keys: ['Software Templates']
+ - name: 'area:techdocs'
+ keys: ['TechDocs']
+ - id: ['integration']
+ block-list: []
+ label:
+ - name: 'integration:aws'
+ keys: ['AWS']
+ - name: 'integration:azure'
+ keys: ['Azure', 'Azure DevOps']
+ - name: 'integration:bitbucket-cloud'
+ keys: ['Bitbucket Cloud']
+ - name: 'integration:bitbucket-server'
+ keys: ['Bitbucket Server']
+ - name: 'integration:gcp'
+ keys: ['GCP']
+ - name: 'integration:gerrit'
+ keys: ['Gerrit']
+ - name: 'integration:gitea'
+ keys: ['Gitea']
+ - name: 'integration:github'
+ keys: ['GitHub']
+ - name: 'integration:gitlab'
+ keys: ['GitLab']
+ - name: 'integration:other'
+ keys: ['Other']
diff --git a/.github/issue-labeler.yml b/.github/issue-labeler.yml
index c6c6ef115f..69eb2fddd9 100644
--- a/.github/issue-labeler.yml
+++ b/.github/issue-labeler.yml
@@ -10,5 +10,3 @@ area:scaffolder:
- '/scaffolder/i'
area:permission:
- '/permission/i'
-bep:
- - '/\bbep\b/i'
diff --git a/.github/labeler.yml b/.github/labeler.yml
index e6568091b8..1f76af339f 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -1,74 +1,60 @@
-area:catalog:
- - changed-files:
- - any-glob-to-any-file:
- - plugins/catalog/**/*
- - plugins/catalog-*/**/*
- - packages/catalog-*/**/*
-area:home:
- - changed-files:
- - any-glob-to-any-file:
- - plugins/home/**/*
-area:search:
- - changed-files:
- - any-glob-to-any-file:
- - plugins/search/**/*
- - plugins/search-*/**/*
- - packages/search-*/**/*
- - plugins/home/**/*
-area:events:
- - changed-files:
- - any-glob-to-any-file:
- - plugins/events-*/**/*
-area:kubernetes:
- - changed-files:
- - any-glob-to-any-file:
- - plugins/kubernetes/**/*
- - plugins/kubernetes-*/**/*
-area:permission:
- - changed-files:
- - any-glob-to-any-file:
- - plugins/permission-*/**/*
-area:scaffolder:
- - changed-files:
- - any-glob-to-any-file:
- - plugins/scaffolder/**/*
- - plugins/scaffolder-*/**/*
-area:techdocs:
- - changed-files:
- - any-glob-to-any-file:
- - plugins/techdocs/**/*
- - plugins/techdocs-*/**/*
- - packages/techdocs-*/**/*
-bep:
- - changed-files:
- - any-glob-to-any-file:
- - beps/**/*
-auth:
+area:auth:
- changed-files:
- any-glob-to-any-file:
- plugins/auth-*/**/*
- packages/core-app-api/src/apis/implementations/auth/**/*
- packages/core-app-api/src/lib/Auth*/**/*
- packages/core-plugin-api/src/apis/definitions/auth.ts
-documentation:
+area:catalog:
+ - changed-files:
+ - any-glob-to-any-file:
+ - plugins/catalog/**/*
+ - plugins/catalog-*/**/*
+ - packages/catalog-*/**/*
+area:documentation:
- changed-files:
- any-glob-to-any-file:
- docs/**/*
-homepage:
+area:events:
+ - changed-files:
+ - any-glob-to-any-file:
+ - plugins/events-*/**/*
+area:home:
- changed-files:
- any-glob-to-any-file:
- plugins/home/**/*
-microsite:
+area:kubernetes:
+ - changed-files:
+ - any-glob-to-any-file:
+ - plugins/kubernetes/**/*
+ - plugins/kubernetes-*/**/*
+area:microsite:
- changed-files:
- any-glob-to-any-file:
- microsite/**/*
-search:
+area:permission:
+ - changed-files:
+ - any-glob-to-any-file:
+ - plugins/permission-*/**/*
+area:search:
- changed-files:
- any-glob-to-any-file:
- - plugins/search/**/*
- - plugins/search-*/**/*
- packages/search-*/**/*
-storybook:
+ - plugins/home/**/*
+ - plugins/search-*/**/*
+ - plugins/search/**/*
+area:scaffolder:
+ - changed-files:
+ - any-glob-to-any-file:
+ - plugins/scaffolder/**/*
+ - plugins/scaffolder-*/**/*
+area:storybook:
- changed-files:
- any-glob-to-any-file:
- storybook/**/*
+area:techdocs:
+ - changed-files:
+ - any-glob-to-any-file:
+ - plugins/techdocs/**/*
+ - plugins/techdocs-*/**/*
+ - packages/techdocs-*/**/*
diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt
index f5defc22f2..c845a6060b 100644
--- a/.github/vale/config/vocabularies/Backstage/accept.txt
+++ b/.github/vale/config/vocabularies/Backstage/accept.txt
@@ -508,6 +508,7 @@ utils
Valentina
validator
validators
+Valkey
varchar
vite
VMware
diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml
index fb2a443f58..74473f30d5 100644
--- a/.github/workflows/api-breaking-changes.yml
+++ b/.github/workflows/api-breaking-changes.yml
@@ -27,7 +27,7 @@ jobs:
run: git fetch --depth 1 origin ${{ github.base_ref }}
- name: setup-node
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
registry-url: https://registry.npmjs.org/
diff --git a/.github/workflows/automate_issue_labels.yml b/.github/workflows/automate_issue_labels.yml
new file mode 100644
index 0000000000..b4a5d0f655
--- /dev/null
+++ b/.github/workflows/automate_issue_labels.yml
@@ -0,0 +1,19 @@
+name: Automate issue labels
+on:
+ issues:
+ types:
+ - labeled
+
+jobs:
+ stale:
+ permissions:
+ issues: write
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Remove needs:triage label
+ uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1
+ if: ${{ startsWith(github.event.label.name, 'priority:') || ( startsWith(github.event.label.name, 'needs:') && github.event.label.name != 'needs:triage' ) }}
+ with:
+ labels: needs:triage
diff --git a/.github/workflows/automate_stale.yml b/.github/workflows/automate_stale.yml
index c34c1312b1..6be74ae99f 100644
--- a/.github/workflows/automate_stale.yml
+++ b/.github/workflows/automate_stale.yml
@@ -19,8 +19,8 @@ jobs:
with:
egress-policy: audit
- - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
- id: stale
+ - name: Stale check - base
+ uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
with:
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
@@ -28,7 +28,7 @@ jobs:
for your contributions.
days-before-issue-stale: 60
days-before-issue-close: 7
- exempt-issue-labels: plugin,after-vacations,will-fix,bep
+ exempt-issue-labels: after vacations,no stale
stale-issue-label: stale
stale-pr-message: >
This PR has been automatically marked as stale because it has not had
@@ -37,6 +37,22 @@ jobs:
and we'll re-open the PR so that you can continue the contribution!
days-before-pr-stale: 14
days-before-pr-close: 7
- exempt-pr-labels: after-vacations,will-fix
+ exempt-pr-labels: after vacations,no stale
stale-pr-label: stale
operations-per-run: 100
+
+ - name: Stale check - bugs without repro
+ uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0
+ with:
+ stale-issue-message: >
+ This bug report has been automatically marked as stale because it has not had
+ recent activity and does not have valid steps to reproduce. It will be closed
+ if no further activity occurs. If the PR was closed and you want it re-opened,
+ let us know and we'll re-open the PR so that you can continue the contribution!
+ days-before-stale: 30
+ days-before-close: 7
+ days-before-pr-stale: -1
+ exempt-issue-labels: after vacations,no stale
+ only-labels: type:bug,needs:repro
+ stale-issue-label: stale
+ operations-per-run: 50
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 76898bc8d2..059596265e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -35,7 +35,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -71,7 +71,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -223,7 +223,7 @@ jobs:
run: git fetch origin ${{ github.event.pull_request.base.ref }}
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml
index df5ed9c07b..e1547801ef 100644
--- a/.github/workflows/deploy_docker-image.yml
+++ b/.github/workflows/deploy_docker-image.yml
@@ -31,7 +31,7 @@ jobs:
ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }}
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml
index ff49b94e56..de08e8a69c 100644
--- a/.github/workflows/deploy_microsite.yml
+++ b/.github/workflows/deploy_microsite.yml
@@ -59,7 +59,7 @@ jobs:
ref: refs/tags/${{ steps.find-release.outputs.result }}
- name: Use Node.js 20.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -115,7 +115,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Use Node.js 20.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -195,7 +195,7 @@ jobs:
egress-policy: audit
- name: Use Node.js 20.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -269,7 +269,7 @@ jobs:
run: ls microsite/build && ls microsite/build/storybook
- name: Deploy both microsite and storybook to gh-pages
- uses: JamesIves/github-pages-deploy-action@62fec3add6773ec5dbbf18d2ee4260911aa35cf4 # v4.6.9
+ uses: JamesIves/github-pages-deploy-action@6c2d9db40f9296374acc17b90404b6e8864128c8 # v4.7.3
with:
branch: gh-pages
folder: microsite/build
diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml
index e363fa4680..b1d79713a4 100644
--- a/.github/workflows/deploy_packages.yml
+++ b/.github/workflows/deploy_packages.yml
@@ -71,7 +71,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/issue.yaml b/.github/workflows/issue.yaml
index 8e30170aca..50d0e4ceb0 100644
--- a/.github/workflows/issue.yaml
+++ b/.github/workflows/issue.yaml
@@ -20,6 +20,12 @@ jobs:
with:
egress-policy: audit
+ # We need to checkout the `.github/ISSUE_TEMPLATE` for the advanced labeler action to be able to read the templates
+ # While at it we might as well checkout all of `.github` so that the labeling actions don't need to fetch their configs
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
+ with:
+ sparse-checkout: .github
+
- name: Add issue labels
uses: github/issue-labeler@c1b0f9f52a63158c4adc09425e858e87b32e9685 # v3.4
with:
@@ -29,3 +35,15 @@ jobs:
enable-versioned-regex: 0
not-before: 2024-04-19T15:03:51Z
repo-token: '${{ secrets.GITHUB_TOKEN }}'
+
+ # These two steps add labels based on user input in the issue form
+ - name: Parse issue form
+ uses: stefanbuck/github-issue-parser@2ea9b35a8c584529ed00891a8f7e41dc46d0441e # v3
+ id: issue-parser
+ with:
+ template-path: .github/ISSUE_TEMPLATE/.common.yaml
+ - name: Add advanced issue labels
+ uses: redhat-plumbers-in-action/advanced-issue-labeler@9e55064634b67244f7deb4211452b4a7217b93de # v2
+ with:
+ issue-form: ${{ steps.issue-parser.outputs.jsonString }}
+ token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 4865bee198..e16a481b4f 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -67,6 +67,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: 'Upload to code-scanning'
- uses: github/codeql-action/upload-sarif@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
+ uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
with:
sarif_file: results.sarif
diff --git a/.github/workflows/sync_canon.yml b/.github/workflows/sync_canon.yml
index a23e039b06..2177177df0 100644
--- a/.github/workflows/sync_canon.yml
+++ b/.github/workflows/sync_canon.yml
@@ -16,7 +16,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Use Node.js 20.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml
index 90a3f62aba..ee97f3fef3 100644
--- a/.github/workflows/sync_code-formatting.yml
+++ b/.github/workflows/sync_code-formatting.yml
@@ -20,7 +20,7 @@ jobs:
fetch-depth: 0
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml
index 1a47978b8c..1c9d37aef0 100644
--- a/.github/workflows/sync_release-manifest.yml
+++ b/.github/workflows/sync_release-manifest.yml
@@ -19,7 +19,7 @@ jobs:
ref: v${{ github.event.client_payload.version }}
- name: Use Node.js 20.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml
index f9e202e3e0..b7cc0b33c7 100644
--- a/.github/workflows/sync_snyk-github-issues.yml
+++ b/.github/workflows/sync_snyk-github-issues.yml
@@ -19,7 +19,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Use Node.js 20.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml
index 42fac516a2..5f0b44d8c6 100644
--- a/.github/workflows/sync_snyk-monitor.yml
+++ b/.github/workflows/sync_snyk-monitor.yml
@@ -58,6 +58,6 @@ jobs:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
NODE_OPTIONS: --max-old-space-size=7168
- name: Upload Snyk report
- uses: github/codeql-action/upload-sarif@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
+ uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
with:
sarif_file: snyk.sarif
diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml
index b1ed9448c0..02a303f9ea 100644
--- a/.github/workflows/verify_accessibility.yml
+++ b/.github/workflows/verify_accessibility.yml
@@ -26,7 +26,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Use Node.js 20.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
- name: yarn install
diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml
index b2555b64b0..5a1fb93d92 100644
--- a/.github/workflows/verify_codeql.yml
+++ b/.github/workflows/verify_codeql.yml
@@ -55,7 +55,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
+ uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -66,7 +66,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
+ uses: github/codeql-action/autobuild@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@@ -80,4 +80,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
+ uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16
diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml
index 7ae7e298e7..0995a85216 100644
--- a/.github/workflows/verify_e2e-linux.yml
+++ b/.github/workflows/verify_e2e-linux.yml
@@ -55,7 +55,7 @@ jobs:
git config --global user.name 'GitHub e2e user'
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml
index e5fe78cd8e..fc30b377bc 100644
--- a/.github/workflows/verify_e2e-techdocs.yml
+++ b/.github/workflows/verify_e2e-techdocs.yml
@@ -37,7 +37,7 @@ jobs:
egress-policy: audit
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.9'
diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml
index 6a27f119e3..0dc0bdaade 100644
--- a/.github/workflows/verify_e2e-windows.yml
+++ b/.github/workflows/verify_e2e-windows.yml
@@ -52,13 +52,13 @@ jobs:
git config --global user.name 'GitHub e2e user'
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
- name: setup python
- uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5.4.0
+ uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: '3.10'
diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml
index ce3be0cf42..74ffed4955 100644
--- a/.github/workflows/verify_microsite.yml
+++ b/.github/workflows/verify_microsite.yml
@@ -64,7 +64,7 @@ jobs:
ref: refs/tags/${{ steps.find-release.outputs.result }}
- name: Use Node.js 20.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -119,7 +119,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Use Node.js 20.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -195,10 +195,10 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Use Node.js 20.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
- - uses: actions/setup-python@42375524e23c412d93fb67b49958b491fce71c38 # v5
+ - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.9'
diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml
index fc461965a3..ee841f6fa8 100644
--- a/.github/workflows/verify_microsite_accessibility.yml
+++ b/.github/workflows/verify_microsite_accessibility.yml
@@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Use Node.js 20.x
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 20.x
diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml
index 075c328359..3cc846a706 100644
--- a/.github/workflows/verify_storybook.yml
+++ b/.github/workflows/verify_storybook.yml
@@ -37,7 +37,7 @@ jobs:
fetch-depth: 0 # Required to retrieve git history
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
@@ -54,7 +54,7 @@ jobs:
- run: yarn --cwd packages/canon build-storybook
- - uses: chromaui/action@649b4fd73c3f7cd7a65bd0b9f131349335ec661b # v11
+ - uses: chromaui/action@e8cc4c31775280b175a3c440076c00d19a9014d7 # v11
with:
token: ${{ secrets.GITHUB_TOKEN }}
# projectToken intentionally shared to allow collaborators to run Chromatic on forks
diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml
index 0ee95463d8..d7b172256d 100644
--- a/.github/workflows/verify_windows.yml
+++ b/.github/workflows/verify_windows.yml
@@ -36,7 +36,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: use node.js ${{ matrix.node-version }}
- uses: actions/setup-node@1d0ff469b7ec7b3cb9d8673fde0c81c44821de2a # v4.2.0
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ matrix.node-version }}
registry-url: https://registry.npmjs.org/ # Needed for auth
diff --git a/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch b/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch
new file mode 100644
index 0000000000..e81b6b1570
--- /dev/null
+++ b/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch
@@ -0,0 +1,11 @@
+diff --git a/lib/NpmHttpFetcher.d.ts b/lib/NpmHttpFetcher.d.ts
+index 015aeb8113776d0bc8d2d11154f02f0f2fd7d889..1398792d977fac7f10c7325a7a56d43914a26d17 100644
+--- a/lib/NpmHttpFetcher.d.ts
++++ b/lib/NpmHttpFetcher.d.ts
+@@ -9,5 +9,5 @@ export declare class NpmHttpFetcher implements Fetcher {
+ prefixPath: import("@yarnpkg/fslib").PortablePath;
+ checksum: string | null;
+ }>;
+- fetchFromNetwork(locator: Locator, opts: FetchOptions): Promise;
++ private fetchFromNetwork;
+ }
diff --git a/LABELS.md b/LABELS.md
new file mode 100644
index 0000000000..9ce65d0a71
--- /dev/null
+++ b/LABELS.md
@@ -0,0 +1,121 @@
+# GitHub Labels in Backstage
+
+This document explains the GitHub labels used in the Backstage main repository. Labels help categorize and track issues and pull requests, but are also used as a communication tool in order to help maintainers respond to issues and pull requests faster.
+
+## Issue Type Labels - `type:*`
+
+These labels help you understand what type of work is needed.
+
+- `type:bug` - Something doesn't work as expected and needs fixing.
+- `type:docs` - Documentation is missing or needs updating.
+- `type:suggestion` - An proposal for a new feature or change.
+- `type:maintenance` - Routine maintenance tasks, version bumps, cleanup, deprecations, etc.
+
+## Priority Labels - `priority:*`
+
+These labels all signal that the issue has been accepted for implementation, and further indicate its priority and whether the owners of the area will address it:
+
+- `priority:critical` - The owners of the area will address this as soon as possible.
+- `priority:roadmap` - The owners of the area have this on their roadmap and will address it when possible, some contributions may be welcome.
+- `priority:contrib-welcome` - The owners of the area are unlikely to address this in the short term, but contributions are welcome.
+- `priority:contrib-needed` - This will not be addressed by the owners of the area, but contributions are welcome.
+
+## Need Labels - `needs:*`
+
+These labels indicate what is needed to move an issue forward before it can be accepted for implementation:
+
+- `needs:bep` - The issue is an advanced addition that needs a [Backstage Enhancement Proposal](./beps/README.md).
+- `needs:direction` - The issue needs direction from the owners of the area.
+- `needs:more-info` - The issue needs more information from the author.
+- `needs:motivation` - It is not clear why this change is needed. The author should provide motivation for the change, for instance by giving examples of concrete use cases or scenarios.
+- `needs:repro` - The issue cannot be reproduced by the owners of the area. The author should provide more information to help them reproduce the issue, if possible with a minimal reproduction repository.
+- `needs:triage` - The issue needs initial review.
+- `needs:unblock` - The issue is blocked by another issue or upstream dependency.
+
+## Area Labels - `area:*`
+
+These labels indicate which part of Backstage an issue or pull request relates to. As a contributor, these help you find issues in areas you're interested in or have expertise in.
+
+- `area:auditor` - Auditor service and it's use in plugins.
+- `area:auth` - Authentication and 3rd party authorization.
+- `area:catalog` - The Catalog plugin and the Software Catalog model and integrations.
+- `area:core` - The core Backstage framework.
+- `area:design-system` - The Canon design system and library.
+- `area:documentation` - Documentation for adopters, users, and developers.
+- `area:events` - The Events system and integrations for other plugins.
+- `area:home` - The Home plugin and the main page of the Backstage site.
+- `area:kubernetes` - The Kubernetes plugin and integrations for other plugins.
+- `area:microsite` - The microsite at [backstage.io](https://backstage.io), excluding the documentation.
+- `area:notifications` - The Notifications plugin and integrations for other plugins.
+- `area:openapi-tooling` - The OpenAPI tooling it's use in plugins.
+- `area:permission` - The Permissions system and permission integrations from other plugins.
+- `area:scaffolder` - The Scaffolder plugin that powers Software Templates.
+- `area:search` - The Search plugin and search integrations for other plugins.
+- `area:techdocs` - The TechDocs plugin.
+
+## Integration Labels - `integration:*`
+
+These labels help you find issues related to specific external integrations:
+
+- `integration:aws` - [Amazon Web Services](https://aws.amazon.com/)
+- `integration:azure` - [Microsoft Azure](https://azure.microsoft.com/) and [Azure DevOps](https://dev.azure.com/)
+- `integration:bitbucket-cloud` - [Bitbucket Cloud](https://bitbucket.org/)
+- `integration:bitbucket-server` - Bitbucket Server (Stash)
+- `integration:gcp` - [Google Cloud Platform](https://cloud.google.com/)
+- `integration:gerrit` - [Gerrit](https://www.gerritcodereview.com/)
+- `integration:gitea` - [Gitea](https://gitea.com/)
+- `integration:github` - [GitHub](https://github.com/)
+- `integration:gitlab` - [GitLab](https://gitlab.com/)
+- `integration:other` - Any other integration
+
+## Domain Labels
+
+These labels indicate that an issue is related to specific domains of expertise.
+
+- `domain:a11y` - Web domain issues specifically related to accessibility.
+- `domain:design` - Visual design and user experience.
+- `domain:docs` - Documentation for adopters, users, and developers.
+- `domain:backend` - Backend development in Node.js.
+- `domain:tooling` - Tooling and automation in Node.js and GitHub Actions.
+- `domain:web` - Frontend development using TypeScript and React.
+
+## General Labels
+
+- `after vacations` - To be handled once the owners return from vacation.
+- `do not merge` - The pull request should not be merged.
+- `fix before release` - Should be handled before the next release.
+- `good first issue` - Good for new contributors.
+- `stale` - The issue or pull request has not seen any activity for a while and will be closed if no further activity is seen.
+- `no stale` - The issue or pull request should not be closed due to inactivity.
+
+## Common Issue Filters
+
+This is a collection of common issue filters that can help you find issues that you are looking for or that match your interests and skills.
+
+### New Contributors
+
+These issues are ideal for new contributors to get started and don't require much familiarity with Backstage.
+
+- [Backend](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Abackend%22%20)
+- [Documentation](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Adocs%22%20)
+- [Tooling](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Atooling%22%20)
+- [Web](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22good%20first%20issue%22%20label%3A%22domain%3Aweb%22%20)
+
+### Experienced Contributors
+
+These issues generally require some familiarity with Backstage and the codebase, and are either open for or require contributions from the community.
+
+- [Backend]()
+- [Documentation]()
+- [Tooling]()
+- [Web]()
+
+### Maintainer Lists
+
+These are useful lists for maintainers.
+
+- [After vacations](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22after%20vacations%22)
+- [Critical priority](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22priority%3Acritical%22)
+- [Fix before release](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22fix%20before%20release%22)
+- [Needs direction](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22needs%3Adirection%22)
+- [Needs triage](https://github.com/backstage/backstage/issues?q=is%3Aopen%20is%3Aissue%20label%3A%22needs%3Atriage%22)
diff --git a/OWNERS.md b/OWNERS.md
index 8190e31bfd..c6bbcf71d7 100644
--- a/OWNERS.md
+++ b/OWNERS.md
@@ -41,6 +41,18 @@ Scope: The Backstage design system, component library, as well as surrounding to
| Charles de Dreuille | Spotify | | [cdedreuille](https://github.com/cdedreuille) | `cdedreuille` |
| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip` |
+### Helm Charts
+
+Team: @backstage/helm-chart-maintainers
+
+Scope: The Backstage [Helm Chart(s)](https://github.com/backstage/charts).
+
+| Name | Organization | Team | GitHub | Discord |
+| -------------------- | ------------ | ---- | ---------------------------------------- | -------------- |
+| Andrew Block | Red Hat | | [sabre1041](http://github.com/sabre1041) | sabre1041#2622 |
+| Tom Coufal | Red Hat | | [tumido](http://github.com/tumido) | Tumi#4346 |
+| Vincenzo Scamporlino | Spotify | | [vinzscam](http://github.com/vinzscam) | vinzscam#6944 |
+
### Home
Team: @backstage/home-maintainers
@@ -54,30 +66,6 @@ Scope: The Backstage home page and information architecture
| Djamaile Rahamat | Spotify | Infinite Buck$ | [djamaile](http://github.com/djamaile) | dyoqi |
| Raghunandan Balachandran | Spotify | Infinite Buck$ | [soapraj](http://github.com/soapraj) | raghunandanb#1114 |
-### Search
-
-Team: @backstage/search-maintainers
-
-Scope: The Backstage Search plugin
-
-| Name | Organization | Team | GitHub | Discord |
-| --------------- | ------------ | ------- | ------------------------------------------------- | ---------------- |
-| Alex Lorenzi | Spotify | ProTean | [AlexLorenzi](https://github.com/AlexLorenzi) | alexlorenzi |
-| John Philip | Spotify | ProTean | [johnphilip283](https://github.com/johnphilip283) | john_philip#2399 |
-| Morgan Da Costa | Spotify | ProTean | [morgandc](https://github.com/morgandc) | - |
-
-### Helm Charts
-
-Team: @backstage/helm-chart-maintainers
-
-Scope: The Backstage [Helm Chart(s)](https://github.com/backstage/charts).
-
-| Name | Organization | Team | GitHub | Discord |
-| -------------------- | ------------ | ---- | ---------------------------------------- | -------------- |
-| Andrew Block | Red Hat | | [sabre1041](http://github.com/sabre1041) | sabre1041#2622 |
-| Tom Coufal | Red Hat | | [tumido](http://github.com/tumido) | Tumi#4346 |
-| Vincenzo Scamporlino | Spotify | | [vinzscam](http://github.com/vinzscam) | vinzscam#6944 |
-
### Kubernetes
Team: @backstage/kubernetes-maintainers
@@ -103,6 +91,18 @@ Scope: The Permission Framework and plugins integrating with the permission fram
| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` |
| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` |
+### Search
+
+Team: @backstage/search-maintainers
+
+Scope: The Backstage Search plugin
+
+| Name | Organization | Team | GitHub | Discord |
+| --------------- | ------------ | ------- | ------------------------------------------------- | ---------------- |
+| Alex Lorenzi | Spotify | ProTean | [AlexLorenzi](https://github.com/AlexLorenzi) | alexlorenzi |
+| John Philip | Spotify | ProTean | [johnphilip283](https://github.com/johnphilip283) | john_philip#2399 |
+| Morgan Da Costa | Spotify | ProTean | [morgandc](https://github.com/morgandc) | - |
+
### TechDocs
Team: @backstage/techdocs-maintainers
@@ -121,6 +121,16 @@ Scope: The TechDocs plugin and related tooling
These incubating project areas have shared ownership with @backstage/maintainers.
+### Auditor
+
+Team: @backstage/auditor-maintainers
+
+Scope: The auditor core service, along with auditor usage in the main repository
+
+| Name | Organization | GitHub | Discord |
+| ------------ | ------------ | ----------------------------------------------- | -------------- |
+| Paul Schultz | Red Hat | [schultzp2020](https://github.com/schultzp2020) | `schultzp2020` |
+
### Community Plugins
Team: @backstage/community-plugins-maintainers
@@ -134,6 +144,18 @@ Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugi
| Kashish Mittal | Red Hat | [04kash](https://github.com/04kash) | `kashh._.` |
| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` |
+### Documentation
+
+Team: @backstage/documentation-maintainers
+
+Scope: The Backstage Documentation and Microsite, excluding the plugins listing
+
+| Name | Organization | GitHub | Discord |
+| --------------- | ------------- | ----------------------------------------------------- | ------------- |
+| Andre Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` |
+| Aramis Sennyey | DoorDash | [aramissennyeydd](https://github.com/aramissennyeydd) | `Aramis#7984` |
+| Peter Macdonald | VodafoneZiggo | [Parsifal-M](https://github.com/Parsifal-M) | `parsifal` |
+
### Events
Team: @backstage/events-maintainers
@@ -174,28 +196,6 @@ Scope: The Scaffolder frontend and backend plugins, and related tooling.
| ------------------- | ------------ | ------------------------------------- | ---------------- |
| Bogdan Nechyporenko | Bol.com | [acierto](https://github.com/acierto) | `bogdan_haarlem` |
-### Auditor
-
-Team: @backstage/auditor-maintainers
-
-Scope: The auditor core service, along with auditor usage in the main repository
-
-| Name | Organization | GitHub | Discord |
-| ------------ | ------------ | ----------------------------------------------- | -------------- |
-| Paul Schultz | Red Hat | [schultzp2020](https://github.com/schultzp2020) | `schultzp2020` |
-
-### Documentation
-
-Team: @backstage/documentation-maintainers
-
-Scope: The Backstage Documentation
-
-| Name | Organization | GitHub | Discord |
-| --------------- | ------------- | ----------------------------------------------------- | ------------- |
-| Andre Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` |
-| Aramis Sennyey | DoorDash | [aramissennyeydd](https://github.com/aramissennyeydd) | `Aramis#7984` |
-| Peter Macdonald | VodafoneZiggo | [Parsifal-M](https://github.com/Parsifal-M) | `parsifal` |
-
## Sponsors
| Name | Organization | GitHub | Email |
diff --git a/beps/0007-auth-external-services/README.md b/beps/0007-auth-external-services/README.md
index d49cd7992f..1d1f3e74b6 100644
--- a/beps/0007-auth-external-services/README.md
+++ b/beps/0007-auth-external-services/README.md
@@ -1,6 +1,6 @@
---
title: Authentication of External Services
-status: provisional
+status: implemented
authors:
- '@mareklibra'
owners:
diff --git a/canon-docs/yarn.lock b/canon-docs/yarn.lock
index 4c6f663544..d4049730ff 100644
--- a/canon-docs/yarn.lock
+++ b/canon-docs/yarn.lock
@@ -722,20 +722,20 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/components@npm:8.6.8":
- version: 8.6.8
- resolution: "@storybook/components@npm:8.6.8"
+"@storybook/components@npm:8.6.12":
+ version: 8.6.12
+ resolution: "@storybook/components@npm:8.6.12"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- checksum: 10/fb69c6d7804f91fffd3fc7d42ba5b144c7b808af6e6b764c14cdef11263897e551e5cc8a0fa379b9ed73f5c0438ed32d355c7ed63801046e1cb078c479d4cecf
+ checksum: 10/ccc6af275bdfbc66de8afb272f59b2b4b6b76bb2961903335cb62e7defae4a2368bd8f5a2008f8598dd8417e5368d824bce836ca96c3c6ff659dbf2f081ec0dd
languageName: node
linkType: hard
-"@storybook/core@npm:8.6.8":
- version: 8.6.8
- resolution: "@storybook/core@npm:8.6.8"
+"@storybook/core@npm:8.6.12":
+ version: 8.6.12
+ resolution: "@storybook/core@npm:8.6.12"
dependencies:
- "@storybook/theming": "npm:8.6.8"
+ "@storybook/theming": "npm:8.6.12"
better-opn: "npm:^3.0.2"
browser-assert: "npm:^1.2.1"
esbuild: "npm:^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0"
@@ -751,7 +751,7 @@ __metadata:
peerDependenciesMeta:
prettier:
optional: true
- checksum: 10/0ca71e2d553acca34a3e6164726f1973a7bfc67a03a179066f92035c93adba356cb69e4368fb5b2cdbe5835d7a08b73e61fb77dabc390a38e38cd7d56ff9f521
+ checksum: 10/78776f51b9eae00f9387421b33b646b1dc67ef833fd6272de03399daa7f0ffa248c65b5f24d5d2a9af923a029d06d84d5425e3455302ece542bf47c7a9ec0df6
languageName: node
linkType: hard
@@ -762,66 +762,66 @@ __metadata:
languageName: node
linkType: hard
-"@storybook/manager-api@npm:8.6.8":
- version: 8.6.8
- resolution: "@storybook/manager-api@npm:8.6.8"
+"@storybook/manager-api@npm:8.6.12":
+ version: 8.6.12
+ resolution: "@storybook/manager-api@npm:8.6.12"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- checksum: 10/ee575781572898fc2fa734750ccb4bad09fc021bd0878ca9a05d9668cb336610d53e8110f4ff3c2c180573a64e9caba8f50f59d0bd3f0398a5c98e2eafdd91ae
+ checksum: 10/d1c44c6649a024c4007461c12a15337f5d13532dbaccc4c02f71bd99599fb973e2574eb8f1bc2d93e05da24e4ae43fa47ec637a7c4cccf5ffc67045cafbf087c
languageName: node
linkType: hard
-"@storybook/preview-api@npm:8.6.8":
- version: 8.6.8
- resolution: "@storybook/preview-api@npm:8.6.8"
+"@storybook/preview-api@npm:8.6.12":
+ version: 8.6.12
+ resolution: "@storybook/preview-api@npm:8.6.12"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- checksum: 10/ac923a342d09c9e51f67734acaea19a41dc87cfc6cb22aea262591396d41cd4b69896417b618c55f17a9d64660b659813475c30d338c9bb6aa45391cfe8328f0
+ checksum: 10/d24f11e4e54e9e51297b0f87e1d462b3f14a974b4681f31d93b62b0706ce5b5ed4ffaaac521ec049dcb0e08e7aa7590f2e039aee4bbe9f85033d69474d982f23
languageName: node
linkType: hard
-"@storybook/react-dom-shim@npm:8.6.8":
- version: 8.6.8
- resolution: "@storybook/react-dom-shim@npm:8.6.8"
+"@storybook/react-dom-shim@npm:8.6.12":
+ version: 8.6.12
+ resolution: "@storybook/react-dom-shim@npm:8.6.12"
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- storybook: ^8.6.8
- checksum: 10/d49bc7f96349460c13c61538116c634174418dd174e80336457ee90f9e054efa6030ec8def9cab870a85657eb8050ee8c5599e0643736642608488b43de54b1f
+ storybook: ^8.6.12
+ checksum: 10/7677b4fae978209af239471f9eb609db4318815bd4249a0f48b9875482d9ae910b93fbe4db5d7f794ecc2a1249edf40da26af9de673c941c48fccc4007819c96
languageName: node
linkType: hard
"@storybook/react@npm:^8.6.8":
- version: 8.6.8
- resolution: "@storybook/react@npm:8.6.8"
+ version: 8.6.12
+ resolution: "@storybook/react@npm:8.6.12"
dependencies:
- "@storybook/components": "npm:8.6.8"
+ "@storybook/components": "npm:8.6.12"
"@storybook/global": "npm:^5.0.0"
- "@storybook/manager-api": "npm:8.6.8"
- "@storybook/preview-api": "npm:8.6.8"
- "@storybook/react-dom-shim": "npm:8.6.8"
- "@storybook/theming": "npm:8.6.8"
+ "@storybook/manager-api": "npm:8.6.12"
+ "@storybook/preview-api": "npm:8.6.12"
+ "@storybook/react-dom-shim": "npm:8.6.12"
+ "@storybook/theming": "npm:8.6.12"
peerDependencies:
- "@storybook/test": 8.6.8
+ "@storybook/test": 8.6.12
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
- storybook: ^8.6.8
+ storybook: ^8.6.12
typescript: ">= 4.2.x"
peerDependenciesMeta:
"@storybook/test":
optional: true
typescript:
optional: true
- checksum: 10/b2e664d6ccb91fd269b9168defb7d6c673a1e35119c61518bf72c8612884873eadcfadffd5b64171b26fbd1cc498828ddf52a9fb2fe34fdac9edb3f2d86a62e8
+ checksum: 10/d8258c82743906f48a872a781f3e5a63e9ce3fda2ba3b911e959cf62ebda43989b23746257d97905addb7142ac7e53d0089dc178bcbeea48ed4d37d025dd047b
languageName: node
linkType: hard
-"@storybook/theming@npm:8.6.8":
- version: 8.6.8
- resolution: "@storybook/theming@npm:8.6.8"
+"@storybook/theming@npm:8.6.12":
+ version: 8.6.12
+ resolution: "@storybook/theming@npm:8.6.12"
peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
- checksum: 10/1be0dea23178af562133f1047165e170f7e90bec4c3cedbdcb59df22685f314273a06a4677835ab84e6dbc682b803590adad6963217971c8136b076ce69800aa
+ checksum: 10/c811d9dbb9eaaa680b922111fca126a2985f2238dfb01c1cd23184323eea12899dc9f079063ac42c5e63b0c83de326bd9cc17241e4060ff04e860c57a55fb8b9
languageName: node
linkType: hard
@@ -932,21 +932,21 @@ __metadata:
linkType: hard
"@types/react-dom@npm:^18.0.0":
- version: 18.3.5
- resolution: "@types/react-dom@npm:18.3.5"
+ version: 18.3.6
+ resolution: "@types/react-dom@npm:18.3.6"
peerDependencies:
"@types/react": ^18.0.0
- checksum: 10/02095b326f373867498e0eb2b5ebb60f9bd9535db0d757ea13504c4b7d75e16605cf1d43ce7a2e67893d177b51db4357cabb2842fb4257c49427d02da1a14e09
+ checksum: 10/ae179355401c64423d39946eda22c5f7f74c94ce61c21505024d4d82c33853ec40bc9b370f75e4a7750b0524aba4d95a43fcc328d8d14684dc2abb41ba529de8
languageName: node
linkType: hard
"@types/react@npm:^18.0.0":
- version: 18.3.18
- resolution: "@types/react@npm:18.3.18"
+ version: 18.3.20
+ resolution: "@types/react@npm:18.3.20"
dependencies:
"@types/prop-types": "npm:*"
csstype: "npm:^3.0.2"
- checksum: 10/7fdd8b853e0d291d4138133f93f8d5c333da918e5804afcea61a923aab4bdfc9bb15eb21a5640959b452972b8715ddf10ffb12b3bd071898b9e37738636463f2
+ checksum: 10/020c51e63b60862e6d772f0cdea0b9441182eedab6289dabd8add0708ded62003834c4e7c6f23a1ccd3ca9486b46296057c3f881c34261a0483765351f8d0bc3
languageName: node
linkType: hard
@@ -1100,8 +1100,8 @@ __metadata:
linkType: hard
"@uiw/codemirror-themes@npm:^4.23.7":
- version: 4.23.10
- resolution: "@uiw/codemirror-themes@npm:4.23.10"
+ version: 4.23.11
+ resolution: "@uiw/codemirror-themes@npm:4.23.11"
dependencies:
"@codemirror/language": "npm:^6.0.0"
"@codemirror/state": "npm:^6.0.0"
@@ -1110,7 +1110,7 @@ __metadata:
"@codemirror/language": ">=6.0.0"
"@codemirror/state": ">=6.0.0"
"@codemirror/view": ">=6.0.0"
- checksum: 10/cdeb3be64e5d1e537fcd03b4945a1b02ccd342f1e8b16e477abc76eff53e8e4115cf21eaac0f4e52d10fffa9690e3f71cc9c59ad2cba2be098a9fda0dacf8ce1
+ checksum: 10/cbdb7865410e4dead2a96857d14e7dd50fcaf3af9f013ce932578b7fdd824648aa8f21f7964e3b1d657f79961f14db605f28178fc81ec53ee77e27c1a066ff32
languageName: node
linkType: hard
@@ -5052,10 +5052,10 @@ __metadata:
linkType: hard
"storybook@npm:^8.6.8":
- version: 8.6.8
- resolution: "storybook@npm:8.6.8"
+ version: 8.6.12
+ resolution: "storybook@npm:8.6.12"
dependencies:
- "@storybook/core": "npm:8.6.8"
+ "@storybook/core": "npm:8.6.12"
peerDependencies:
prettier: ^2 || ^3
peerDependenciesMeta:
@@ -5065,7 +5065,7 @@ __metadata:
getstorybook: ./bin/index.cjs
sb: ./bin/index.cjs
storybook: ./bin/index.cjs
- checksum: 10/9d7cd271fda874063cb59795ece7da907608d47e8a1ac441c4b30670c542fb929c9c7f30447ba3151a41631f0ec1f4dbb10cc214853f3a703f2019f23811e5bb
+ checksum: 10/babd1d086eb02ba25ee659e02e619f7797a6b91028ad74d2da0ab77e72021cd5c2ac4f239668f15156aabf00bd97066a774370dceadf178b1e649bf971160a26
languageName: node
linkType: hard
diff --git a/docs/assets/integrations/azure/org.svg b/docs/assets/integrations/azure/org.svg
new file mode 100644
index 0000000000..d30fc6e1d8
--- /dev/null
+++ b/docs/assets/integrations/azure/org.svg
@@ -0,0 +1 @@
+someRootGroup alpha beta sharks goats dolphins John Anna Patricia Emil Lovisa Chandra
\ No newline at end of file
diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md
index cce8f08b7a..6a59805b0b 100644
--- a/docs/auth/identity-resolver.md
+++ b/docs/auth/identity-resolver.md
@@ -402,6 +402,50 @@ async signInResolver({ profile }, ctx) {
}
```
+## Reducing the size of issued tokens
+
+By default the auth backend will issue user identity tokens that include the
+ownership references of the user in the `ent` claim of the JWT payload. This is
+done to make it easier and more efficient for consumers of the token to resolve
+ownership of the user. However, depending on the shape of your organization and
+how you resolve ownership claims, these tokens can grow quite large.
+
+To address this, the auth backend now supports the configuration flag
+`auth.omitIdentityTokenOwnershipClaim` that causes the `ent` claim to be omitted
+from the token. This can be set to `true` in the `app-config.yaml` file.
+
+```yaml title="in app-config.yaml"
+auth:
+ omitIdentityTokenOwnershipClaim: true
+```
+
+When this flag is set, the `ent` claim will no longer be present in the token,
+and consumers of the token will need to call the `/v1/userinfo` endpoint on the
+auth backend to fetch the ownership references of the user. However, there's usually no
+action required for consumers. Clients will still receive the full set
+of claims during authentication, and any plugin backends will already need to
+use the
+[`UserInfoService`](../backend-system/core-services/user-info.md) to
+access the ownership references from user credentials, which already calls the
+user info endpoint if necessary.
+
+When enabling this flag, it is important that any custom sign-in resolvers directly return the result of the sign-in method. For example, the following would not work:
+
+```ts
+const { token } = await ctx.issueToken({
+ claims: { sub: entityRef, ent: [entityRef] },
+});
+return { token }; // WARNING: This will not work
+```
+
+Instead, the sign-in resolver should directly return the result:
+
+```ts
+return ctx.issueToken({
+ claims: { sub: entityRef, ent: [entityRef] },
+});
+```
+
## Profile Transforms
Similar to a custom sign-in resolver, you can also write a custom profile transform
diff --git a/docs/backend-system/core-services/scheduler.md b/docs/backend-system/core-services/scheduler.md
index 154878ddfc..eb25692698 100644
--- a/docs/backend-system/core-services/scheduler.md
+++ b/docs/backend-system/core-services/scheduler.md
@@ -38,3 +38,94 @@ createBackendPlugin({
},
});
```
+
+## REST API
+
+The scheduler exposes a REST API on top of each plugin's base URL, that lets you inspect and affect the current state of all of that plugin's tasks.
+
+### `GET /.backstage/scheduler/v1/tasks`
+
+Lists all tasks that the given plugin registered at startup, and their current states. The response shape is as follows:
+
+```json
+{
+ "tasks": [
+ {
+ "taskId": "InternalOpenApiDocumentationProvider:refresh",
+ "pluginId": "catalog",
+ "scope": "global",
+ "settings": {
+ "version": 2,
+ "cadence": "PT10S",
+ "initialDelayDuration": "PT10S",
+ "timeoutAfterDuration": "PT1M"
+ },
+ "taskState": {
+ "status": "idle",
+ "startsAt": "2025-04-11T20:35:13.418+02:00",
+ "lastRunEndedAt": "2025-04-11T20:35:03.453+02:00"
+ },
+ "workerState": {
+ "status": "initial-wait"
+ }
+ }
+ ]
+}
+```
+
+Each task has the following properties:
+
+| Field | Format | Description |
+| ------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `taskId` | string | A unique (per plugin) ID for the task |
+| `pluginId` | string | The plugin where the task is scheduled |
+| `scope` | string | Either `local` (runs on each worker node with potential overlaps, similar to `setInterval`), or `global` (runs on one worker node at a time, without overlaps) |
+| `settings` | object | Serialized form of the initial settings passed in when scheduling the task. The only completely fixed well known field is `version`; the others depend on what version is used |
+| `settings.version` | string | Internal identifier of the format of the settings object. The format of this object can change completely for each version. This document describes version 2 specifically |
+| `settings.cadence` | string; ISO duration | How often the task runs. Either the string `manual` (only runs when manually triggered), or an ISO duration string starting with the letter P, or a `cron` format string |
+| `settings.initialDelayDuration` | string; ISO duration | How long workers wait at service startup before starting to look for work, to give the service some time to stabilize, as an ISO duration string (if configured) |
+| `settings.timeoutAfterDuration` | string; ISO duration | How long after a task starts that it's considered timed out and available for retries |
+| `taskState` | object | The current state of the task (see below for details) |
+| `workerState` | object | The status of the worker responsible for task |
+
+The `taskState` shape depends on whether the task is currently running or not. When running:
+
+| Field | Format | Optional | Description |
+| -------------------------- | ----------------------------- | -------- | ------------------------------------------------------------------------- |
+| `taskState.status` | string | | `running` |
+| `taskState.startedAt` | string; ISO timestamp | | When the current task run started |
+| `taskState.timesOutAt` | string; ISO timestamp | | When the current task run will time out if it does not finish before that |
+| `taskState.lastRunError` | string; JSON serialized error | optional | When the task last ran, if it threw an error, this field contains it |
+| `taskState.lastRunEndedAt` | string; ISO timestamp | optional | When the task last ran, it ended at this time |
+
+When the task is idle:
+
+| Field | Format | Optional | Description |
+| -------------------------- | ----------------------------- | -------- | ------------------------------------------------------------------------------------------ |
+| `taskState.status` | string | | `idle` |
+| `taskState.startsAt` | string; ISO timestamp | optional | When the task is scheduled to run next; will not be set if the task uses manual scheduling |
+| `taskState.lastRunError` | string; JSON serialized error | optional | When the task last ran, if it threw an error, this field contains it |
+| `taskState.lastRunEndedAt` | string; ISO timestamp | optional | When the task last ran, it ended at this time |
+
+The `workerState` shape is as follows:
+
+| Field | Description |
+| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `workerState.status` | The status of the worker responsible for task; either `initial-wait` (right at service startup), `running` (task is currently running), or `idle` (task is not running at the moment) |
+
+### `POST /.backstage/scheduler/v1/tasks//trigger`
+
+Schedules the given task ID for immediate execution, instead of waiting for its
+next scheduled time slot to arrive.
+
+Note that there can still be an additional small delay before a worker discovers
+that the task is due and actually picks it up. This typically takes less than a
+second, but it can vary.
+
+The request has no body.
+
+Responds with
+
+- `200 OK` if successful
+- `404 Not Found` if there was no such registered task for this plugin
+- `409 Conflict` if the task was already in a running state
diff --git a/docs/features/kubernetes/authenticationstrategy.md b/docs/features/kubernetes/authenticationstrategy.md
index c902995a19..e39201aaeb 100644
--- a/docs/features/kubernetes/authenticationstrategy.md
+++ b/docs/features/kubernetes/authenticationstrategy.md
@@ -182,7 +182,7 @@ Let's create a new file to house the Pinniped authentication strategy which will
```ts title="plugins/kubernetes-backend-module-pinniped/src/PinnipedStrategy.ts"
import { KubernetesRequestAuth } from '@backstage/plugin-kubernetes-common';
-import { Logger } from 'winston';
+import { LoggerService } from '@backstage/backend-plugin-api';
import {
AuthMetadata,
AuthenticationStrategy,
@@ -197,7 +197,7 @@ import { JsonObject } from '@backstage/types';
export class PinnipedStrategy implements AuthenticationStrategy {
private pinnipedHelper: PinnipedHelper;
- constructor(private readonly logger: Logger) {
+ constructor(private readonly logger: LoggerService) {
this.pinnipedHelper = new PinnipedHelper(logger);
}
@@ -239,7 +239,7 @@ export class PinnipedStrategy implements AuthenticationStrategy {
}
```
-The `PinnipedStrategy` implements the `AuthenticationStrategy` interface, it uses the PinnipedHelper class to exchange the clusterIdToken ( created by a custom Pinniped client-side `KubernetesAuthProvider` ) for a x509 certificate, certificate that will allow us to consume the kubernetes cluster. It also returns the audience value to the front-end through `presentAuthMetadata`.
+The `PinnipedStrategy` implements the `AuthenticationStrategy` interface, it uses the `PinnipedHelper` class to exchange the `clusterIdToken` ( created by a custom Pinniped client-side `KubernetesAuthProvider` ) for a x509 certificate, certificate that will allow us to consume the kubernetes cluster. It also returns the audience value to the front-end through `presentAuthMetadata`.
> Notice that the PinnipedHelper class will help you only to exchange the token, It doesn't introduce a cache layer, something that your strategy could introduce.
@@ -250,12 +250,8 @@ import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
-import {
- AuthenticationStrategy,
- kubernetesAuthStrategyExtensionPoint,
-} from '@backstage/plugin-kubernetes-node';
+import { kubernetesAuthStrategyExtensionPoint } from '@backstage/plugin-kubernetes-node';
import { PinnipedStrategy } from './PinnipedStrategy';
-import { loggerToWinstonLogger } from '@backstage/backend-common';
export const kubernetesModulePinniped = createBackendModule({
pluginId: 'kubernetes',
@@ -267,11 +263,7 @@ export const kubernetesModulePinniped = createBackendModule({
authStrategy: kubernetesAuthStrategyExtensionPoint,
},
async init({ logger, authStrategy }) {
- const winstonLogger = loggerToWinstonLogger(logger);
- const pinnipedStrategy: AuthenticationStrategy = new PinnipedStrategy(
- winstonLogger,
- );
- authStrategy.addAuthStrategy('pinniped', pinnipedStrategy);
+ authStrategy.addAuthStrategy('pinniped', new PinnipedStrategy(logger));
},
});
},
@@ -288,7 +280,6 @@ import { KubernetesBuilder } from '@backstage/plugin-kubernetes-backend';
import { Router } from 'express';
import { PluginEnvironment } from '../types';
import { CatalogClient } from '@backstage/catalog-client';
-import { loggerToWinstonLogger } from '@backstage/backend-common';
import { AuthenticationStrategy } from '@backstage/plugin-kubernetes-node';
import { PinnipedStrategy } from '@internal/plugin-kubernetes-backend-module-pinniped';
@@ -296,9 +287,8 @@ export default async function createPlugin(
env: PluginEnvironment,
): Promise {
const catalogApi = new CatalogClient({ discoveryApi: env.discovery });
- const winstonLogger = loggerToWinstonLogger(env.logger);
const pinnipedStrategy: AuthenticationStrategy = new PinnipedStrategy(
- winstonLogger,
+ env.logger,
);
const { router } = await KubernetesBuilder.createBuilder({
logger: env.logger,
diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md
index c323d5118a..a22a7d94d9 100644
--- a/docs/features/kubernetes/configuration.md
+++ b/docs/features/kubernetes/configuration.md
@@ -355,8 +355,7 @@ Specifies the app that provides the Kubernetes dashboard.
This will be used for formatting links to kubernetes objects inside the
dashboard.
-The supported dashboards are: `standard`, `rancher`, `openshift`, `gke`, `aks`,
-`eks`. However, not all of them are implemented yet, so please contribute!
+The supported dashboards are: `aks`, `eks`, `gke`, `headlamp`, `openshift`, `rancher`, `standard`. However, not all of them are implemented yet, so please contribute!
Note that it will default to the regular dashboard provided by the Kubernetes
project (`standard`), that can run in any Kubernetes cluster.
@@ -448,6 +447,41 @@ cluster locator method can be configured in this way.
Configures which [custom resources][3] to look for when returning an entity's
Kubernetes resources belonging to the cluster. Same specification as [`customResources`](#customresources-optional)
+#### `headlamp`
+
+When using `headlamp` as your dashboard, you have two configuration options:
+
+1. External Headlamp instance:
+
+```yaml
+kubernetes:
+ clusterLocatorMethods:
+ - type: 'config'
+ clusters:
+ - url: http://127.0.0.1:9999
+ name: my-cluster
+ dashboardUrl: http://headlamp.example.com # Your Headlamp instance URL
+ dashboardApp: 'headlamp'
+ dashboardParameters:
+ clusterName: 'my-cluster' # Optional, defaults to 'default'
+```
+
+2. Internal Headlamp (When using the Headlamp plugin for Backstage):
+
+```yaml
+kubernetes:
+ clusterLocatorMethods:
+ - type: 'config'
+ clusters:
+ - url: http://127.0.0.1:9999
+ name: my-cluster
+ dashboardApp: 'headlamp'
+ dashboardParameters:
+ internal: true
+ headlampRoute: '/headlamp' # Optional, defaults to '/headlamp'
+ clusterName: 'my-cluster' # Optional, defaults to 'default'
+```
+
#### `gke`
This cluster locator is designed to work with Kubernetes clusters running in
diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md
index 44946b334a..8938fefdf6 100644
--- a/docs/features/software-catalog/external-integrations.md
+++ b/docs/features/software-catalog/external-integrations.md
@@ -70,7 +70,7 @@ putting all extensions like this in a backend module package of their own in the
`plugins` folder of your Backstage repo:
```sh
-yarn new --select backend-module --option pluginId=catalog
+yarn new --select backend-plugin-module --option pluginId=catalog
```
The class will have this basic structure:
diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md
index a65bfb4a44..ac1d0ee8cd 100644
--- a/docs/features/software-catalog/index.md
+++ b/docs/features/software-catalog/index.md
@@ -108,7 +108,7 @@ metadata in the software catalog after a short while.
## Finding software in the catalog
-By default the software catalog shows components owned by the team of the logged
+By default, the software catalog shows components owned by the team of the logged
in user. But you can also switch to _All_ to see all the components across your
company's software ecosystem. Basic inline _search_ and _column filtering_ makes
it easy to browse a big set of components.
diff --git a/docs/frontend-system/architecture/15-plugins.md b/docs/frontend-system/architecture/15-plugins.md
index a54573427d..825f2a3982 100644
--- a/docs/frontend-system/architecture/15-plugins.md
+++ b/docs/frontend-system/architecture/15-plugins.md
@@ -28,7 +28,7 @@ const myPage = PageBlueprint.make({
});
export default createFrontendPlugin({
- id: 'my-plugin',
+ pluginId: 'my-plugin',
extensions: [myPage],
});
```
diff --git a/docs/frontend-system/architecture/36-routes.md b/docs/frontend-system/architecture/36-routes.md
index e5d316e751..7b6376b2eb 100644
--- a/docs/frontend-system/architecture/36-routes.md
+++ b/docs/frontend-system/architecture/36-routes.md
@@ -55,7 +55,7 @@ const catalogIndexPage = createPageExtension({
});
export default createFrontendPlugin({
- id: 'catalog',
+ pluginId: 'catalog',
// highlight-start
routes: {
index: indexRouteRef,
@@ -204,7 +204,7 @@ const catalogIndexPage = createPageExtension({
});
export default createFrontendPlugin({
- id: 'catalog',
+ pluginId: 'catalog',
routes: {
index: indexRouteRef,
},
@@ -411,7 +411,7 @@ const catalogIndexPage = createPageExtension({
});
export default createFrontendPlugin({
- id: 'catalog',
+ pluginId: 'catalog',
routes: {
index: indexRouteRef,
// highlight-next-line
diff --git a/docs/frontend-system/architecture/50-naming-patterns.md b/docs/frontend-system/architecture/50-naming-patterns.md
index 11e0b8e733..55068d8ea1 100644
--- a/docs/frontend-system/architecture/50-naming-patterns.md
+++ b/docs/frontend-system/architecture/50-naming-patterns.md
@@ -24,7 +24,7 @@ Example:
```ts
// This declaration is only for internal usage in tests. This could also be a direct default export.
export const userSettingsPlugin = createFrontendPlugin({
- id: 'user-settings',
+ pluginId: 'user-settings',
...
})
@@ -68,7 +68,7 @@ const catalogSearchResultListItem = SearchResultListItemBlueprint.make({
// Note that the extensions themselves are not exported, only the plugin instance
export const catalogPlugin = createFrontendPlugin({
- id: 'catalog',
+ pluginId: 'catalog',
extensions: [catalogEntityPage, catalogSearchResultListItem /* ... */],
});
```
diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md
index ecd036af61..861be2e39c 100644
--- a/docs/frontend-system/building-apps/08-migrating.md
+++ b/docs/frontend-system/building-apps/08-migrating.md
@@ -219,7 +219,7 @@ Can be converted to the following plugin configuration:
```tsx
createFrontendPlugin({
- id: 'tech-radar',
+ pluginId: 'tech-radar',
// ...
featureFlags: [{ name: 'tech-radar' }],
// ...
diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md
index 633afd6ce8..33ec65d9d1 100644
--- a/docs/frontend-system/building-plugins/01-index.md
+++ b/docs/frontend-system/building-plugins/01-index.md
@@ -32,7 +32,7 @@ This is how to create a minimal plugin:
import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
export const examplePlugin = createFrontendPlugin({
- id: 'example',
+ pluginId: 'example',
extensions: [],
});
```
@@ -98,7 +98,7 @@ const exampleNavItem = NavItemBlueprint.make({
// The same plugin as above, now with the extensions added
export const examplePlugin = createFrontendPlugin({
- id: 'example',
+ pluginId: 'example',
extensions: [examplePage, exampleNavItem],
// We can also make routes available to other plugins.
// highlight-start
@@ -174,7 +174,7 @@ const exampleApi = ApiBlueprint.make({
/* Omitted definitions for examplePage, exampleNavItem, and rootRouteRef. */
export const examplePlugin = createFrontendPlugin({
- id: 'example',
+ pluginId: 'example',
extensions: [
// highlight-add-next-line
exampleApi,
@@ -210,7 +210,7 @@ const exampleEntityContent = EntityContentBlueprint.make({
});
export const examplePlugin = createFrontendPlugin({
- id: 'example',
+ pluginId: 'example',
extensions: [
// highlight-add-next-line
exampleEntityContent,
diff --git a/docs/frontend-system/building-plugins/05-migrating.md b/docs/frontend-system/building-plugins/05-migrating.md
index 846e2c944b..ba292aa5d5 100644
--- a/docs/frontend-system/building-plugins/05-migrating.md
+++ b/docs/frontend-system/building-plugins/05-migrating.md
@@ -42,7 +42,9 @@ In order to migrate the actual definition of the plugin you need to recreate the
import { convertLegacyRouteRefs } from '@backstage/core-compat-api';
export default createFrontendPlugin({
- id: 'my-plugin',
+ // The plugin ID is now provided as `pluginId` instead of `id`
+ /* highlight-next-line */
+ pluginId: 'my-plugin',
// bind all the extensions to the plugin
/* highlight-next-line */
extensions: [/* APIs will go here, but don't worry about those yet */],
@@ -138,7 +140,7 @@ Then add the `fooPage` extension to the plugin:
import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
export default createFrontendPlugin({
- id: 'my-plugin',
+ pluginId: 'my-plugin',
// bind all the extensions to the plugin
/* highlight-remove-next-line */
extensions: [],
@@ -229,7 +231,7 @@ Finally, let's add the `exampleWorkApi` extension to the plugin:
import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
export default createFrontendPlugin({
- id: 'my-plugin',
+ pluginId: 'my-plugin',
// bind all the extensions to the plugin
/* highlight-remove-next-line */
extensions: [fooPage],
diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md
index a2b3bf06f6..bf53911d24 100644
--- a/docs/frontend-system/utility-apis/02-creating.md
+++ b/docs/frontend-system/utility-apis/02-creating.md
@@ -80,7 +80,7 @@ const workApi = ApiBlueprint.make({
* @public
*/
export default createFrontendPlugin({
- id: 'example',
+ pluginId: 'example',
extensions: [exampleWorkApi],
});
```
diff --git a/docs/getting-started/config/authentication.md b/docs/getting-started/config/authentication.md
index 2442d2b767..cb0a329ae1 100644
--- a/docs/getting-started/config/authentication.md
+++ b/docs/getting-started/config/authentication.md
@@ -56,7 +56,15 @@ Open `packages/app/src/App.tsx` and below the last `import` line, add:
import { githubAuthApiRef } from '@backstage/core-plugin-api';
```
-Search for `const app = createApp({` in this file, and below `apis,` add:
+Search for `const app = createApp({` in this file, and replace:
+
+```tsx title="packages/app/src/App.tsx"
+components: {
+ SignInPage: props => ,
+},
+```
+
+with
```tsx title="packages/app/src/App.tsx"
components: {
diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md
index 068aa70101..3765275e1e 100644
--- a/docs/integrations/azure/org.md
+++ b/docs/integrations/azure/org.md
@@ -125,6 +125,8 @@ microsoftGraphOrg:
In addition to these groups, one additional group will be created for your organization.
All imported groups will be a child of this group.
+By default the provider will get groups using the msgraph `/group` endpoint, but it is possible to use different endpoints by setting the `path` configuration. All the endpoint containing `/microsoft.graph.group` will return the right type of group object. [See usage](#Using-path-parameter) for more details.
+
### Users
There are two modes for importing users - You can import all user objects matching a `filter`.
@@ -148,6 +150,36 @@ microsoftGraphOrg:
search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")'
```
+By default the provider will get user using the msgraph `/user` endpoint, but it is possible to use different endpoints by setting the `path` configuration. All the endpoint containing `/microsoft.graph.user` will return the right type of user object. [See usage](#Using-path-parameter) for more details.
+
+### Using `path` parameter
+
+By default the provider will get groups and users using the msgraph `/group` and `/user` endpoints, but it is possible to use different endpoints by setting the `path` configuration.
+All the endpoint containing `/microsoft.graph.user` will return the right type of user object and all the endpoint containing `/microsoft.graph.group` will return the right type of group object.
+
+#### Example
+
+Given the following org structure it is possible to use the `path` parameter to get all the users and groups that are members of the group `someRootGroup` on all levels.
+
+
+
+
+
+
+
+The configuration would look like this:
+
+```yaml
+microsoftGraphOrg:
+ providerId:
+ group:
+ path: /groups/{someRootGroup id}/transitiveMembers/microsoft.graph.group
+ user:
+ path: /groups/{someRootGroup id}/transitiveMembers/microsoft.graph.user
+```
+
+Using the transitive members endpoint will return all the users and groups that are members of the group `someRootGroup` on all levels.
+
### User photos
By default, the photos of users will be fetched and added to each user entity. For huge organizations this may be unfeasible, as it will take a _very_ long time, and can be disabled by setting `loadPhotos` to `false`:
diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md
index ff0e60f498..9560c5e06b 100644
--- a/docs/integrations/bitbucketCloud/discovery.md
+++ b/docs/integrations/bitbucketCloud/discovery.md
@@ -12,6 +12,23 @@ The provider will search your Bitbucket Cloud account and register catalog files
as Location entity and via following processing steps add all contained catalog entities.
This can be useful as an alternative to static locations or manually adding things to the catalog.
+## Event-based Discovery
+
+Supported events for event-based updates:
+
+- [`repo:push`](https://support.atlassian.com/bitbucket-cloud/docs/event-payloads/#Push)
+ received as `bitbucket.repo:push`
+- [`repo:updated`](https://support.atlassian.com/bitbucket-cloud/docs/event-payloads/#Updated)
+ received as `bitbucket.repo:updated`
+ - Changes are triggered if the repository slug/name changed, causing a new URL for it.
+
+To receive events, you need to add webhook subscriptions at Bitbucket Cloud.
+Enable the triggers (event types) you want to receive.
+For the entity provider, only the event types above are supported ("Repository Push" and/or "Repository Updated")
+and additional event types will be ignored (e.g., for other use cases/integrations you might have).
+
+For more information on how to set up the event-based discovery, see the installation instructions below.
+
## Installation
You will have to add the entity provider in the catalog initialization code of your
diff --git a/docs/releases/v1.39.0-next.0-changelog.md b/docs/releases/v1.39.0-next.0-changelog.md
new file mode 100644
index 0000000000..6374d72c9f
--- /dev/null
+++ b/docs/releases/v1.39.0-next.0-changelog.md
@@ -0,0 +1,2016 @@
+# Release v1.39.0-next.0
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.39.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.39.0-next.0)
+
+## @backstage/backend-dynamic-feature-service@0.7.0-next.0
+
+### Minor Changes
+
+- 10f693c: **BREAKING** Removed support for the legacy backend, please migrate to the new backend system
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/plugin-catalog-backend@1.32.2-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/cli-node@0.2.13
+ - @backstage/config-loader@1.10.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-events-backend@0.5.2-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
+ - @backstage/backend-openapi-utils@0.5.3-next.0
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-app-node@0.1.33-next.0
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-search-common@1.2.17
+
+## @backstage/backend-test-utils@1.5.0-next.0
+
+### Minor Changes
+
+- b3832d1: Add a functional `mockServices.events()`
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/backend-app-api@1.2.3-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/catalog-client@1.10.0-next.0
+
+### Minor Changes
+
+- 1a003ff: Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend@0.25.0-next.0
+
+### Minor Changes
+
+- 57221d9: **BREAKING**: Removed support for the old backend system, and removed all deprecated exports.
+
+ If you were using one of the deprecated imports from this package, you will have to follow the instructions in their respective deprecation notices before upgrading. Most of the general utilities are available from `@backstage/plugin-auth-node`, and the specific auth providers are available from dedicated packages such as for example `@backstage/plugin-auth-backend-module-github-provider`. See [the auth docs](https://backstage.io/docs/auth/) for specific instructions.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-bitbucket-cloud-common@0.3.0-next.0
+
+### Minor Changes
+
+- 5850717: Update Bitbucket Cloud schema and models.
+
+ The latest schema was fetched from Bitbucket Cloud and stored locally.
+ Based on the updated schema, the models got regenerated.
+
+ **BREAKING:**
+
+ Due to the schema changes, the model update includes one breaking change:
+
+ - `Account.username` was removed.
+
+ Additionally, there were a couple of compatible changes including the addition of
+ `BaseCommit.committer` and others.
+
+### Patch Changes
+
+- 57ad208: Add support for `repo:updated` events as `Events.RepoUpdatedEvent`.
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+
+## @backstage/plugin-catalog-backend-module-github@0.9.0-next.0
+
+### Minor Changes
+
+- ff335e5: **BREAKING** The `GithubLocationAnalyzer` now requires the `AuthService` and the `CatalogService` when being constructed and the `TokenManger` has been removed.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/plugin-catalog-backend@1.32.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.0-next.0
+
+### Minor Changes
+
+- 10f693c: **BREAKING** Removed support for the legacy backend, please [migrate to the new backend system](https://github.com/backstage/backstage/tree/v1.38.0/plugins/catalog-backend-module-incremental-ingestion#installation). Also, if you were importing from the `/alpha` export of this package, you should remove the `/alpha` part.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/plugin-catalog-backend@1.32.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-common@0.8.4
+
+## @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.0
+
+### Minor Changes
+
+- f453d5c: **BREAKING** Removed support for the legacy backend and removed references to `@backstage/backend-common`, please [migrate to the new backend system](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating)
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.7
+ - @backstage/plugin-permission-common@0.8.4
+
+## @backstage/plugin-catalog-node@1.17.0-next.0
+
+### Minor Changes
+
+- 1a003ff: Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-common@0.8.4
+
+## @backstage/plugin-catalog-react@1.18.0-next.0
+
+### Minor Changes
+
+- d47aaa3: Added EntityOrderFilter to sort entities by different fields/columns. This new filter allows users to specify the order in which entities are displayed in the catalog.
+
+ Example usage:
+
+ ```ts
+ import {
+ EntityOrderFilter,
+ useEntityList,
+ } from '@backstage/plugin-catalog-react';
+ // ...
+ const { updateFilters } = useEntityList();
+
+ // ...
+ updateFilters({
+ order: new EntityOrderFilter([
+ {
+ field: 'metadata.name',
+ order: 'desc',
+ },
+ ]),
+ });
+ ```
+
+- 1a003ff: Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/frontend-test-utils@0.3.2-next.0
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-permission-react@0.4.33
+
+## @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.0
+
+### Minor Changes
+
+- af853ef: Added a module that is able to transfer messages from Google Pub/Sub subscriptions into the Backstage events system.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-permission-backend@0.7.0-next.0
+
+### Minor Changes
+
+- cf8fd51: **BREAKING** Removed support for the legacy backend system, please [migrate to the new backend system](https://backstage.io/docs/backend-system/building-backends/migrating)
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.8.4
+
+## @backstage/plugin-scaffolder@1.31.0-next.0
+
+### Minor Changes
+
+- 4235e87: add templating extensions page
+
+### Patch Changes
+
+- 92c3658: Full support in EntityPicker (and derivatives) for default EntityPresentationApi
+- d7da01d: Fix EntityPicker field to render description as markdown, matching other form components in the system.
+- 36ae651: Fixing a bug where the name for `templatingExtensions` was incorrectly set to `templateExtensions`
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/plugin-scaffolder-react@1.16.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-react@0.4.33
+ - @backstage/plugin-scaffolder-common@1.5.10
+
+## @backstage/plugin-scaffolder-react@1.16.0-next.0
+
+### Minor Changes
+
+- 4235e87: add templating extensions page
+
+### Patch Changes
+
+- 36ae651: Fixing a bug where the name for `templatingExtensions` was incorrectly set to `templateExtensions`
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/theme@0.6.5
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-permission-react@0.4.33
+ - @backstage/plugin-scaffolder-common@1.5.10
+
+## @backstage/backend-app-api@1.2.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/backend-defaults@0.9.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/backend-app-api@1.2.3-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/cli-node@0.2.13
+ - @backstage/config-loader@1.10.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/backend-dev-utils@0.1.5
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/types@1.2.1
+
+## @backstage/backend-openapi-utils@0.5.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/backend-plugin-api@1.3.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-common@0.8.4
+
+## @backstage/canon@0.3.2-next.0
+
+### Patch Changes
+
+- e996368: Fix Canon missing dependencies
+
+## @backstage/cli@0.32.1-next.0
+
+### Patch Changes
+
+- 5cd3c54: Updated dependency `react-refresh` to `^0.17.0`.
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/cli-node@0.2.13
+ - @backstage/config-loader@1.10.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.1.10
+ - @backstage/release-manifests@0.0.12
+ - @backstage/types@1.2.1
+
+## @backstage/core-compat-api@0.4.2-next.0
+
+### Patch Changes
+
+- 6eedf46: Updated dependency `@backstage-community/plugin-puppetdb` to `^0.6.0`.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/version-bridge@1.0.11
+
+## @backstage/create-app@0.6.2-next.0
+
+### Patch Changes
+
+- Bumped create-app version.
+- Updated dependencies
+ - @backstage/cli-common@0.1.15
+
+## @backstage/dev-utils@1.1.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/app-defaults@1.6.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/theme@0.6.5
+
+## @backstage/frontend-app-api@0.11.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-defaults@0.2.2-next.0
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
+## @backstage/frontend-defaults@0.2.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-app-api@0.11.2-next.0
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/plugin-app@0.1.9-next.0
+
+## @backstage/frontend-test-utils@0.3.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/frontend-app-api@0.11.2-next.0
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/test-utils@1.7.7
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-app@0.1.9-next.0
+
+## @backstage/integration@1.16.4-next.0
+
+### Patch Changes
+
+- f3381d3: Added missing `organizations` property to `azure` section in `config.d.ts` file
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/integration-react@1.2.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.6
+
+## @backstage/repo-tools@0.13.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/cli-node@0.2.13
+ - @backstage/config-loader@1.10.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/errors@1.2.7
+
+## @techdocs/cli@1.9.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/plugin-techdocs-node@1.13.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+
+## @backstage/plugin-api-docs@0.12.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/plugin-catalog@1.29.1-next.0
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-react@0.4.33
+
+## @backstage/plugin-app@0.1.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/theme@0.6.5
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-react@0.4.33
+
+## @backstage/plugin-app-backend@0.5.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config-loader@1.10.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-app-node@0.1.33-next.0
+
+## @backstage/plugin-app-node@0.1.33-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config-loader@1.10.0
+
+## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
+## @backstage/plugin-auth-backend-module-auth0-provider@0.2.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
+## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
+## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
+## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.0
+
+### Patch Changes
+
+- 5cdfe05: Added missing types package
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
+## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
+## @backstage/plugin-auth-backend-module-google-provider@0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
+## @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
+## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
+## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-oidc-provider@0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-auth-backend-module-okta-provider@0.2.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
+## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
+## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/catalog-model@1.7.3
+
+## @backstage/plugin-auth-node@0.6.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-catalog@1.29.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-react@0.4.33
+ - @backstage/plugin-scaffolder-common@1.5.10
+ - @backstage/plugin-search-common@1.2.17
+ - @backstage/plugin-search-react@1.8.8
+
+## @backstage/plugin-catalog-backend@1.32.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.3.4-next.0
+ - @backstage/backend-openapi-utils@0.5.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-search-common@1.2.17
+
+## @backstage/plugin-catalog-backend-module-aws@0.4.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-kubernetes-common@0.9.4
+
+## @backstage/plugin-catalog-backend-module-azure@0.3.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+
+## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/backend-openapi-utils@0.5.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.4.8-next.0
+
+### Patch Changes
+
+- 3bce578: Support Bitbucket Cloud's `repo:updated` events at `BitbucketCloudEntityProvider`.
+
+ To make use of the new event type, you have to configure your webhook or add a new ones
+ that delivers this event type to Backstage similar to `repo:push` before.
+
+ Only `repo:updated` events that modify a repository's URL (e.g., due to a name change)
+ will cause changes (removing the "old", adding the "new" repository).
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+
+## @backstage/plugin-catalog-backend-module-bitbucket-server@0.4.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.3
+
+## @backstage/plugin-catalog-backend-module-gcp@0.3.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-kubernetes-common@0.9.4
+
+## @backstage/plugin-catalog-backend-module-gerrit@0.3.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.3
+
+## @backstage/plugin-catalog-backend-module-github-org@0.3.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend-module-github@0.9.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+
+## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
+## @backstage/plugin-catalog-backend-module-ldap@0.11.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+
+## @backstage/plugin-catalog-backend-module-logs@0.1.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.32.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
+## @backstage/plugin-catalog-backend-module-msgraph@0.6.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+
+## @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+
+## @backstage/plugin-catalog-backend-module-puppetdb@0.2.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-scaffolder-common@1.5.10
+
+## @backstage/plugin-catalog-graph@0.4.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-catalog-import@0.12.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/plugin-catalog-common@1.1.3
+
+## @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+
+## @backstage/plugin-devtools@0.1.27-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/plugin-devtools-common@0.1.15
+ - @backstage/plugin-permission-react@0.4.33
+
+## @backstage/plugin-devtools-backend@0.5.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config-loader@1.10.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-devtools-common@0.1.15
+ - @backstage/plugin-permission-common@0.8.4
+
+## @backstage/plugin-events-backend@0.5.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/backend-openapi-utils@0.5.3-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-events-backend-module-aws-sqs@0.4.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-events-backend-module-azure@0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
+## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
+## @backstage/plugin-events-backend-module-bitbucket-server@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
+## @backstage/plugin-events-backend-module-gerrit@0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
+## @backstage/plugin-events-backend-module-github@0.3.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+
+## @backstage/plugin-events-backend-module-gitlab@0.3.1-next.0
+
+### Patch Changes
+
+- a820df1: Adds support for `object_kind` field with priority over `event_name` on Gitlab webhook event types
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+
+## @backstage/plugin-events-backend-test-utils@0.1.44-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.11-next.0
+
+## @backstage/plugin-events-node@0.4.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-gateway-backend@1.0.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+
+## @backstage/plugin-home@0.8.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/theme@0.6.5
+ - @backstage/plugin-home-react@0.1.25
+
+## @backstage/plugin-kubernetes@0.12.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/plugin-kubernetes-common@0.9.4
+ - @backstage/plugin-kubernetes-react@0.5.6
+ - @backstage/plugin-permission-react@0.4.33
+
+## @backstage/plugin-kubernetes-backend@0.19.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-kubernetes-node@0.2.6-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/types@1.2.1
+ - @backstage/plugin-kubernetes-common@0.9.4
+ - @backstage/plugin-permission-common@0.8.4
+
+## @backstage/plugin-kubernetes-cluster@0.0.25-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-kubernetes-common@0.9.4
+ - @backstage/plugin-kubernetes-react@0.5.6
+ - @backstage/plugin-permission-react@0.4.33
+
+## @backstage/plugin-kubernetes-node@0.2.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/types@1.2.1
+ - @backstage/plugin-kubernetes-common@0.9.4
+
+## @backstage/plugin-notifications@0.5.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/theme@0.6.5
+ - @backstage/types@1.2.1
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-signals-react@0.0.12
+
+## @backstage/plugin-notifications-backend@0.5.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/plugin-notifications-node@0.2.15-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-signals-node@0.1.20-next.0
+
+## @backstage/plugin-notifications-backend-module-email@0.3.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-notifications-node@0.2.15-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/types@1.2.1
+ - @backstage/plugin-notifications-common@0.0.8
+
+## @backstage/plugin-notifications-backend-module-slack@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-notifications-node@0.2.15-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-notifications-common@0.0.8
+
+## @backstage/plugin-notifications-node@0.2.15-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-signals-node@0.1.20-next.0
+
+## @backstage/plugin-org@0.6.39-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/plugin-catalog-common@1.1.3
+
+## @backstage/plugin-org-react@0.1.38-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+
+## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/plugin-permission-common@0.8.4
+
+## @backstage/plugin-permission-node@0.9.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.8.4
+
+## @backstage/plugin-proxy-backend@0.6.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-proxy-node@0.1.4-next.0
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-proxy-node@0.1.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+
+## @backstage/plugin-scaffolder-backend@1.32.2-next.0
+
+### Patch Changes
+
+- 36ae651: Fixing a bug where the name for `templatingExtensions` was incorrectly set to `templateExtensions`
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.0
+ - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.0
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.0
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-scaffolder-common@1.5.10
+
+## @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.0
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-scaffolder-backend-module-gcp@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.0
+
+### Patch Changes
+
+- 6579c2c: Use action context logger in Octokit client
+- b2b654c: Added optional assignees parameter to `publish:github:pull-request` action
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.0
+
+### Patch Changes
+
+- d15355c: If the commit action is not `create` log a more appropriate error message to the end user advising that the files they're trying to modify might not exist
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-notifications-node@0.2.15-next.0
+ - @backstage/plugin-notifications-common@0.0.8
+
+## @backstage/plugin-scaffolder-backend-module-rails@0.5.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-scaffolder-backend-module-sentry@0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.0
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-scaffolder-node@0.8.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-scaffolder-common@1.5.10
+
+## @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-test-utils@1.5.0-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-search@1.4.26-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-search-common@1.2.17
+ - @backstage/plugin-search-react@1.8.8
+
+## @backstage/plugin-search-backend@2.0.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
+ - @backstage/backend-openapi-utils@0.5.3-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-search-common@1.2.17
+
+## @backstage/plugin-search-backend-module-catalog@0.3.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-search-common@1.2.17
+
+## @backstage/plugin-search-backend-module-elasticsearch@1.7.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/plugin-search-common@1.2.17
+
+## @backstage/plugin-search-backend-module-explore@0.3.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/plugin-search-common@1.2.17
+
+## @backstage/plugin-search-backend-module-pg@0.5.44-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/plugin-search-common@1.2.17
+
+## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/plugin-search-common@1.2.17
+
+## @backstage/plugin-search-backend-module-techdocs@0.4.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/plugin-techdocs-node@1.13.3-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-search-common@1.2.17
+
+## @backstage/plugin-search-backend-node@1.3.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-search-common@1.2.17
+
+## @backstage/plugin-signals-backend@0.3.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+ - @backstage/plugin-signals-node@0.1.20-next.0
+
+## @backstage/plugin-signals-node@0.1.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-techdocs@1.12.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/theme@0.6.5
+ - @backstage/plugin-auth-react@0.1.14
+ - @backstage/plugin-search-common@1.2.17
+ - @backstage/plugin-search-react@1.8.8
+ - @backstage/plugin-techdocs-common@0.1.0
+ - @backstage/plugin-techdocs-react@1.2.16
+
+## @backstage/plugin-techdocs-addons-test-utils@1.0.48-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/plugin-techdocs@1.12.6-next.0
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/test-utils@1.7.7
+ - @backstage/plugin-catalog@1.29.1-next.0
+ - @backstage/plugin-search-react@1.8.8
+ - @backstage/plugin-techdocs-react@1.2.16
+
+## @backstage/plugin-techdocs-backend@2.0.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/plugin-techdocs-node@1.13.3-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-techdocs-common@0.1.0
+
+## @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/plugin-techdocs-react@1.2.16
+
+## @backstage/plugin-techdocs-node@1.13.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/plugin-search-common@1.2.17
+ - @backstage/plugin-techdocs-common@0.1.0
+
+## @backstage/plugin-user-settings@0.8.22-next.0
+
+### Patch Changes
+
+- a7bfdb6: plugin-user-settingsgs support i18n
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/theme@0.6.5
+ - @backstage/types@1.2.1
+ - @backstage/plugin-signals-react@0.0.12
+ - @backstage/plugin-user-settings-common@0.0.1
+
+## @backstage/plugin-user-settings-backend@0.3.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-signals-node@0.1.20-next.0
+ - @backstage/plugin-user-settings-common@0.0.1
+
+## example-app@0.2.109-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/canon@0.3.2-next.0
+ - @backstage/plugin-user-settings@0.8.22-next.0
+ - @backstage/plugin-scaffolder@1.31.0-next.0
+ - @backstage/cli@0.32.1-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/plugin-scaffolder-react@1.16.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/plugin-catalog-import@0.12.14-next.0
+ - @backstage/plugin-techdocs@1.12.6-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.0
+ - @backstage/app-defaults@1.6.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-app-api@0.11.2-next.0
+ - @backstage/theme@0.6.5
+ - @backstage/plugin-api-docs@0.12.7-next.0
+ - @backstage/plugin-auth-react@0.1.14
+ - @backstage/plugin-catalog@1.29.1-next.0
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-graph@0.4.19-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.0
+ - @backstage/plugin-devtools@0.1.27-next.0
+ - @backstage/plugin-home@0.8.8-next.0
+ - @backstage/plugin-kubernetes@0.12.7-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.25-next.0
+ - @backstage/plugin-notifications@0.5.5-next.0
+ - @backstage/plugin-org@0.6.39-next.0
+ - @backstage/plugin-permission-react@0.4.33
+ - @backstage/plugin-search@1.4.26-next.0
+ - @backstage/plugin-search-common@1.2.17
+ - @backstage/plugin-search-react@1.8.8
+ - @backstage/plugin-signals@0.0.18
+ - @backstage/plugin-techdocs-react@1.2.16
+
+## example-app-next@0.0.23-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/canon@0.3.2-next.0
+ - @backstage/plugin-user-settings@0.8.22-next.0
+ - @backstage/plugin-scaffolder@1.31.0-next.0
+ - @backstage/cli@0.32.1-next.0
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/plugin-scaffolder-react@1.16.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/plugin-catalog-import@0.12.14-next.0
+ - @backstage/plugin-techdocs@1.12.6-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.0
+ - @backstage/app-defaults@1.6.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-app-api@0.11.2-next.0
+ - @backstage/frontend-defaults@0.2.2-next.0
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/theme@0.6.5
+ - @backstage/plugin-api-docs@0.12.7-next.0
+ - @backstage/plugin-app@0.1.9-next.0
+ - @backstage/plugin-app-visualizer@0.1.18
+ - @backstage/plugin-auth-react@0.1.14
+ - @backstage/plugin-catalog@1.29.1-next.0
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-graph@0.4.19-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.0
+ - @backstage/plugin-home@0.8.8-next.0
+ - @backstage/plugin-kubernetes@0.12.7-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.25-next.0
+ - @backstage/plugin-notifications@0.5.5-next.0
+ - @backstage/plugin-org@0.6.39-next.0
+ - @backstage/plugin-permission-react@0.4.33
+ - @backstage/plugin-search@1.4.26-next.0
+ - @backstage/plugin-search-common@1.2.17
+ - @backstage/plugin-search-react@1.8.8
+ - @backstage/plugin-signals@0.0.18
+ - @backstage/plugin-techdocs-react@1.2.16
+
+## example-backend@0.0.38-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.0
+ - @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.0
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.0
+ - @backstage/plugin-auth-backend@0.25.0-next.0
+ - @backstage/plugin-scaffolder-backend@1.32.2-next.0
+ - @backstage/plugin-permission-backend@0.7.0-next.0
+ - @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.0
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/plugin-catalog-backend@1.32.2-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.0
+ - @backstage/plugin-techdocs-backend@2.0.2-next.0
+ - @backstage/plugin-notifications-backend@0.5.6-next.0
+ - @backstage/plugin-signals-backend@0.3.4-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-app-backend@0.5.2-next.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.0
+ - @backstage/plugin-devtools-backend@0.5.5-next.0
+ - @backstage/plugin-events-backend@0.5.2-next.0
+ - @backstage/plugin-kubernetes-backend@0.19.6-next.0
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/plugin-proxy-backend@0.6.2-next.0
+ - @backstage/plugin-search-backend@2.0.2-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.3.4-next.0
+ - @backstage/plugin-search-backend-module-explore@0.3.2-next.0
+ - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.0
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.0
+
+## e2e-test@0.2.28-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.6.2-next.0
+ - @backstage/cli-common@0.1.15
+ - @backstage/errors@1.2.7
+
+## @internal/scaffolder@0.0.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-react@1.16.0-next.0
+ - @backstage/frontend-plugin-api@0.10.1
+
+## techdocs-cli-embedded-app@0.2.108-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.32.1-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/plugin-techdocs@1.12.6-next.0
+ - @backstage/app-defaults@1.6.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/test-utils@1.7.7
+ - @backstage/theme@0.6.5
+ - @backstage/plugin-catalog@1.29.1-next.0
+ - @backstage/plugin-techdocs-react@1.2.16
+
+## yarn-plugin-backstage@0.0.5-next.0
+
+### Patch Changes
+
+- fd70d86: Add both `npm:` and `backstage:` ranges to the lockfile to ensure compatibility with tools that parse the lockfile and ensure dependencies stay locked when building dist workspaces.
+- Updated dependencies
+ - @backstage/cli-common@0.1.15
+ - @backstage/release-manifests@0.0.12
+
+## @internal/plugin-todo-list-backend@1.0.39-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/errors@1.2.7
diff --git a/docs/releases/v1.39.0-next.1-changelog.md b/docs/releases/v1.39.0-next.1-changelog.md
new file mode 100644
index 0000000000..f2b31bb002
--- /dev/null
+++ b/docs/releases/v1.39.0-next.1-changelog.md
@@ -0,0 +1,2428 @@
+# Release v1.39.0-next.1
+
+Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.39.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.39.0-next.1)
+
+## @backstage/plugin-catalog-backend@2.0.0-next.1
+
+### Major Changes
+
+- 90ab044: **BREAKING**: Removed all deprecated exports, and removed support for the old backend system.
+
+ It also removes the `CodeOwnersProcessor` from the default set of processors, because it is expensive to run and has vague semantics. You need to update your backend to add it to the `catalogProcessingExtensionPoint` if you wish to continue using it.
+
+ The following removed exports are available from `@backstage/plugin-catalog-node`:
+
+ - `locationSpecToMetadataName`
+ - `locationSpecToLocationEntity`
+ - `processingResult`
+ - `EntitiesSearchFilter`
+ - `EntityFilter`
+ - `DeferredEntity`
+ - `EntityRelationSpec`
+ - `CatalogProcessor`
+ - `CatalogProcessorParser`
+ - `CatalogProcessorCache`
+ - `CatalogProcessorEmit`
+ - `CatalogProcessorLocationResult`
+ - `CatalogProcessorEntityResult`
+ - `CatalogProcessorRelationResult`
+ - `CatalogProcessorErrorResult`
+ - `CatalogProcessorRefreshKeysResult`
+ - `CatalogProcessorResult`
+ - `EntityProvider`
+ - `EntityProviderConnection`
+ - `EntityProviderMutation`
+ - `AnalyzeOptions`
+ - `LocationAnalyzer`
+ - `ScmLocationAnalyzer`
+ - `PlaceholderResolver`
+ - `PlaceholderResolverParams`
+ - `PlaceholderResolverRead`
+ - `PlaceholderResolverResolveUrl`
+ - `parseEntityYaml`
+
+ The following removed exports are available from `@backstage/plugin-catalog-common`:
+
+ - `LocationSpec`
+ - `AnalyzeLocationRequest`
+ - `AnalyzeLocationResponse`
+ - `AnalyzeLocationExistingEntity`
+ - `AnalyzeLocationGenerateEntity`
+ - `AnalyzeLocationEntityField`
+
+ The following removed exports are instead implemented in the new backend system by `@backstage/plugin-search-backend-module-catalog`:
+
+ - `defaultCatalogCollatorEntityTransformer`
+ - `CatalogCollatorEntityTransformer`
+ - `DefaultCatalogCollator`
+
+ The following exports are removed without a direct replacement:
+
+ - `DefaultCatalogCollatorFactory`
+ - `DefaultCatalogCollatorFactoryOptions`
+ - `LocationEntityProcessor`
+ - `LocationEntityProcessorOptions`
+ - `CatalogBuilder`
+ - `CatalogEnvironment`
+ - `CatalogPermissionRuleInput`
+ - `CatalogProcessingEngine`
+ - `createRandomProcessingInterval`
+ - `ProcessingIntervalFunction`
+
+### Minor Changes
+
+- 6c9b88e: **BREAKING ALPHA**: You can no longer import the catalog plugin from the `/alpha` export; please use the regular root default export instead.
+- d88b922: Adds the ability to disable the default entity processors using a new boolean app config item `catalog.disableDefaultProcessors`.
+
+### Patch Changes
+
+- 0e710fc: This patch addresses an issue identified in Backstage when configured with a MySQL database. If an entity of type location
+ (e..all.yaml) has more than 70 referenced entities, clicking "Refresh" does not update the referenced entities as expected. This occurs because the TEXT type in MySQL has a limit of 65,535 bytes, which is insufficient to store all the referenced entities, causing the refresh operation to fail.
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/backend-openapi-utils@0.5.3-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/backend-defaults@0.10.0-next.1
+
+### Minor Changes
+
+- d385854: **BREAKING**: The `DefaultSchedulerService` constructor options now requires `RootLifecycleService`, `HttpRouterService`, and `PluginMetadataService` fields.
+
+ The scheduler will register a REST API for listing and triggering tasks. Please see [the scheduler documentation](https://backstage.io/docs/backend-system/core-services/scheduler) for more details about this API.
+
+### Patch Changes
+
+- acea1d4: update documentation
+- 72d019d: Removed various typos
+- c6bc67d: Added Valkey support alongside Redis in backend-defaults cache clients, using the new Keyv Valkey package. Also extended backend-test-utils to support Valkey in tests.
+- 36f77e9: Bug fix: Pass user provided token through to gitlab url resolvers
+- Updated dependencies
+ - @backstage/backend-app-api@1.2.3-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/backend-dev-utils@0.1.5
+ - @backstage/cli-node@0.2.13
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/backend-test-utils@1.5.0-next.1
+
+### Minor Changes
+
+- c6bc67d: Added Valkey support alongside Redis in backend-defaults cache clients, using the new Keyv Valkey package. Also extended backend-test-utils to support Valkey in tests.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/backend-app-api@1.2.3-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/canon@0.4.0-next.1
+
+### Minor Changes
+
+- ea36f74: **Breaking Change** Icons on Button and IconButton now need to be imported and placed like this: <Button iconStart={ } />
+
+### Patch Changes
+
+- 720033c: For improved a11y, clicking a Select component label now focuses the Select trigger element, and the TextField component's label is now styled to indicate it's interactive.
+- 206ffbe: Fixed an issue with Canon's DataTable.Pagination component showing the wrong number for the "to" count.
+- 72d019d: Removed various typos
+
+## @backstage/plugin-catalog-backend-module-msgraph@0.7.0-next.1
+
+### Minor Changes
+
+- 20c1ea7: Add new `userGroupMember.path`, `user.path` and, `group.path` option to each query type to allow more complex msgraph queries
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## @backstage/plugin-catalog-node@1.17.0-next.1
+
+### Minor Changes
+
+- a459f17: Added `parseEntityYaml` from `@backstage/plugin-catalog-backend`, to make it more easily usable by custom plugins and modules
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## @backstage/plugin-events-backend-module-github@0.4.0-next.1
+
+### Minor Changes
+
+- ae249fc: **BREAKING**: Removed the `createGithubSignatureValidator` export.
+
+ Added support webhook validation based on `integrations.github.[].apps.[].webhookSecret`.
+
+### Patch Changes
+
+- c7ef81c: Correct README installation instructions.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-kubernetes-node@0.3.0-next.1
+
+### Minor Changes
+
+- 9cdf001: **BREAKING**: The `PinnipedHelper` class now expects a regular `LoggerService` instance from the new backend system, instead of a Winston logger.
+
+### Patch Changes
+
+- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-kubernetes-common@0.9.5-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-permission-backend@0.7.0-next.1
+
+### Minor Changes
+
+- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-permission-common@0.9.0-next.0
+
+### Minor Changes
+
+- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests.
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-permission-node@0.10.0-next.1
+
+### Minor Changes
+
+- 4da2965: Fixed an issue causing the `PermissionClient` to exhaust the request body size limit too quickly when making many requests.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend@1.33.0-next.1
+
+### Minor Changes
+
+- 587cb05: Added `workspace:template` and `workspace:template:file` actions to complement respective `fetch:*` actions
+
+### Patch Changes
+
+- eb39388: Fixed bug in fs:delete that prevented wildcard patterns from matching paths starting with "."
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.1
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.1
+ - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.1
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+ - @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.1
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.1
+ - @backstage/plugin-scaffolder-common@1.5.11-next.0
+
+## @backstage/plugin-search-react@1.9.0-next.0
+
+### Minor Changes
+
+- 611c941: Allow search filters to provide labels and values separately, and not only values
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/app-defaults@1.6.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## @backstage/backend-app-api@1.2.3-next.1
+
+### Patch Changes
+
+- 729a7d6: Added a configuration to permit backend plugin module failures on startup:
+
+ ```yaml
+ backend:
+ ...
+ startup:
+ plugins:
+ plugin-x:
+ modules:
+ module-y:
+ onPluginModuleBootFailure: continue
+ ```
+
+ This configuration permits `plugin-x` with `module-y` to fail on startup. Omitting the
+ `onPluginModuleBootFailure` configuration matches the previous behavior, wherein any
+ individual plugin module failure is forwarded to the plugin and aborts backend startup.
+
+ The default can also be changed, so that continuing on failure is the default
+ unless otherwise specified:
+
+ ```yaml
+ backend:
+ startup:
+ default:
+ onPluginModuleBootFailure: continue
+ plugins:
+ catalog:
+ modules:
+ github:
+ onPluginModuleBootFailure: abort
+ ```
+
+- 72d019d: Removed various typos
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/backend-dynamic-feature-service@0.7.0-next.1
+
+### Patch Changes
+
+- 72d019d: Fixed various typos.
+ `FrontendRemoteResolver`'s misspelled `getAdditionaRemoteInfo` has been deprecated. Use the correct spelling `getAdditionalRemoteInfo` instead.
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@2.0.0-next.1
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/plugin-events-backend@0.5.2-next.1
+ - @backstage/plugin-search-backend-node@1.3.11-next.1
+ - @backstage/backend-openapi-utils@0.5.3-next.1
+ - @backstage/cli-common@0.1.15
+ - @backstage/cli-node@0.2.13
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-app-node@0.1.33-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/backend-openapi-utils@0.5.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/backend-plugin-api@1.3.1-next.1
+
+### Patch Changes
+
+- acea1d4: update documentation
+- 72d019d: Removed various typos
+- d385854: Minor doc comment update
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/cli@0.32.1-next.1
+
+### Patch Changes
+
+- d649df0: Internal code cleanup
+- 9aaec54: Internal refactor of opaque type handling.
+- 9285385: Added `info` object to the context of the alpha CLI.
+- 6cc9507: Updated dependency `@octokit/request` to `^8.0.0`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/cli-node@0.2.13
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.1.10
+ - @backstage/release-manifests@0.0.12
+ - @backstage/types@1.2.1
+
+## @backstage/config-loader@1.10.1-next.0
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/core-compat-api@0.4.2-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/version-bridge@1.0.11
+
+## @backstage/core-components@0.17.2-next.0
+
+### Patch Changes
+
+- e0d1025: `LogViewer` now supports a `textWrap` prop that wraps log lines to the next line for overflowing content instead of using horizontal scroll
+- bb84534: Fix the hidden sidebar's sub-menu when the sidebar is scrollable
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/version-bridge@1.0.11
+
+## @backstage/create-app@0.6.2-next.1
+
+### Patch Changes
+
+- Bumped create-app version.
+- Updated dependencies
+ - @backstage/cli-common@0.1.15
+
+## @backstage/dev-utils@1.1.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/app-defaults@1.6.2-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+
+## @backstage/frontend-app-api@0.11.2-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/frontend-defaults@0.2.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
+## @backstage/frontend-defaults@0.2.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/plugin-app@0.1.9-next.1
+ - @backstage/frontend-app-api@0.11.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/frontend-dynamic-feature-loader@0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/config@1.3.2
+
+## @backstage/frontend-plugin-api@0.10.2-next.0
+
+### Patch Changes
+
+- fb58f20: The `id` option of `createFrontendPlugin` has been renamed to `pluginId` in order to better align with similar APIs in the frontend and backend systems.
+
+ The old `id` option is deprecated and will be removed in a future release.
+
+- 72d019d: Removed various typos
+
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
+## @backstage/frontend-test-utils@0.3.2-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/plugin-app@0.1.9-next.1
+ - @backstage/frontend-app-api@0.11.2-next.1
+ - @backstage/test-utils@1.7.8-next.0
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
+## @backstage/integration@1.16.4-next.1
+
+### Patch Changes
+
+- acea1d4: update documentation
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/integration-react@1.2.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.6
+
+## @backstage/repo-tools@0.13.3-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/cli-node@0.2.13
+ - @backstage/errors@1.2.7
+
+## @techdocs/cli@1.9.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-techdocs-node@1.13.3-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+
+## @backstage/test-utils@1.7.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## @backstage/theme@0.6.6-next.0
+
+### Patch Changes
+
+- 1b14572: Show arrow when MuiTableSortLabel receives focus
+
+## @backstage/plugin-api-docs@0.12.7-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-catalog@1.29.1-next.1
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## @backstage/plugin-app@0.1.9-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## @backstage/plugin-app-backend@0.5.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-app-node@0.1.33-next.1
+
+## @backstage/plugin-app-node@0.1.33-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config-loader@1.10.1-next.0
+
+## @backstage/plugin-app-visualizer@0.1.19-next.0
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+
+## @backstage/plugin-auth-backend@0.25.0-next.1
+
+### Patch Changes
+
+- 0d606ac: Added the configuration flag `auth.omitIdentityTokenOwnershipClaim` that causes issued user tokens to no longer contain the `ent` claim that represents the ownership references of the user.
+
+ The benefit of this new flag is that issued user tokens will be much smaller in
+ size, but they will no longer be self-contained. This means that any consumers
+ of the token that require access to the ownership claims now need to call the
+ `/api/auth/v1/userinfo` endpoint instead. Within the Backstage ecosystem this is
+ done automatically, as clients will still receive the full set of claims during
+ authentication, while plugin backends will need to use the `UserInfoService`
+ which already calls the user info endpoint if necessary.
+
+ When enabling this flag, it is important that any custom sign-in resolvers directly return the result of the sign-in method. For example, the following would not work:
+
+ ```ts
+ const { token } = await ctx.issueToken({
+ claims: { sub: entityRef, ent: [entityRef] },
+ });
+ return { token }; // WARNING: This will not work with the flag enabled
+ ```
+
+ Instead, the sign-in resolver should directly return the result:
+
+ ```ts
+ return ctx.issueToken({
+ claims: { sub: entityRef, ent: [entityRef] },
+ });
+ ```
+
+- 72d019d: Removed various typos
+
+- b128ed9: The `static` key store now issues tokens with the same structure as other key stores. Tokens now include the `typ` field in the header and the `uip` (user identity proof) in the payload.
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-auth-backend-module-atlassian-provider@0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-auth-backend-module-auth0-provider@0.2.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-auth-backend-module-gitlab-provider@0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-auth-backend-module-google-provider@0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-microsoft-provider@0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-auth-backend-module-oauth2-provider@0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-auth-backend-module-oidc-provider@0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-auth-backend-module-okta-provider@0.2.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-auth-backend-module-onelogin-provider@0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-auth-backend-module-pinniped-provider@0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.5.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+
+## @backstage/plugin-auth-node@0.6.3-next.1
+
+### Patch Changes
+
+- 332e934: Added the `identity` property to `BackstageSignInResult`.
+
+ The `prepareBackstageIdentityResponse` function will now also forward the `identity` to the response if present in the provided sign-in result.
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-auth-react@0.1.15-next.0
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-bitbucket-cloud-common@0.3.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.1
+
+## @backstage/plugin-catalog@1.29.1-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-search-react@1.9.0-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+ - @backstage/plugin-scaffolder-common@1.5.11-next.0
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/plugin-catalog-backend-module-aws@0.4.11-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-kubernetes-common@0.9.5-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## @backstage/plugin-catalog-backend-module-azure@0.3.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.1
+
+### Patch Changes
+
+- d385854: Do not swallow errors; instead allow them to bubble up to the task scheduler for better tracking and logging.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/backend-openapi-utils@0.5.3-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.4.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.1
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-catalog-backend-module-bitbucket-server@0.4.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-catalog-backend-module-gcp@0.3.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-kubernetes-common@0.9.5-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+
+## @backstage/plugin-catalog-backend-module-gerrit@0.3.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## @backstage/plugin-catalog-backend-module-github@0.9.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@2.0.0-next.1
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-catalog-backend-module-github-org@0.3.10-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-catalog-backend-module-github@0.9.0-next.1
+ - @backstage/config@1.3.2
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@2.0.0-next.1
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-catalog-backend-module-ldap@0.11.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## @backstage/plugin-catalog-backend-module-logs@0.1.10-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@2.0.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## @backstage/plugin-catalog-backend-module-puppetdb@0.2.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-scaffolder-common@1.5.11-next.0
+
+## @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.8-next.0
+
+## @backstage/plugin-catalog-common@1.1.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/plugin-catalog-graph@0.4.19-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-catalog-import@0.12.14-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## @backstage/plugin-catalog-react@1.18.0-next.1
+
+### Patch Changes
+
+- 6d7f0d5: Fixed an issue causing entities of kind user and group to be empty when an owner was selected
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/frontend-test-utils@0.3.2-next.1
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-catalog-unprocessed-entities-common@0.0.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.0-next.0
+
+## @backstage/plugin-config-schema@0.1.68-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-devtools@0.1.27-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-devtools-common@0.1.16-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## @backstage/plugin-devtools-backend@0.5.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-devtools-common@0.1.16-next.0
+
+## @backstage/plugin-devtools-common@0.1.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-events-backend@0.5.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/backend-openapi-utils@0.5.3-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-events-backend-module-aws-sqs@0.4.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-events-backend-module-azure@0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-events-backend-module-bitbucket-server@0.1.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-events-backend-module-gerrit@0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-events-backend-module-gitlab@0.3.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-events-backend-test-utils@0.1.44-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-events-node@0.4.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-gateway-backend@1.0.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-home@0.8.8-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/plugin-home-react@0.1.26-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+
+## @backstage/plugin-home-react@0.1.26-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+
+## @backstage/plugin-kubernetes@0.12.7-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-kubernetes-react@0.5.7-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/plugin-kubernetes-common@0.9.5-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## @backstage/plugin-kubernetes-backend@0.19.6-next.1
+
+### Patch Changes
+
+- f6f692c: Changed logging of cluster details to debug to minimise log clutter.
+- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-kubernetes-node@0.3.0-next.1
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/plugin-kubernetes-common@0.9.5-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-kubernetes-cluster@0.0.25-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-kubernetes-react@0.5.7-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/plugin-kubernetes-common@0.9.5-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## @backstage/plugin-kubernetes-common@0.9.5-next.0
+
+### Patch Changes
+
+- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-kubernetes-react@0.5.7-next.0
+
+### Patch Changes
+
+- 599c89e: Add headlamp formatter
+- 216c6b2: Updated dependency `@kubernetes/client-node` to `1.1.2`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/plugin-kubernetes-common@0.9.5-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-notifications@0.5.5-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-signals-react@0.0.12
+
+## @backstage/plugin-notifications-backend@0.5.6-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-signals-node@0.1.20-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-notifications-node@0.2.15-next.1
+
+## @backstage/plugin-notifications-backend-module-email@0.3.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/types@1.2.1
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-notifications-node@0.2.15-next.1
+
+## @backstage/plugin-notifications-backend-module-slack@0.1.1-next.1
+
+### Patch Changes
+
+- a1c5bbb: Added email-based Slack User ID lookup if `metadata.annotations.slack.com/bot-notify` is missing from user entity
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-notifications-node@0.2.15-next.1
+
+## @backstage/plugin-notifications-node@0.2.15-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-signals-node@0.1.20-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-notifications-common@0.0.8
+
+## @backstage/plugin-org@0.6.39-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 08ba448: display entity-ref in GroupProfileCard so groups can easily determine their Group ID
+- 18e84c9: Fixed missing spec.profile field on MyGroupsSidebarItem.tsx so the group spec.profile.displayName is shown on the sidebar"
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## @backstage/plugin-org-react@0.1.38-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+
+## @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+
+## @backstage/plugin-permission-react@0.4.34-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.6
+
+## @backstage/plugin-proxy-backend@0.6.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/types@1.2.1
+ - @backstage/plugin-proxy-node@0.1.4-next.1
+
+## @backstage/plugin-proxy-node@0.1.4-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## @backstage/plugin-scaffolder@1.31.0-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/plugin-scaffolder-react@1.16.0-next.1
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+ - @backstage/plugin-scaffolder-common@1.5.11-next.0
+
+## @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.1
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.1
+
+## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-scaffolder-backend-module-gcp@0.2.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-notifications-node@0.2.15-next.1
+
+## @backstage/plugin-scaffolder-backend-module-rails@0.5.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-scaffolder-backend-module-sentry@0.2.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-scaffolder-common@1.5.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-scaffolder-node@0.8.2-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-scaffolder-common@1.5.11-next.0
+
+## @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.1
+
+### Patch Changes
+
+- b27c48d: Include optional `user` in `createMockActionContext`
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/backend-test-utils@1.5.0-next.1
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-scaffolder-react@1.16.0-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-permission-react@0.4.34-next.0
+ - @backstage/plugin-scaffolder-common@1.5.11-next.0
+
+## @backstage/plugin-search@1.4.26-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-search-react@1.9.0-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/plugin-search-backend@2.0.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/plugin-search-backend-node@1.3.11-next.1
+ - @backstage/backend-openapi-utils@0.5.3-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/plugin-search-backend-module-catalog@0.3.4-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/plugin-search-backend-module-elasticsearch@1.7.2-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-search-backend-node@1.3.11-next.1
+ - @backstage/config@1.3.2
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/plugin-search-backend-module-explore@0.3.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-search-backend-node@1.3.11-next.1
+ - @backstage/config@1.3.2
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/plugin-search-backend-module-pg@0.5.44-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-search-backend-node@1.3.11-next.1
+ - @backstage/config@1.3.2
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/plugin-search-backend-module-stack-overflow-collator@0.3.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-search-backend-node@1.3.11-next.1
+ - @backstage/config@1.3.2
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/plugin-search-backend-module-techdocs@0.4.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-techdocs-node@1.13.3-next.1
+ - @backstage/plugin-search-backend-node@1.3.11-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/plugin-search-backend-node@1.3.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## @backstage/plugin-search-common@1.2.18-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/types@1.2.1
+
+## @backstage/plugin-signals@0.0.19-next.0
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/types@1.2.1
+ - @backstage/plugin-signals-react@0.0.12
+
+## @backstage/plugin-signals-backend@0.3.4-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-signals-node@0.1.20-next.1
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-signals-node@0.1.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## @backstage/plugin-techdocs@1.12.6-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-search-react@1.9.0-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/plugin-techdocs-react@1.2.17-next.0
+ - @backstage/plugin-auth-react@0.1.15-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-search-common@1.2.18-next.0
+ - @backstage/plugin-techdocs-common@0.1.0
+
+## @backstage/plugin-techdocs-addons-test-utils@1.0.48-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-techdocs@1.12.6-next.1
+ - @backstage/plugin-catalog@1.29.1-next.1
+ - @backstage/plugin-search-react@1.9.0-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/plugin-techdocs-react@1.2.17-next.0
+ - @backstage/test-utils@1.7.8-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+
+## @backstage/plugin-techdocs-backend@2.0.2-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-techdocs-node@1.13.3-next.1
+ - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-techdocs-common@0.1.0
+
+## @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-techdocs-react@1.2.17-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/core-plugin-api@1.10.6
+
+## @backstage/plugin-techdocs-node@1.13.3-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/plugin-search-common@1.2.18-next.0
+ - @backstage/plugin-techdocs-common@0.1.0
+
+## @backstage/plugin-techdocs-react@1.2.17-next.0
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/version-bridge@1.0.11
+
+## @backstage/plugin-user-settings@0.8.22-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 5b04b14: Uppercase language name in language select
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-signals-react@0.0.12
+ - @backstage/plugin-user-settings-common@0.0.1
+
+## @backstage/plugin-user-settings-backend@0.3.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-signals-node@0.1.20-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-user-settings-common@0.0.1
+
+## example-app@0.2.109-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/cli@0.32.1-next.1
+ - @backstage/canon@0.4.0-next.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.1
+ - @backstage/plugin-catalog-import@0.12.14-next.1
+ - @backstage/plugin-catalog-graph@0.4.19-next.1
+ - @backstage/plugin-notifications@0.5.5-next.1
+ - @backstage/plugin-user-settings@0.8.22-next.1
+ - @backstage/plugin-kubernetes@0.12.7-next.1
+ - @backstage/plugin-scaffolder@1.31.0-next.1
+ - @backstage/plugin-api-docs@0.12.7-next.1
+ - @backstage/plugin-devtools@0.1.27-next.1
+ - @backstage/plugin-techdocs@1.12.6-next.1
+ - @backstage/plugin-catalog@1.29.1-next.1
+ - @backstage/plugin-signals@0.0.19-next.0
+ - @backstage/plugin-search@1.4.26-next.1
+ - @backstage/plugin-home@0.8.8-next.1
+ - @backstage/plugin-org@0.6.39-next.1
+ - @backstage/plugin-search-react@1.9.0-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.25-next.1
+ - @backstage/frontend-app-api@0.11.2-next.1
+ - @backstage/plugin-scaffolder-react@1.16.0-next.1
+ - @backstage/plugin-techdocs-react@1.2.17-next.0
+ - @backstage/plugin-auth-react@0.1.15-next.0
+ - @backstage/app-defaults@1.6.2-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## example-app-next@0.0.23-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/cli@0.32.1-next.1
+ - @backstage/canon@0.4.0-next.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.1
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-app-visualizer@0.1.19-next.0
+ - @backstage/plugin-catalog-import@0.12.14-next.1
+ - @backstage/plugin-catalog-graph@0.4.19-next.1
+ - @backstage/plugin-notifications@0.5.5-next.1
+ - @backstage/plugin-user-settings@0.8.22-next.1
+ - @backstage/plugin-kubernetes@0.12.7-next.1
+ - @backstage/plugin-scaffolder@1.31.0-next.1
+ - @backstage/plugin-api-docs@0.12.7-next.1
+ - @backstage/plugin-techdocs@1.12.6-next.1
+ - @backstage/plugin-catalog@1.29.1-next.1
+ - @backstage/plugin-signals@0.0.19-next.0
+ - @backstage/plugin-search@1.4.26-next.1
+ - @backstage/plugin-home@0.8.8-next.1
+ - @backstage/plugin-app@0.1.9-next.1
+ - @backstage/plugin-org@0.6.39-next.1
+ - @backstage/plugin-search-react@1.9.0-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.25-next.1
+ - @backstage/frontend-app-api@0.11.2-next.1
+ - @backstage/plugin-scaffolder-react@1.16.0-next.1
+ - @backstage/plugin-techdocs-react@1.2.17-next.0
+ - @backstage/plugin-auth-react@0.1.15-next.0
+ - @backstage/app-defaults@1.6.2-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.1
+ - @backstage/frontend-defaults@0.2.2-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## app-next-example-plugin@0.0.23-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+
+## example-backend@0.0.38-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.0-next.1
+ - @backstage/plugin-catalog-backend@2.0.0-next.1
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.1
+ - @backstage/plugin-kubernetes-backend@0.19.6-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/plugin-scaffolder-backend@1.33.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-backend@0.7.0-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.1
+ - @backstage/plugin-techdocs-backend@2.0.2-next.1
+ - @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.1
+ - @backstage/plugin-notifications-backend@0.5.6-next.1
+ - @backstage/plugin-signals-backend@0.3.4-next.1
+ - @backstage/plugin-app-backend@0.5.2-next.1
+ - @backstage/plugin-devtools-backend@0.5.5-next.1
+ - @backstage/plugin-events-backend@0.5.2-next.1
+ - @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.1
+ - @backstage/plugin-proxy-backend@0.6.2-next.1
+ - @backstage/plugin-search-backend@2.0.2-next.1
+ - @backstage/plugin-search-backend-node@1.3.11-next.1
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.1
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.1
+ - @backstage/plugin-search-backend-module-catalog@0.3.4-next.1
+ - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.1
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.1
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.1
+ - @backstage/plugin-search-backend-module-explore@0.3.2-next.1
+
+## e2e-test@0.2.28-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.6.2-next.1
+ - @backstage/cli-common@0.1.15
+ - @backstage/errors@1.2.7
+
+## @internal/frontend@0.0.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
+## @internal/scaffolder@0.0.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/plugin-scaffolder-react@1.16.0-next.1
+
+## techdocs-cli-embedded-app@0.2.108-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/cli@0.32.1-next.1
+ - @backstage/plugin-techdocs@1.12.6-next.1
+ - @backstage/plugin-catalog@1.29.1-next.1
+ - @backstage/plugin-techdocs-react@1.2.17-next.0
+ - @backstage/app-defaults@1.6.2-next.0
+ - @backstage/test-utils@1.7.8-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+
+## @internal/plugin-todo-list@1.0.39-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+
+## @internal/plugin-todo-list-backend@1.0.39-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/errors@1.2.7
+
+## @internal/plugin-todo-list-common@1.0.25-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.0-next.0
diff --git a/docs/tutorials/setup-opentelemetry.md b/docs/tutorials/setup-opentelemetry.md
index 3d7cb4bb4b..23cd71b10f 100644
--- a/docs/tutorials/setup-opentelemetry.md
+++ b/docs/tutorials/setup-opentelemetry.md
@@ -27,21 +27,26 @@ yarn --cwd packages/backend add \
In your `packages/backend/src` folder, create an `instrumentation.js` file.
```typescript title="in packages/backend/src/instrumentation.js"
-const { NodeSDK } = require('@opentelemetry/sdk-node');
-const {
- getNodeAutoInstrumentations,
-} = require('@opentelemetry/auto-instrumentations-node');
-const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
+// Prevent from running more than once (due to worker threads)
+const { isMainThread } = require('node:worker_threads');
-// By default exports the metrics on localhost:9464/metrics
-const prometheus = new PrometheusExporter();
-const sdk = new NodeSDK({
- // You can add a traceExporter field here too
- metricReader: prometheus,
- instrumentations: [getNodeAutoInstrumentations()],
-});
+if (isMainThread) {
+ const { NodeSDK } = require('@opentelemetry/sdk-node');
+ const {
+ getNodeAutoInstrumentations,
+ } = require('@opentelemetry/auto-instrumentations-node');
+ const { PrometheusExporter } = require('@opentelemetry/exporter-prometheus');
-sdk.start();
+ // By default exports the metrics on localhost:9464/metrics
+ const prometheus = new PrometheusExporter();
+ const sdk = new NodeSDK({
+ // You can add a traceExporter field here too
+ metricReader: prometheus,
+ instrumentations: [getNodeAutoInstrumentations()],
+ });
+
+ sdk.start();
+}
```
You probably won't need all of the instrumentation inside `getNodeAutoInstrumentations()` so make sure to
diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md
index 88052c5f39..f171f951c4 100644
--- a/docs/tutorials/using-backstage-proxy-within-plugin.md
+++ b/docs/tutorials/using-backstage-proxy-within-plugin.md
@@ -27,7 +27,7 @@ If your plugin requires access to an API, backstage offers
- [Bundling your ApiRef with your plugin](#bundling-your-apiref-with-your-plugin)
- [Using the API in your components](#using-your-plugin-in-your-components)
-# Setting up the backstage proxy
+## Setting up the backstage proxy
Let's say your plugin's API is hosted at _https://api.myawesomeservice.com/v1_,
and you want to be able to access it within backstage at
diff --git a/lighthouserc.js b/lighthouserc.js
index 396bff5dc7..c7c1275cdb 100644
--- a/lighthouserc.js
+++ b/lighthouserc.js
@@ -29,6 +29,7 @@ module.exports = {
'http://localhost:3000/create/tasks',
'http://localhost:3000/create/actions',
'http://localhost:3000/create/edit',
+ 'http://localhost:3000/create/templating-extensions',
'http://localhost:3000/create/templates/default/react-ssr-template',
/** Search */
'http://localhost:3000/search',
diff --git a/microsite/blog/2025-04-29-backstagecon-kubecon-25-london.mdx b/microsite/blog/2025-04-29-backstagecon-kubecon-25-london.mdx
new file mode 100644
index 0000000000..99da880900
--- /dev/null
+++ b/microsite/blog/2025-04-29-backstagecon-kubecon-25-london.mdx
@@ -0,0 +1,119 @@
+---
+# prettier-ignore
+title: "Backstage in London: Highlights from BackstageCon and KubeCon + CloudNativeCon Europe 2025"
+author: André Wanlin, Spotify
+authorURL: https://github.com/awanlin
+authorImageURL: https://avatars.githubusercontent.com/u/67169551?v=4
+---
+
+
+
+Wow, London sure didn't hold back! [Five years][birthday] after the open source framework was released, Backstage was clearly on people's minds — from those new to internal developer portals to those who have been contributing since the earliest days. Conversations about Backstage spanned from BackstageCon to the rest of KubeCon + CloudNativeCon Europe, which had a record-setting attendance of nearly 13,000 people. But if you couldn't make it, then settle in, prepare a spot of tea, and read on for highlights. Then head over to the Backstage Community's YouTube channel to [catch up on all the great Backstage talks][playlist] you missed.
+
+[birthday]: https://www.youtube.com/watch?v=74ayHL1WHiE&list=PL8iP9yIjU0Q3K8LV_a9CcFmhvvNTAUzL1
+[playlist]: https://www.youtube.com/playlist?list=PL8iP9yIjU0Q3K8LV_a9CcFmhvvNTAUzL1
+
+{/* truncate */}
+
+## London called — the Backstage Community answered
+
+
+📸 _[CNCF][flickr]_
+
+## Scenes from BackstageCon
+
+
+📸 _[CNCF][flickr]_
+
+Kicking the week off was the fifth-ever BackstageCon — a full day of talks featuring voices and perspectives from across the community. Here are just a few of the highlights from the day.
+
+### #1: [Unlocking Backstage's Potential: Driving Adoption and Maturity With Your Developer Community][1]
+
+
+
+Learn how to bridge the gap between the lofty ROI numbers you've read about and your own experiences. Spotify's Stanley Nicholl shares his insights for how to unlock internal Backstage adoption by tapping into your greatest resource: your internal developer community.
+
+[1]: https://www.youtube.com/watch?v=Rg3ANYLyFoM&list=PL8iP9yIjU0Q3K8LV_a9CcFmhvvNTAUzL1
+
+### #2: [Panel: What Do Cars, Clothes, Wardrobes and MRI Machines Have in Common?][2]
+
+
+📸 _[CNCF][flickr]_
+
+If that riddle of a title doesn't pull you in, I'm not sure what else to say! This panel — featuring Scott Guymer (Philips), Remi Higuchi & Tina Vincent (H&M Group), Jan Magnusson (IKEA), and Martin Wänerskär (Volvo Cars) — brings a diverse group of Backstage adopters together. It's well worth hearing their different impact and success stories, and comparing their perspectives on the business case for developer portals.
+
+[2]: https://www.youtube.com/watch?v=h6BgF9dQDNo&list=PL8iP9yIjU0Q3K8LV_a9CcFmhvvNTAUzL1
+
+### #3: [Lightning Talk: We Built a Unified AI Asset Catalog in Backstage][3]
+
+
+📸 _[CNCF][flickr]_
+
+With all the various AI tools out there, it's becoming hard to keep track of them all. But with the Catalog in Backstage, this becomes much easier. John Collier from Red Hat walks through how they approached this problem space internally as their inventory of AI tools — inference servers, models, and API gateways — grew and grew.
+
+[3]: https://www.youtube.com/watch?v=tUR_2Nk0v4E&list=PL8iP9yIjU0Q3K8LV_a9CcFmhvvNTAUzL1
+
+### #4: [Exemplar-y Backstage Templates][4]
+
+
+
+The heart of offering your platform in a self-service way is to use Software Templates. What if you could make the templates themselves easier to update and contribute to? Brent Swisher from educational nonprofit ITHAKA walks through how they came up with a technique called “exemplar” templates to speed up the process of creating and updating templates, making them more accessible to other devs and easier to maintain.
+
+[4]: https://www.youtube.com/watch?v=xY7fVOVsK_A&list=PL8iP9yIjU0Q3K8LV_a9CcFmhvvNTAUzL1
+
+### #5: [From Click To Chaos: Linking Argo Workflows and Backstage for Automated Testing][5]
+
+
+
+This talk by Chris Heisz and Luke Beamish from graph technology specialists, Neo4j, is a great showcase of how you can use the Scaffolder along with Argo Workflows to offer self-service integration and chaos testing. See how they ensure thousands of Kubernetes clusters are stable and performant. (You'll be OK, little guy!)
+
+[5]: https://www.youtube.com/watch?v=qukwR1RDrFg&list=PL8iP9yIjU0Q3K8LV_a9CcFmhvvNTAUzL1
+
+### Watch all the recordings
+
+But that's just the start! There were so many more insights shared by the community. To see recordings of all the sessions, check out the full [BackstageCon playlist][playlist].
+
+## Breaking through the bustle at KubeCon + CloudNativeCon
+
+
+📸 _[CNCF][flickr]_
+
+KubeCon was also a great showcase for Backstage, grabbing the interests of both current and potential adopters.
+
+### Standing-room only for the State of Backstage
+
+
+📸 _Spotify_
+
+It was standing room only for the core maintainers talk — [The State of Backstage in 2025][state] — where they covered the past few months of accomplishments and outlined what they have in store for the rest of the year, including a demo of the latest features in the New Frontend System.
+
+[state]: https://www.youtube.com/watch?v=anqWhSnN7sA&list=PL8iP9yIjU0Q3K8LV_a9CcFmhvvNTAUzL1
+
+### Big birthday energy on the big stage
+
+
+📸 _[CNCF][flickr]_
+
+With the project just turning five, [Backstage got a nice Happy Birthday][keynote] from the Keynote main stage! Tyson Singer, Spotify's head of technology and platforms, outlined the journey Backstage took to get where it is now.
+
+[keynote]: https://www.youtube.com/watch?v=JqG1wey7-Ao&t=563s
+
+### Backstage Contribfest goes across the pond!
+
+
+_(Hey, that's me! Thanks for all the great pics, 📸 [CNCF][flickr])_
+
+After a successful appearance last year in [Salt Lake City][slc], Backstage ContribFest made its European debut. The community came out — with laptops in tow — for this session all about contributing to Backstage and the Community Plugins. Excited first-timers got to sit down with experienced contributors as well as core maintainers to work through a list of issues or to build plugins together. Keep your eyes on the release notes — some of those features might just have started here!
+
+[slc]: https://backstage.io/blog/2024/12/09/kubecon-slc-24
+
+## Cheerio, London! What's up, ATLiens!
+
+
+📸 _[CNCF][flickr]_
+
+What a jam-packed conference week! As I was walking out of the Excel London conference center, I swear I spotted Bowie in a low-riding, candy-red Cadillac saying something about needing to see [his cousins and his mama, too][spotify]? Guess we'll find out [in Atlanta this November][na]!
+
+[spotify]: https://open.spotify.com/track/2oLwDI8tZUOh6SmTMRRfKs?si=61b5e96260c34592
+[na]: https://events.linuxfoundation.org/kubecon-cloudnativecon-north-america/
+[flickr]: https://www.flickr.com/photos/143247548@N03/albums/72177720324566471
diff --git a/microsite/blog/assets/2025-04-29/backstagecon-kubecon-2025-london.png b/microsite/blog/assets/2025-04-29/backstagecon-kubecon-2025-london.png
new file mode 100644
index 0000000000..1e13820060
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/backstagecon-kubecon-2025-london.png differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-beefeaters.jpg b/microsite/blog/assets/2025-04-29/kc25london-beefeaters.jpg
new file mode 100644
index 0000000000..ffd8e3543a
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-beefeaters.jpg differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-contribfest.jpg b/microsite/blog/assets/2025-04-29/kc25london-contribfest.jpg
new file mode 100644
index 0000000000..371788b548
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-contribfest.jpg differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-crowds-entering.jpg b/microsite/blog/assets/2025-04-29/kc25london-crowds-entering.jpg
new file mode 100644
index 0000000000..a18bf496b7
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-crowds-entering.jpg differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-crowds-from-above.jpg b/microsite/blog/assets/2025-04-29/kc25london-crowds-from-above.jpg
new file mode 100644
index 0000000000..82451228d5
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-crowds-from-above.jpg differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-ithaka.png b/microsite/blog/assets/2025-04-29/kc25london-ithaka.png
new file mode 100644
index 0000000000..38cd2d98ec
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-ithaka.png differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-keynote.jpg b/microsite/blog/assets/2025-04-29/kc25london-keynote.jpg
new file mode 100644
index 0000000000..a73a469ed6
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-keynote.jpg differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-maintainers-talk.jpg b/microsite/blog/assets/2025-04-29/kc25london-maintainers-talk.jpg
new file mode 100644
index 0000000000..9f23075aa1
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-maintainers-talk.jpg differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-neo4j.png b/microsite/blog/assets/2025-04-29/kc25london-neo4j.png
new file mode 100644
index 0000000000..7ed51da169
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-neo4j.png differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-panel.jpg b/microsite/blog/assets/2025-04-29/kc25london-panel.jpg
new file mode 100644
index 0000000000..ab7de3e704
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-panel.jpg differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-redhat.jpg b/microsite/blog/assets/2025-04-29/kc25london-redhat.jpg
new file mode 100644
index 0000000000..25ec035ab4
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-redhat.jpg differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-sponsors.jpg b/microsite/blog/assets/2025-04-29/kc25london-sponsors.jpg
new file mode 100644
index 0000000000..f40b26665a
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-sponsors.jpg differ
diff --git a/microsite/blog/assets/2025-04-29/kc25london-spotify.png b/microsite/blog/assets/2025-04-29/kc25london-spotify.png
new file mode 100644
index 0000000000..4be228fc37
Binary files /dev/null and b/microsite/blog/assets/2025-04-29/kc25london-spotify.png differ
diff --git a/microsite/data/plugins/scalr b/microsite/data/plugins/scalr
new file mode 100644
index 0000000000..63b2403d39
--- /dev/null
+++ b/microsite/data/plugins/scalr
@@ -0,0 +1,10 @@
+---
+title: 'Scalr'
+author: 'Scalr'
+authorUrl: 'https://www.scalr.com'
+category: 'Infrastructure as Code'
+description: 'This Plugin adds Scalr Environments to your entities'
+documentation: 'https://github.com/Scalr/scalr-backstage-plugin/blob/main/README.md'
+iconUrl: 'https://cdn.prod.website-files.com/63e6a81a3b7fbc072c4af4a7/63eab2d9d2e4cb583a3a7613_Scalr%20Full-Color%20Logo%20Dark.svg'
+npmPackageName: '@scalr-io/backstage-plugin-scalr'
+addedDate: '2025-04-15'
diff --git a/microsite/data/plugins/sendgrid.yaml b/microsite/data/plugins/sendgrid.yaml
new file mode 100644
index 0000000000..11706dd9ed
--- /dev/null
+++ b/microsite/data/plugins/sendgrid.yaml
@@ -0,0 +1,9 @@
+---
+title: SendGrid Scaffolder Action
+author: CodeVerse-GP
+authorUrl: https://github.com/CodeVerse-GP
+category: Scaffolder
+description: A Backstage scaffolder action to send emails using SendGrid.
+documentation: https://github.com/CodeVerse-GP/scaffolder-backend-module-sendgrid#readme
+npmPackageName: '@codeverse-gp/scaffolder-backend-module-sendgrid'
+addedDate: '2025-04-18'
diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts
index 664f6e1e6e..71331710d5 100644
--- a/microsite/docusaurus.config.ts
+++ b/microsite/docusaurus.config.ts
@@ -453,7 +453,7 @@ const config: Config = {
},
{
label: 'Adopting',
- to: 'https://backstage.spotify.com',
+ to: 'https://backstage.io/docs/getting-started/',
},
{
label: 'Subscribe to our newsletter',
@@ -468,18 +468,6 @@ const config: Config = {
{
title: 'More',
items: [
- {
- label: 'Open Source @ Spotify',
- to: 'https://spotify.github.io/',
- },
- {
- label: 'Spotify Engineering Blog',
- to: 'https://engineering.atspotify.com/',
- },
- {
- label: 'Spotify for Developers',
- to: 'https://developer.spotify.com/',
- },
{
label: 'GitHub',
to: 'https://github.com/backstage/',
diff --git a/package.json b/package.json
index f42bafb4ab..15036733a7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "root",
- "version": "1.38.0",
+ "version": "1.39.0-next.1",
"backstage": {
"cli": {
"new": {
@@ -55,6 +55,7 @@
"start:microsite": "cd microsite/ && yarn start",
"start:next": "yarn start example-app-next example-backend",
"storybook": "yarn ./storybook run storybook",
+ "sync-issue-templates": "node ./.github/ISSUE_TEMPLATE/sync.js",
"techdocs-cli": "node scripts/techdocs-cli.js",
"techdocs-cli:dev": "cross-env TECHDOCS_CLI_DEV_MODE=true node scripts/techdocs-cli.js",
"test": "NODE_OPTIONS='--no-node-snapshot --experimental-vm-modules' backstage-cli repo test",
@@ -106,13 +107,15 @@
"csstype@npm:^3.0.2": "3.0.9",
"csstype@npm:^3.1.2": "3.0.9",
"csstype@npm:^3.1.3": "3.0.9",
- "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch"
+ "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch",
+ "@yarnpkg/plugin-npm@npm:^3.1.0": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch"
},
"dependencies": {
"@backstage/errors": "workspace:^",
"@manypkg/get-packages": "^1.1.3",
"@types/global-agent": "^2.1.3",
- "@useoptic/optic": "^1.0.0"
+ "@useoptic/optic": "^1.0.0",
+ "yaml": "^2.7.0"
},
"devDependencies": {
"@backstage/cli": "workspace:*",
diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md
index 0cfaba95ce..43487efc73 100644
--- a/packages/app-defaults/CHANGELOG.md
+++ b/packages/app-defaults/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/app-defaults
+## 1.6.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
## 1.6.1
### Patch Changes
diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json
index 76f16f935b..a6220cbe18 100644
--- a/packages/app-defaults/package.json
+++ b/packages/app-defaults/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/app-defaults",
- "version": "1.6.1",
+ "version": "1.6.2-next.0",
"description": "Provides the default wiring of a Backstage App",
"backstage": {
"role": "web-library"
diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md
index 2836f5c114..1cd3e791f0 100644
--- a/packages/app-next-example-plugin/CHANGELOG.md
+++ b/packages/app-next-example-plugin/CHANGELOG.md
@@ -1,5 +1,13 @@
# app-next-example-plugin
+## 0.0.23-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+
## 0.0.22
### Patch Changes
diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json
index 02c84250f9..f6699ef0fa 100644
--- a/packages/app-next-example-plugin/package.json
+++ b/packages/app-next-example-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "app-next-example-plugin",
- "version": "0.0.22",
+ "version": "0.0.23-next.0",
"description": "Backstage internal example plugin",
"backstage": {
"role": "frontend-plugin",
diff --git a/packages/app-next-example-plugin/src/plugin.tsx b/packages/app-next-example-plugin/src/plugin.tsx
index 9d335f480c..7e1e7a1bd6 100644
--- a/packages/app-next-example-plugin/src/plugin.tsx
+++ b/packages/app-next-example-plugin/src/plugin.tsx
@@ -28,6 +28,6 @@ export const ExamplePage = PageBlueprint.make({
/** @public */
export const examplePlugin = createFrontendPlugin({
- id: 'example',
+ pluginId: 'example',
extensions: [ExamplePage],
});
diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md
index 7b57ab2af3..0a34bdb8a8 100644
--- a/packages/app-next/CHANGELOG.md
+++ b/packages/app-next/CHANGELOG.md
@@ -1,5 +1,97 @@
# example-app-next
+## 0.0.23-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/cli@0.32.1-next.1
+ - @backstage/canon@0.4.0-next.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.1
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-app-visualizer@0.1.19-next.0
+ - @backstage/plugin-catalog-import@0.12.14-next.1
+ - @backstage/plugin-catalog-graph@0.4.19-next.1
+ - @backstage/plugin-notifications@0.5.5-next.1
+ - @backstage/plugin-user-settings@0.8.22-next.1
+ - @backstage/plugin-kubernetes@0.12.7-next.1
+ - @backstage/plugin-scaffolder@1.31.0-next.1
+ - @backstage/plugin-api-docs@0.12.7-next.1
+ - @backstage/plugin-techdocs@1.12.6-next.1
+ - @backstage/plugin-catalog@1.29.1-next.1
+ - @backstage/plugin-signals@0.0.19-next.0
+ - @backstage/plugin-search@1.4.26-next.1
+ - @backstage/plugin-home@0.8.8-next.1
+ - @backstage/plugin-app@0.1.9-next.1
+ - @backstage/plugin-org@0.6.39-next.1
+ - @backstage/plugin-search-react@1.9.0-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.25-next.1
+ - @backstage/frontend-app-api@0.11.2-next.1
+ - @backstage/plugin-scaffolder-react@1.16.0-next.1
+ - @backstage/plugin-techdocs-react@1.2.17-next.0
+ - @backstage/plugin-auth-react@0.1.15-next.0
+ - @backstage/app-defaults@1.6.2-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.1
+ - @backstage/frontend-defaults@0.2.2-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## 0.0.23-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/canon@0.3.2-next.0
+ - @backstage/plugin-user-settings@0.8.22-next.0
+ - @backstage/plugin-scaffolder@1.31.0-next.0
+ - @backstage/cli@0.32.1-next.0
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/plugin-scaffolder-react@1.16.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/plugin-catalog-import@0.12.14-next.0
+ - @backstage/plugin-techdocs@1.12.6-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.0
+ - @backstage/app-defaults@1.6.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-app-api@0.11.2-next.0
+ - @backstage/frontend-defaults@0.2.2-next.0
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/theme@0.6.5
+ - @backstage/plugin-api-docs@0.12.7-next.0
+ - @backstage/plugin-app@0.1.9-next.0
+ - @backstage/plugin-app-visualizer@0.1.18
+ - @backstage/plugin-auth-react@0.1.14
+ - @backstage/plugin-catalog@1.29.1-next.0
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-graph@0.4.19-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.0
+ - @backstage/plugin-home@0.8.8-next.0
+ - @backstage/plugin-kubernetes@0.12.7-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.25-next.0
+ - @backstage/plugin-notifications@0.5.5-next.0
+ - @backstage/plugin-org@0.6.39-next.0
+ - @backstage/plugin-permission-react@0.4.33
+ - @backstage/plugin-search@1.4.26-next.0
+ - @backstage/plugin-search-common@1.2.17
+ - @backstage/plugin-search-react@1.8.8
+ - @backstage/plugin-signals@0.0.18
+ - @backstage/plugin-techdocs-react@1.2.16
+
## 0.0.22
### Patch Changes
diff --git a/packages/app-next/package.json b/packages/app-next/package.json
index a0b91183d3..690a9d4f28 100644
--- a/packages/app-next/package.json
+++ b/packages/app-next/package.json
@@ -1,6 +1,6 @@
{
"name": "example-app-next",
- "version": "0.0.22",
+ "version": "0.0.23-next.1",
"backstage": {
"role": "frontend"
},
diff --git a/packages/app-next/src/examples/pagesPlugin.tsx b/packages/app-next/src/examples/pagesPlugin.tsx
index 1d0f9f3e0e..ddd080c28b 100644
--- a/packages/app-next/src/examples/pagesPlugin.tsx
+++ b/packages/app-next/src/examples/pagesPlugin.tsx
@@ -131,7 +131,7 @@ const ExternalPage = PageBlueprint.make({
});
export const pagesPlugin = createFrontendPlugin({
- id: 'pages',
+ pluginId: 'pages',
// routes: {
// index: indexRouteRef,
// // reference in config:
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index 1009b4ab4f..8ebf850a7d 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -1,5 +1,89 @@
# example-app
+## 0.2.109-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/cli@0.32.1-next.1
+ - @backstage/canon@0.4.0-next.1
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.1
+ - @backstage/plugin-catalog-import@0.12.14-next.1
+ - @backstage/plugin-catalog-graph@0.4.19-next.1
+ - @backstage/plugin-notifications@0.5.5-next.1
+ - @backstage/plugin-user-settings@0.8.22-next.1
+ - @backstage/plugin-kubernetes@0.12.7-next.1
+ - @backstage/plugin-scaffolder@1.31.0-next.1
+ - @backstage/plugin-api-docs@0.12.7-next.1
+ - @backstage/plugin-devtools@0.1.27-next.1
+ - @backstage/plugin-techdocs@1.12.6-next.1
+ - @backstage/plugin-catalog@1.29.1-next.1
+ - @backstage/plugin-signals@0.0.19-next.0
+ - @backstage/plugin-search@1.4.26-next.1
+ - @backstage/plugin-home@0.8.8-next.1
+ - @backstage/plugin-org@0.6.39-next.1
+ - @backstage/plugin-search-react@1.9.0-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/plugin-kubernetes-cluster@0.0.25-next.1
+ - @backstage/frontend-app-api@0.11.2-next.1
+ - @backstage/plugin-scaffolder-react@1.16.0-next.1
+ - @backstage/plugin-techdocs-react@1.2.17-next.0
+ - @backstage/plugin-auth-react@0.1.15-next.0
+ - @backstage/app-defaults@1.6.2-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## 0.2.109-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/canon@0.3.2-next.0
+ - @backstage/plugin-user-settings@0.8.22-next.0
+ - @backstage/plugin-scaffolder@1.31.0-next.0
+ - @backstage/cli@0.32.1-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/plugin-scaffolder-react@1.16.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/plugin-catalog-import@0.12.14-next.0
+ - @backstage/plugin-techdocs@1.12.6-next.0
+ - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.0
+ - @backstage/app-defaults@1.6.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-app-api@0.11.2-next.0
+ - @backstage/theme@0.6.5
+ - @backstage/plugin-api-docs@0.12.7-next.0
+ - @backstage/plugin-auth-react@0.1.14
+ - @backstage/plugin-catalog@1.29.1-next.0
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-graph@0.4.19-next.0
+ - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.0
+ - @backstage/plugin-devtools@0.1.27-next.0
+ - @backstage/plugin-home@0.8.8-next.0
+ - @backstage/plugin-kubernetes@0.12.7-next.0
+ - @backstage/plugin-kubernetes-cluster@0.0.25-next.0
+ - @backstage/plugin-notifications@0.5.5-next.0
+ - @backstage/plugin-org@0.6.39-next.0
+ - @backstage/plugin-permission-react@0.4.33
+ - @backstage/plugin-search@1.4.26-next.0
+ - @backstage/plugin-search-common@1.2.17
+ - @backstage/plugin-search-react@1.8.8
+ - @backstage/plugin-signals@0.0.18
+ - @backstage/plugin-techdocs-react@1.2.16
+
## 0.2.108
### Patch Changes
diff --git a/packages/app/package.json b/packages/app/package.json
index 9bd852a733..2393a89feb 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,6 +1,6 @@
{
"name": "example-app",
- "version": "0.2.108",
+ "version": "0.2.109-next.1",
"backstage": {
"role": "frontend"
},
diff --git a/packages/app/src/components/search/SearchModal.tsx b/packages/app/src/components/search/SearchModal.tsx
index 77c95399d0..04f49d6ddb 100644
--- a/packages/app/src/components/search/SearchModal.tsx
+++ b/packages/app/src/components/search/SearchModal.tsx
@@ -148,15 +148,18 @@ export const SearchModal = ({ toggleModal }: { toggleModal: () => void }) => {
values={async () => {
// Return a list of entities which are documented.
const { items } = await catalogApi.getEntities({
- fields: ['metadata.name'],
+ fields: ['metadata.name', 'metadata.title'],
filter: {
'metadata.annotations.backstage.io/techdocs-ref':
CATALOG_FILTER_EXISTS,
},
});
- const names = items.map(entity => entity.metadata.name);
- names.sort();
+ const names = items.map(entity => ({
+ value: entity.metadata.name,
+ label: entity.metadata.title ?? entity.metadata.name,
+ }));
+ names.sort((a, b) => a.label.localeCompare(b.label));
return names;
}}
/>
diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx
index 9d45583044..743a08561c 100644
--- a/packages/app/src/components/search/SearchPage.tsx
+++ b/packages/app/src/components/search/SearchPage.tsx
@@ -97,15 +97,18 @@ const SearchPage = () => {
values={async () => {
// Return a list of entities which are documented.
const { items } = await catalogApi.getEntities({
- fields: ['metadata.name'],
+ fields: ['metadata.name', 'metadata.title'],
filter: {
'metadata.annotations.backstage.io/techdocs-ref':
CATALOG_FILTER_EXISTS,
},
});
- const names = items.map(entity => entity.metadata.name);
- names.sort();
+ const names = items.map(entity => ({
+ value: entity.metadata.name,
+ label: entity.metadata.title ?? entity.metadata.name,
+ }));
+ names.sort((a, b) => a.label.localeCompare(b.label));
return names;
}}
/>
diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md
index 52b7763924..2d49092405 100644
--- a/packages/backend-app-api/CHANGELOG.md
+++ b/packages/backend-app-api/CHANGELOG.md
@@ -1,5 +1,56 @@
# @backstage/backend-app-api
+## 1.2.3-next.1
+
+### Patch Changes
+
+- 729a7d6: Added a configuration to permit backend plugin module failures on startup:
+
+ ```yaml
+ backend:
+ ...
+ startup:
+ plugins:
+ plugin-x:
+ modules:
+ module-y:
+ onPluginModuleBootFailure: continue
+ ```
+
+ This configuration permits `plugin-x` with `module-y` to fail on startup. Omitting the
+ `onPluginModuleBootFailure` configuration matches the previous behavior, wherein any
+ individual plugin module failure is forwarded to the plugin and aborts backend startup.
+
+ The default can also be changed, so that continuing on failure is the default
+ unless otherwise specified:
+
+ ```yaml
+ backend:
+ startup:
+ default:
+ onPluginModuleBootFailure: continue
+ plugins:
+ catalog:
+ modules:
+ github:
+ onPluginModuleBootFailure: abort
+ ```
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## 1.2.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 1.2.2
### Patch Changes
diff --git a/packages/backend-app-api/config.d.ts b/packages/backend-app-api/config.d.ts
index 0cf91852a1..58511c91fb 100644
--- a/packages/backend-app-api/config.d.ts
+++ b/packages/backend-app-api/config.d.ts
@@ -34,6 +34,14 @@ export interface Config {
* `onPluginBootFailure: abort` to be required.
*/
onPluginBootFailure?: 'continue' | 'abort';
+ /**
+ * The default value for `onPluginModuleBootFailure` if not specified for a particular plugin module.
+ * This defaults to 'abort', which means `onPluginModuleBootFailure: continue` must be specified
+ * for backend startup to continue on plugin module boot failure. This can also be set to
+ * 'continue', which flips the logic for individual plugin modules so that they must be set to
+ * `onPluginModuleBootFailure: abort` to be required.
+ */
+ onPluginModuleBootFailure?: 'continue' | 'abort';
};
plugins?: {
[pluginId: string]: {
@@ -46,6 +54,19 @@ export interface Config {
* setting).
*/
onPluginBootFailure?: 'continue' | 'abort';
+ modules?: {
+ [moduleId: string]: {
+ /**
+ * Used to control backend startup behavior when this plugin module fails to boot up. Setting
+ * this to `continue` allows the backend to continue starting up, even if this plugin
+ * module fails. This can enable leaving a crashing plugin installed, but still permit backend
+ * startup, which may help troubleshoot data-dependent issues. Plugin module failures for plugin modules
+ * set to `abort` are fatal (this is the default unless overridden by the `default`
+ * setting).
+ */
+ onPluginModuleBootFailure?: 'continue' | 'abort';
+ };
+ };
};
};
};
diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json
index 4eee5db7f6..01812f903c 100644
--- a/packages/backend-app-api/package.json
+++ b/packages/backend-app-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-app-api",
- "version": "1.2.2",
+ "version": "1.2.3-next.1",
"description": "Core API used by Backstage backend apps",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts
index fe0a4f526b..3b0596cd13 100644
--- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts
+++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts
@@ -541,7 +541,7 @@ describe('BackendInitializer', () => {
});
it('should forward errors when plugins fail to start', async () => {
- const init = new BackendInitializer([]);
+ const init = new BackendInitializer(baseFactories);
init.add(
createBackendPlugin({
pluginId: 'test',
@@ -562,8 +562,7 @@ describe('BackendInitializer', () => {
it('should permit startup errors for plugins with onPluginBootFailure: continue', async () => {
const init = new BackendInitializer([
- mockServices.rootLifecycle.factory(),
- mockServices.rootLogger.factory(),
+ ...baseFactories,
mockServices.rootConfig.factory({
data: {
backend: {
@@ -590,8 +589,7 @@ describe('BackendInitializer', () => {
it('should permit startup errors if the default onPluginBootFailure is continue', async () => {
const init = new BackendInitializer([
- mockServices.rootLifecycle.factory(),
- mockServices.rootLogger.factory(),
+ ...baseFactories,
mockServices.rootConfig.factory({
data: {
backend: {
@@ -618,8 +616,7 @@ describe('BackendInitializer', () => {
it('should forward errors for plugins explicitly marked to abort when the default is continue', async () => {
const init = new BackendInitializer([
- mockServices.rootLifecycle.factory(),
- mockServices.rootLogger.factory(),
+ ...baseFactories,
mockServices.rootConfig.factory({
data: {
backend: {
@@ -649,6 +646,130 @@ describe('BackendInitializer', () => {
);
});
+ it('should forward errors when plugin modules fail to start', async () => {
+ const init = new BackendInitializer(baseFactories);
+ init.add(testPlugin);
+ init.add(
+ createBackendModule({
+ pluginId: 'test',
+ moduleId: 'mod',
+ register(reg) {
+ reg.registerInit({
+ deps: {},
+ async init() {
+ throw new Error('NOPE');
+ },
+ });
+ },
+ }),
+ );
+ await expect(init.start()).rejects.toThrow(
+ "Module 'mod' for plugin 'test' startup failed; caused by Error: NOPE",
+ );
+ });
+
+ it('should permit startup errors for plugin modules with onPluginModuleBootFailure: continue', async () => {
+ const init = new BackendInitializer([
+ ...baseFactories,
+ mockServices.rootConfig.factory({
+ data: {
+ backend: {
+ startup: {
+ plugins: {
+ test: {
+ modules: { mod: { onPluginModuleBootFailure: 'continue' } },
+ },
+ },
+ },
+ },
+ },
+ }),
+ ]);
+ init.add(testPlugin);
+ init.add(
+ createBackendModule({
+ pluginId: 'test',
+ moduleId: 'mod',
+ register(reg) {
+ reg.registerInit({
+ deps: {},
+ async init() {
+ throw new Error('NOPE');
+ },
+ });
+ },
+ }),
+ );
+ await expect(init.start()).resolves.not.toThrow();
+ });
+
+ it('should permit startup errors if the default onPluginModuleBootFailure is continue', async () => {
+ const init = new BackendInitializer([
+ ...baseFactories,
+ mockServices.rootConfig.factory({
+ data: {
+ backend: {
+ startup: { default: { onPluginModuleBootFailure: 'continue' } },
+ },
+ },
+ }),
+ ]);
+ init.add(testPlugin);
+ init.add(
+ createBackendModule({
+ pluginId: 'test',
+ moduleId: 'mod',
+ register(reg) {
+ reg.registerInit({
+ deps: {},
+ async init() {
+ throw new Error('NOPE');
+ },
+ });
+ },
+ }),
+ );
+ await expect(init.start()).resolves.not.toThrow();
+ });
+
+ it('should forward errors for plugin modules explicitly marked to abort when the default is continue', async () => {
+ const init = new BackendInitializer([
+ ...baseFactories,
+ mockServices.rootConfig.factory({
+ data: {
+ backend: {
+ startup: {
+ default: { onPluginModuleBootFailure: 'continue' },
+ plugins: {
+ test: {
+ modules: { mod: { onPluginModuleBootFailure: 'abort' } },
+ },
+ },
+ },
+ },
+ },
+ }),
+ ]);
+ init.add(testPlugin);
+ init.add(
+ createBackendModule({
+ pluginId: 'test',
+ moduleId: 'mod',
+ register(reg) {
+ reg.registerInit({
+ deps: {},
+ async init() {
+ throw new Error('NOPE');
+ },
+ });
+ },
+ }),
+ );
+ await expect(init.start()).rejects.toThrow(
+ "Module 'mod' for plugin 'test' startup failed; caused by Error: NOPE",
+ );
+ });
+
it('should forward errors when multiple plugins fail to start', async () => {
const init = new BackendInitializer([]);
init.add(
diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts
index f521966d4d..9b925adb30 100644
--- a/packages/backend-app-api/src/wiring/BackendInitializer.ts
+++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts
@@ -365,17 +365,38 @@ export class BackendInitializer {
}
await tree.parallelTopologicalTraversal(
async ({ moduleId, moduleInit }) => {
- const moduleDeps = await this.#getInitDeps(
- moduleInit.init.deps,
- pluginId,
- moduleId,
- );
- await moduleInit.init.func(moduleDeps).catch(error => {
- throw new ForwardedError(
- `Module '${moduleId}' for plugin '${pluginId}' startup failed`,
- error,
+ const isModuleBootFailurePermitted =
+ this.#getPluginModuleBootFailurePredicate(
+ pluginId,
+ moduleId,
+ rootConfig,
);
- });
+
+ try {
+ const moduleDeps = await this.#getInitDeps(
+ moduleInit.init.deps,
+ pluginId,
+ moduleId,
+ );
+ await moduleInit.init.func(moduleDeps).catch(error => {
+ throw new ForwardedError(
+ `Module '${moduleId}' for plugin '${pluginId}' startup failed`,
+ error,
+ );
+ });
+ } catch (error: unknown) {
+ assertError(error);
+ if (isModuleBootFailurePermitted) {
+ initLogger.onPermittedPluginModuleFailure(
+ pluginId,
+ moduleId,
+ error,
+ );
+ } else {
+ initLogger.onPluginModuleFailed(pluginId, moduleId, error);
+ throw error;
+ }
+ }
},
);
}
@@ -472,7 +493,7 @@ export class BackendInitializer {
const rootLifecycleService = await this.#getRootLifecycleImpl();
- // Root services like the health one need to immediatelly be notified of the shutdown
+ // Root services like the health one need to immediately be notified of the shutdown
await rootLifecycleService.beforeShutdown();
// Get all plugins.
@@ -649,6 +670,24 @@ export class BackendInitializer {
return pluginStartupBootFailureValue === 'continue';
}
+
+ #getPluginModuleBootFailurePredicate(
+ pluginId: string,
+ moduleId: string,
+ config?: Config,
+ ): boolean {
+ const defaultStartupBootFailureValue =
+ config?.getOptionalString(
+ 'backend.startup.default.onPluginModuleBootFailure',
+ ) ?? 'abort';
+
+ const pluginModuleStartupBootFailureValue =
+ config?.getOptionalString(
+ `backend.startup.plugins.${pluginId}.modules.${moduleId}.onPluginModuleBootFailure`,
+ ) ?? defaultStartupBootFailureValue;
+
+ return pluginModuleStartupBootFailureValue === 'continue';
+ }
}
function toInternalBackendFeature(
diff --git a/packages/backend-app-api/src/wiring/createInitializationLogger.ts b/packages/backend-app-api/src/wiring/createInitializationLogger.ts
index 0978cfb0c8..600826a435 100644
--- a/packages/backend-app-api/src/wiring/createInitializationLogger.ts
+++ b/packages/backend-app-api/src/wiring/createInitializationLogger.ts
@@ -29,6 +29,12 @@ export function createInitializationLogger(
onPluginStarted(pluginId: string): void;
onPluginFailed(pluginId: string, error: Error): void;
onPermittedPluginFailure(pluginId: string, error: Error): void;
+ onPluginModuleFailed(pluginId: string, moduleId: string, error: Error): void;
+ onPermittedPluginModuleFailure(
+ pluginId: string,
+ moduleId: string,
+ error: Error,
+ ): void;
onAllStarted(): void;
} {
const logger = rootLogger?.child({ type: 'initialization' });
@@ -87,6 +93,26 @@ export function createInitializationLogger(
error,
);
},
+ onPluginModuleFailed(pluginId: string, moduleId: string, error: Error) {
+ const status =
+ starting.size > 0
+ ? `, waiting for ${starting.size} other plugins to finish before shutting down the process`
+ : '';
+ logger?.error(
+ `Module ${moduleId} in Plugin '${pluginId}' threw an error during startup${status}.`,
+ error,
+ );
+ },
+ onPermittedPluginModuleFailure(
+ pluginId: string,
+ moduleId: string,
+ error: Error,
+ ) {
+ logger?.error(
+ `Module ${moduleId} in Plugin '${pluginId}' threw an error during startup, but boot failure is permitted for this plugin module so startup will continue.`,
+ error,
+ );
+ },
onAllStarted() {
logger?.info(`Plugin initialization complete${getInitStatus()}`);
diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md
index aa92174067..49a911c398 100644
--- a/packages/backend-defaults/CHANGELOG.md
+++ b/packages/backend-defaults/CHANGELOG.md
@@ -1,5 +1,53 @@
# @backstage/backend-defaults
+## 0.10.0-next.1
+
+### Minor Changes
+
+- d385854: **BREAKING**: The `DefaultSchedulerService` constructor options now requires `RootLifecycleService`, `HttpRouterService`, and `PluginMetadataService` fields.
+
+ The scheduler will register a REST API for listing and triggering tasks. Please see [the scheduler documentation](https://backstage.io/docs/backend-system/core-services/scheduler) for more details about this API.
+
+### Patch Changes
+
+- acea1d4: update documentation
+- 72d019d: Removed various typos
+- c6bc67d: Added Valkey support alongside Redis in backend-defaults cache clients, using the new Keyv Valkey package. Also extended backend-test-utils to support Valkey in tests.
+- 36f77e9: Bug fix: Pass user provided token through to gitlab url resolvers
+- Updated dependencies
+ - @backstage/backend-app-api@1.2.3-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/backend-dev-utils@0.1.5
+ - @backstage/cli-node@0.2.13
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.9.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/backend-app-api@1.2.3-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/cli-node@0.2.13
+ - @backstage/config-loader@1.10.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/backend-dev-utils@0.1.5
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/types@1.2.1
+
## 0.9.0
### Minor Changes
diff --git a/packages/backend-defaults/config.d.ts b/packages/backend-defaults/config.d.ts
index b221edfcb5..14732bbccd 100644
--- a/packages/backend-defaults/config.d.ts
+++ b/packages/backend-defaults/config.d.ts
@@ -660,6 +660,77 @@ export interface Config {
};
};
}
+ | {
+ store: 'valkey';
+ /**
+ * A valkey connection string in the form `redis://user:pass@host:port`.
+ * @visibility secret
+ */
+ connection: string;
+ /** An optional default TTL (in milliseconds, if given as a number). */
+ defaultTtl?: number | HumanDuration | string;
+ valkey?: {
+ /**
+ * An optional Valkey client configuration. These options are passed to the `@keyv/valkey` client.
+ */
+ client?: {
+ /**
+ * Namespace for the current instance.
+ */
+ namespace?: string;
+ /**
+ * Separator to use between namespace and key.
+ */
+ keyPrefixSeparator?: string;
+ /**
+ * Number of keys to delete in a single batch.
+ */
+ clearBatchSize?: number;
+ /**
+ * Enable Unlink instead of using Del for clearing keys. This is more performant but may not be supported by all Redis versions.
+ */
+ useUnlink?: boolean;
+ /**
+ * Whether to allow clearing all keys when no namespace is set.
+ * If set to true and no namespace is set, iterate() will return all keys.
+ * Defaults to `false`.
+ */
+ noNamespaceAffectsAll?: boolean;
+ };
+ /**
+ * An optional Valkey cluster (redis cluster under the hood) configuration.
+ */
+ cluster?: {
+ /**
+ * Cluster configuration options to be passed to the `@keyv/valkey` client (and node-redis under the hood)
+ * https://github.com/redis/node-redis/blob/master/docs/clustering.md
+ *
+ * @visibility secret
+ */
+ rootNodes: Array;
+ /**
+ * Cluster node default configuration options to be passed to the `@keyv/redis` client (and node-redis under the hood)
+ * https://github.com/redis/node-redis/blob/master/docs/clustering.md
+ *
+ * @visibility secret
+ */
+ defaults?: Partial;
+ /**
+ * When `true`, `.connect()` will only discover the cluster topology, without actually connecting to all the nodes.
+ * Useful for short-term or PubSub-only connections.
+ */
+ minimizeConnections?: boolean;
+ /**
+ * When `true`, distribute load by executing readonly commands (such as `GET`, `GEOSEARCH`, etc.) across all cluster nodes. When `false`, only use master nodes.
+ */
+ useReplicas?: boolean;
+ /**
+ * The maximum number of times a command will be redirected due to `MOVED` or `ASK` errors.
+ */
+ maxCommandRedirections?: number;
+ };
+ };
+ }
| {
store: 'memcache';
/**
diff --git a/packages/backend-defaults/migrations/scheduler/20250411000000_last_run.js b/packages/backend-defaults/migrations/scheduler/20250411000000_last_run.js
new file mode 100644
index 0000000000..512aede943
--- /dev/null
+++ b/packages/backend-defaults/migrations/scheduler/20250411000000_last_run.js
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2024 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// @ts-check
+
+/**
+ * @param { import("knex").Knex } knex
+ * @returns { Promise }
+ */
+exports.up = async function up(knex) {
+ await knex.schema.alterTable('backstage_backend_tasks__tasks', table => {
+ table
+ .text('last_run_error_json', 'longtext')
+ .nullable()
+ .comment(
+ 'JSON serialized error object from the last task run, if it failed',
+ );
+ table
+ .dateTime('last_run_ended_at')
+ .nullable()
+ .comment('The last time that the task ended');
+ });
+};
+
+/**
+ * @param { import("knex").Knex } knex
+ * @returns { Promise }
+ */
+exports.down = async function down(knex) {
+ await knex.schema.alterTable('backstage_backend_tasks__tasks', table => {
+ table.dropColumn('last_run_error_json');
+ table.dropColumn('last_run_ended_at');
+ });
+};
diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json
index 45b41fa90e..ec6486d2b3 100644
--- a/packages/backend-defaults/package.json
+++ b/packages/backend-defaults/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-defaults",
- "version": "0.9.0",
+ "version": "0.10.0-next.1",
"description": "Backend defaults used by Backstage backend apps",
"backstage": {
"role": "node-library"
@@ -144,6 +144,7 @@
"@google-cloud/storage": "^7.0.0",
"@keyv/memcache": "^2.0.1",
"@keyv/redis": "^4.0.1",
+ "@keyv/valkey": "^1.0.1",
"@manypkg/get-packages": "^1.1.3",
"@octokit/rest": "^19.0.3",
"@opentelemetry/api": "^1.9.0",
diff --git a/packages/backend-defaults/report-scheduler.api.md b/packages/backend-defaults/report-scheduler.api.md
index 9879ad7c97..e0a85710a5 100644
--- a/packages/backend-defaults/report-scheduler.api.md
+++ b/packages/backend-defaults/report-scheduler.api.md
@@ -4,7 +4,9 @@
```ts
import { DatabaseService } from '@backstage/backend-plugin-api';
+import { HttpRouterService } from '@backstage/backend-plugin-api';
import { LoggerService } from '@backstage/backend-plugin-api';
+import { PluginMetadataService } from '@backstage/backend-plugin-api';
import { RootLifecycleService } from '@backstage/backend-plugin-api';
import { SchedulerService } from '@backstage/backend-plugin-api';
import { ServiceFactory } from '@backstage/backend-plugin-api';
@@ -15,7 +17,9 @@ export class DefaultSchedulerService {
static create(options: {
database: DatabaseService;
logger: LoggerService;
- rootLifecycle?: RootLifecycleService;
+ rootLifecycle: RootLifecycleService;
+ httpRouter: HttpRouterService;
+ pluginMetadata: PluginMetadataService;
}): SchedulerService;
}
diff --git a/packages/backend-defaults/report-scheduler.sql.md b/packages/backend-defaults/report-scheduler.sql.md
index 5542ac48b0..3dad48bd7e 100644
--- a/packages/backend-defaults/report-scheduler.sql.md
+++ b/packages/backend-defaults/report-scheduler.sql.md
@@ -10,6 +10,8 @@
| `current_run_started_at` | `timestamp with time zone` | true | - | - |
| `current_run_ticket` | `text` | true | - | - |
| `id` | `character varying` | false | 255 | - |
+| `last_run_ended_at` | `timestamp with time zone` | true | - | - |
+| `last_run_error_json` | `text` | true | - | - |
| `next_run_start_at` | `timestamp with time zone` | true | - | - |
| `settings_json` | `text` | false | - | - |
diff --git a/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts b/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts
index 189976ed7d..d8cb60e8b5 100644
--- a/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts
+++ b/packages/backend-defaults/src/entrypoints/auth/DefaultAuthService.ts
@@ -151,7 +151,7 @@ export class DefaultAuthService implements AuthService {
}
// check whether a plugin support the new auth system
- // by checking the public keys endpoint existance.
+ // by checking the public keys endpoint existence.
switch (type) {
// TODO: Check whether the principal is ourselves
case 'service':
diff --git a/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts
index 3a7886098f..4e9475b66e 100644
--- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts
+++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.test.ts
@@ -16,6 +16,7 @@
import { mockServices, TestCaches } from '@backstage/backend-test-utils';
import KeyvRedis, { createCluster } from '@keyv/redis';
+import KeyvValkey from '@keyv/valkey';
import KeyvMemcache from '@keyv/memcache';
import { CacheManager } from './CacheManager';
@@ -33,6 +34,16 @@ jest.mock('@keyv/redis', () => {
createCluster: jest.fn(),
};
});
+jest.mock('@keyv/valkey', () => {
+ const Actual = jest.requireActual('@keyv/valkey');
+ const DefaultConstructor = Actual.default;
+ return {
+ ...Actual,
+ __esModule: true,
+ default: jest.fn((...args: any[]) => new DefaultConstructor(...args)),
+ createCluster: jest.fn(),
+ };
+});
jest.mock('@keyv/memcache', () => {
const Actual = jest.requireActual('@keyv/memcache');
const DefaultConstructor = Actual.default;
@@ -70,6 +81,9 @@ describe('CacheManager integration', () => {
} else if (store === 'memcache') {
// eslint-disable-next-line jest/no-conditional-expect
expect(KeyvMemcache).toHaveBeenCalledTimes(3);
+ } else if (store === 'valkey') {
+ // eslint-disable-next-line jest/no-conditional-expect
+ expect(KeyvValkey).toHaveBeenCalledTimes(3);
}
},
);
diff --git a/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts
index ab8b03eb3b..9a691bdb21 100644
--- a/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts
+++ b/packages/backend-defaults/src/entrypoints/cache/CacheManager.ts
@@ -47,6 +47,7 @@ export class CacheManager {
*/
private readonly storeFactories = {
redis: this.createRedisStoreFactory(),
+ valkey: this.createValkeyStoreFactory(),
memcache: this.createMemcacheStoreFactory(),
memory: this.createMemoryStoreFactory(),
};
@@ -80,7 +81,7 @@ export class CacheManager {
if (config.has('backend.cache.useRedisSets')) {
logger?.warn(
- "The 'backend.cache.useRedisSets' configuration key is deprecated and no longer has any effect. The underlying '@keyv/redis' library no longer supports redis sets.",
+ "The 'backend.cache.useRedisSets' configuration key is deprecated and no longer has any effect. The underlying '@keyv/redis' and '@keyv/valkey' libraries no longer support redis sets.",
);
}
@@ -111,7 +112,7 @@ export class CacheManager {
/**
* Parse store-specific options from configuration.
*
- * @param store - The cache store type ('redis', 'memcache', or 'memory')
+ * @param store - The cache store type ('redis', 'valkey', 'memcache', or 'memory')
* @param config - The configuration service
* @param logger - Optional logger for warnings
* @returns The parsed store options
@@ -123,7 +124,10 @@ export class CacheManager {
): CacheStoreOptions | undefined {
const storeConfigPath = `backend.cache.${store}`;
- if (store === 'redis' && config.has(storeConfigPath)) {
+ if (
+ (store === 'redis' || store === 'valkey') &&
+ config.has(storeConfigPath)
+ ) {
return CacheManager.parseRedisOptions(storeConfigPath, config, logger);
}
@@ -255,6 +259,41 @@ export class CacheManager {
};
}
+ private createValkeyStoreFactory(): StoreFactory {
+ const KeyvValkey = require('@keyv/valkey').default;
+ const { createCluster } = require('@keyv/valkey');
+ const stores: Record = {};
+
+ return (pluginId, defaultTtl) => {
+ if (!stores[pluginId]) {
+ const valkeyOptions = this.storeOptions?.client || {
+ keyPrefixSeparator: ':',
+ };
+ if (this.storeOptions?.cluster) {
+ // Create a Valkey cluster (Redis cluster under the hood)
+ const cluster = createCluster(this.storeOptions?.cluster);
+ stores[pluginId] = new KeyvValkey(cluster, valkeyOptions);
+ } else {
+ // Create a regular Valkey connection
+ stores[pluginId] = new KeyvValkey(this.connection, valkeyOptions);
+ }
+
+ // Always provide an error handler to avoid stopping the process
+ stores[pluginId].on('error', (err: Error) => {
+ this.logger?.error('Failed to create valkey cache client', err);
+ this.errorHandler?.(err);
+ });
+ }
+ return new Keyv({
+ namespace: pluginId,
+ ttl: defaultTtl,
+ store: stores[pluginId],
+ emitErrors: false,
+ useKeyPrefix: false,
+ });
+ };
+ }
+
private createMemcacheStoreFactory(): StoreFactory {
const KeyvMemcache = require('@keyv/memcache').default;
const stores: Record = {};
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/database/migrateBackendTasks.ts b/packages/backend-defaults/src/entrypoints/scheduler/database/migrateBackendTasks.ts
index ab8b7a8e73..1d847f5c59 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/database/migrateBackendTasks.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/database/migrateBackendTasks.ts
@@ -18,12 +18,12 @@ import { resolvePackagePath } from '@backstage/backend-plugin-api';
import { Knex } from 'knex';
import { DB_MIGRATIONS_TABLE } from './tables';
-export async function migrateBackendTasks(knex: Knex): Promise {
- const migrationsDir = resolvePackagePath(
- '@backstage/backend-defaults',
- 'migrations/scheduler',
- );
+export const migrationsDir = resolvePackagePath(
+ '@backstage/backend-defaults',
+ 'migrations/scheduler',
+);
+export async function migrateBackendTasks(knex: Knex): Promise {
await knex.migrate.latest({
directory: migrationsDir,
tableName: DB_MIGRATIONS_TABLE,
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/database/migrations.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/database/migrations.test.ts
new file mode 100644
index 0000000000..9bd987ac70
--- /dev/null
+++ b/packages/backend-defaults/src/entrypoints/scheduler/database/migrations.test.ts
@@ -0,0 +1,108 @@
+/*
+ * 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 { Knex } from 'knex';
+import { TestDatabases } from '@backstage/backend-test-utils';
+import fs from 'fs';
+import { migrationsDir } from './migrateBackendTasks';
+
+const migrationsFiles = fs.readdirSync(migrationsDir).sort();
+
+async function migrateUpOnce(knex: Knex): Promise {
+ await knex.migrate.up({ directory: migrationsDir });
+}
+
+async function migrateDownOnce(knex: Knex): Promise {
+ await knex.migrate.down({ directory: migrationsDir });
+}
+
+async function migrateUntilBefore(knex: Knex, target: string): Promise {
+ const index = migrationsFiles.indexOf(target);
+ if (index === -1) {
+ throw new Error(`Migration ${target} not found`);
+ }
+ for (let i = 0; i < index; i++) {
+ await migrateUpOnce(knex);
+ }
+}
+
+jest.setTimeout(60_000);
+
+describe('migrations', () => {
+ const databases = TestDatabases.create();
+
+ it.each(databases.eachSupportedId())(
+ '20250411000000_last_run.js, %p',
+ async databaseId => {
+ const knex = await databases.init(databaseId);
+
+ await migrateUntilBefore(knex, '20250411000000_last_run.js');
+
+ await knex
+ .insert({
+ id: 'i',
+ settings_json: '{}',
+ })
+ .into('backstage_backend_tasks__tasks');
+
+ await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
+ {
+ id: 'i',
+ settings_json: '{}',
+ next_run_start_at: null,
+ current_run_ticket: null,
+ current_run_started_at: null,
+ current_run_expires_at: null,
+ },
+ ]);
+
+ await migrateUpOnce(knex);
+
+ await knex
+ .table('backstage_backend_tasks__tasks')
+ .update({ last_run_error_json: 'error' })
+ .where({ id: 'i' });
+
+ await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
+ {
+ id: 'i',
+ settings_json: '{}',
+ next_run_start_at: null,
+ current_run_ticket: null,
+ current_run_started_at: null,
+ current_run_expires_at: null,
+ last_run_ended_at: null,
+ last_run_error_json: 'error',
+ },
+ ]);
+
+ await migrateDownOnce(knex);
+
+ await expect(knex('backstage_backend_tasks__tasks')).resolves.toEqual([
+ {
+ id: 'i',
+ settings_json: '{}',
+ next_run_start_at: null,
+ current_run_ticket: null,
+ current_run_started_at: null,
+ current_run_expires_at: null,
+ },
+ ]);
+
+ await knex.destroy();
+ },
+ );
+});
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/database/tables.ts b/packages/backend-defaults/src/entrypoints/scheduler/database/tables.ts
index 63aad6e42a..8e2fa8d92e 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/database/tables.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/database/tables.ts
@@ -20,8 +20,10 @@ export const DB_TASKS_TABLE = 'backstage_backend_tasks__tasks';
export type DbTasksRow = {
id: string;
settings_json: string;
- next_run_start_at: Date;
+ next_run_start_at?: Date | string; // This can be null when in manual trigger mode
current_run_ticket?: string;
current_run_started_at?: Date | string;
current_run_expires_at?: Date | string;
+ last_run_error_json?: string;
+ last_run_ended_at?: Date | string;
};
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.test.ts
index 199d25f2aa..61f9bd0639 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.test.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.test.ts
@@ -19,12 +19,18 @@ import { Duration } from 'luxon';
import waitForExpect from 'wait-for-expect';
import { DefaultSchedulerService } from './DefaultSchedulerService';
import { createTestScopedSignal } from './__testUtils__/createTestScopedSignal';
+import { PluginMetadataService } from '@backstage/backend-plugin-api';
jest.setTimeout(60_000);
describe('TaskScheduler', () => {
const logger = mockServices.logger.mock();
const databases = TestDatabases.create();
+ const rootLifecycle = mockServices.rootLifecycle.mock();
+ const httpRouter = mockServices.httpRouter.mock();
+ const pluginMetadata = {
+ getId: () => 'test',
+ } satisfies PluginMetadataService;
const testScopedSignal = createTestScopedSignal();
it.each(databases.eachSupportedId())(
@@ -33,7 +39,13 @@ describe('TaskScheduler', () => {
const knex = await databases.init(databaseId);
const database = mockServices.database({ knex });
- const manager = DefaultSchedulerService.create({ database, logger });
+ const manager = DefaultSchedulerService.create({
+ database,
+ logger,
+ rootLifecycle,
+ httpRouter,
+ pluginMetadata,
+ });
const fn = jest.fn();
await manager.scheduleTask({
@@ -56,7 +68,13 @@ describe('TaskScheduler', () => {
const knex = await databases.init(databaseId);
const database = mockServices.database({ knex });
- const manager = DefaultSchedulerService.create({ database, logger });
+ const manager = DefaultSchedulerService.create({
+ database,
+ logger,
+ rootLifecycle,
+ httpRouter,
+ pluginMetadata,
+ });
const fn = jest.fn();
await manager.scheduleTask({
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.ts
index dcaf673753..0a7b9fbb87 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/DefaultSchedulerService.ts
@@ -16,7 +16,9 @@
import {
DatabaseService,
+ HttpRouterService,
LoggerService,
+ PluginMetadataService,
RootLifecycleService,
SchedulerService,
} from '@backstage/backend-plugin-api';
@@ -35,7 +37,9 @@ export class DefaultSchedulerService {
static create(options: {
database: DatabaseService;
logger: LoggerService;
- rootLifecycle?: RootLifecycleService;
+ rootLifecycle: RootLifecycleService;
+ httpRouter: HttpRouterService;
+ pluginMetadata: PluginMetadataService;
}): SchedulerService {
const databaseFactory = once(async () => {
const knex = await options.database.getClient();
@@ -52,17 +56,22 @@ export class DefaultSchedulerService {
logger: options.logger,
});
- options.rootLifecycle?.addShutdownHook(() => abortController.abort());
+ options.rootLifecycle.addShutdownHook(() => abortController.abort());
janitor.start(abortController.signal);
}
return knex;
});
- return new PluginTaskSchedulerImpl(
+ const scheduler = new PluginTaskSchedulerImpl(
+ options.pluginMetadata.getId(),
databaseFactory,
options.logger,
options.rootLifecycle,
);
+
+ options.httpRouter.use(scheduler.getRouter());
+
+ return scheduler;
}
}
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts
index d4d3d16067..14fedfb3c0 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.test.ts
@@ -16,6 +16,9 @@
import { LocalTaskWorker } from './LocalTaskWorker';
import { mockServices } from '@backstage/backend-test-utils';
+import waitFor from 'wait-for-expect';
+
+jest.setTimeout(10_000);
describe('LocalTaskWorker', () => {
const logger = mockServices.logger.mock();
@@ -106,4 +109,108 @@ describe('LocalTaskWorker', () => {
expect(fn).toHaveBeenCalledTimes(2);
controller.abort();
});
+
+ it('goes through the expected states', async () => {
+ const fn = jest
+ .fn()
+ .mockImplementationOnce(() => new Promise(r => setTimeout(r, 100)))
+ .mockImplementationOnce(
+ () =>
+ new Promise((_, r) =>
+ setTimeout(() => r(new Error('boo')), 100),
+ ),
+ )
+ .mockImplementation(() => new Promise(r => setTimeout(r, 100)));
+ const controller = new AbortController();
+
+ const worker = new LocalTaskWorker('a', fn, logger);
+ worker.start(
+ {
+ version: 2,
+ initialDelayDuration: 'PT0.5S',
+ cadence: 'PT0.5S',
+ timeoutAfterDuration: 'PT1S',
+ },
+ { signal: controller.signal },
+ );
+
+ await waitFor(() => {
+ expect(worker.taskState()).toEqual({
+ status: 'idle',
+ startsAt: expect.any(String),
+ });
+ expect(worker.workerState()).toEqual({
+ status: 'initial-wait',
+ });
+ });
+
+ // Start, complete successfully
+ await waitFor(() => {
+ expect(worker.taskState()).toEqual({
+ status: 'running',
+ startedAt: expect.any(String),
+ timesOutAt: expect.any(String),
+ });
+ expect(worker.workerState()).toEqual({
+ status: 'running',
+ });
+ });
+ await waitFor(() => {
+ expect(worker.taskState()).toEqual({
+ status: 'idle',
+ startsAt: expect.any(String),
+ lastRunEndedAt: expect.any(String),
+ });
+ expect(worker.workerState()).toEqual({
+ status: 'idle',
+ });
+ });
+
+ // Start, complete with error
+ await waitFor(() => {
+ expect(worker.taskState()).toEqual({
+ status: 'running',
+ startedAt: expect.any(String),
+ timesOutAt: expect.any(String),
+ lastRunEndedAt: expect.any(String),
+ });
+ expect(worker.workerState()).toEqual({
+ status: 'running',
+ });
+ });
+ await waitFor(() => {
+ expect(worker.taskState()).toEqual({
+ status: 'idle',
+ startsAt: expect.any(String),
+ lastRunEndedAt: expect.any(String),
+ lastRunError: expect.any(String),
+ });
+ expect(worker.workerState()).toEqual({
+ status: 'idle',
+ });
+ });
+
+ // Start, complete successfully
+ await waitFor(() => {
+ expect(worker.taskState()).toEqual({
+ status: 'running',
+ startedAt: expect.any(String),
+ timesOutAt: expect.any(String),
+ lastRunEndedAt: expect.any(String),
+ });
+ expect(worker.workerState()).toEqual({
+ status: 'running',
+ });
+ });
+ await waitFor(() => {
+ expect(worker.taskState()).toEqual({
+ status: 'idle',
+ startsAt: expect.any(String),
+ lastRunEndedAt: expect.any(String),
+ });
+ expect(worker.workerState()).toEqual({
+ status: 'idle',
+ });
+ });
+ });
});
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts
index 278afae224..a063f4145e 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/LocalTaskWorker.ts
@@ -19,8 +19,8 @@ import { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api';
import { ConflictError } from '@backstage/errors';
import { CronTime } from 'cron';
import { DateTime, Duration } from 'luxon';
-import { TaskSettingsV2 } from './types';
-import { delegateAbortController, sleep } from './util';
+import { TaskSettingsV2, TaskApiTasksResponse } from './types';
+import { delegateAbortController, serializeError, sleep } from './util';
/**
* Implements tasks that run locally without cross-host collaboration.
@@ -29,6 +29,12 @@ import { delegateAbortController, sleep } from './util';
*/
export class LocalTaskWorker {
private abortWait: AbortController | undefined;
+ #taskState: Exclude = {
+ status: 'idle',
+ };
+ #workerState: TaskApiTasksResponse['workerState'] = {
+ status: 'idle',
+ };
constructor(
private readonly taskId: string,
@@ -45,12 +51,7 @@ export class LocalTaskWorker {
let attemptNum = 1;
for (;;) {
try {
- if (settings.initialDelayDuration) {
- await this.sleep(
- Duration.fromISO(settings.initialDelayDuration),
- options.signal,
- );
- }
+ await this.performInitialWait(settings, options.signal);
while (!options.signal.aborted) {
const startTime = process.hrtime();
@@ -84,6 +85,38 @@ export class LocalTaskWorker {
this.abortWait.abort();
}
+ taskState(): TaskApiTasksResponse['taskState'] {
+ return this.#taskState;
+ }
+
+ workerState(): TaskApiTasksResponse['workerState'] {
+ return this.#workerState;
+ }
+
+ /**
+ * Does the once-at-startup initial wait, if configured.
+ */
+ private async performInitialWait(
+ settings: TaskSettingsV2,
+ signal: AbortSignal,
+ ): Promise {
+ if (settings.initialDelayDuration) {
+ const parsedDuration = Duration.fromISO(settings.initialDelayDuration);
+
+ this.#taskState = {
+ status: 'idle',
+ startsAt: DateTime.utc().plus(parsedDuration).toISO()!,
+ lastRunEndedAt: this.#taskState.lastRunEndedAt,
+ lastRunError: this.#taskState.lastRunError,
+ };
+ this.#workerState = {
+ status: 'initial-wait',
+ };
+
+ await this.sleep(parsedDuration, signal);
+ }
+ }
+
/**
* Makes a single attempt at running the task to completion.
*/
@@ -94,14 +127,29 @@ export class LocalTaskWorker {
// Abort the task execution either if the worker is stopped, or if the
// task timeout is hit
const taskAbortController = delegateAbortController(signal);
+ const timeoutDuration = Duration.fromISO(settings.timeoutAfterDuration);
const timeoutHandle = setTimeout(() => {
taskAbortController.abort();
- }, Duration.fromISO(settings.timeoutAfterDuration).as('milliseconds'));
+ }, timeoutDuration.as('milliseconds'));
+
+ this.#taskState = {
+ status: 'running',
+ startedAt: DateTime.utc().toISO()!,
+ timesOutAt: DateTime.utc().plus(timeoutDuration).toISO()!,
+ lastRunEndedAt: this.#taskState.lastRunEndedAt,
+ lastRunError: this.#taskState.lastRunError,
+ };
+ this.#workerState = {
+ status: 'running',
+ };
try {
await this.fn(taskAbortController.signal);
+ this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!;
+ this.#taskState.lastRunError = undefined;
} catch (e) {
- // ignore intentionally
+ this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!;
+ this.#taskState.lastRunError = serializeError(e);
}
// release resources
@@ -133,11 +181,20 @@ export class LocalTaskWorker {
}
dt = Math.max(dt, 0);
+ const startsAt = DateTime.now().plus(Duration.fromMillis(dt));
+
+ this.#taskState = {
+ status: 'idle',
+ startsAt: startsAt.toISO()!,
+ lastRunEndedAt: this.#taskState.lastRunEndedAt,
+ lastRunError: this.#taskState.lastRunError,
+ };
+ this.#workerState = {
+ status: 'idle',
+ };
this.logger.debug(
- `task: ${this.taskId} will next occur around ${DateTime.now().plus(
- Duration.fromMillis(dt),
- )}`,
+ `task: ${this.taskId} will next occur around ${startsAt}`,
);
await this.sleep(Duration.fromMillis(dt), signal);
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts
index 712ed63a2f..c0339a7a2d 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.test.ts
@@ -53,6 +53,7 @@ describe('PluginTaskManagerImpl', () => {
const knex = await databases.init(databaseId);
await migrateBackendTasks(knex);
const manager = new PluginTaskSchedulerImpl(
+ 'myplugin',
async () => knex,
mockServices.logger.mock(),
{
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts
index ce1ff823cf..b7a946ab80 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts
@@ -27,9 +27,11 @@ import {
import { Counter, Histogram, Gauge, metrics, trace } from '@opentelemetry/api';
import { Knex } from 'knex';
import { Duration } from 'luxon';
+import express from 'express';
+import Router from 'express-promise-router';
import { LocalTaskWorker } from './LocalTaskWorker';
import { TaskWorker } from './TaskWorker';
-import { TaskSettingsV2 } from './types';
+import { TaskSettingsV2, TaskApiTasksResponse } from './types';
import { delegateAbortController, TRACER_ID, validateId } from './util';
const tracer = trace.getTracer(TRACER_ID);
@@ -38,7 +40,8 @@ const tracer = trace.getTracer(TRACER_ID);
* Implements the actual task management.
*/
export class PluginTaskSchedulerImpl implements SchedulerService {
- private readonly localTasksById = new Map();
+ private readonly localWorkersById = new Map();
+ private readonly globalWorkersById = new Map();
private readonly allScheduledTasks: SchedulerServiceTaskDescriptor[] = [];
private readonly shutdownInitiated: Promise;
@@ -48,9 +51,10 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
private readonly lastCompleted: Gauge;
constructor(
+ private readonly pluginId: string,
private readonly databaseFactory: () => Promise,
private readonly logger: LoggerService,
- rootLifecycle?: RootLifecycleService,
+ rootLifecycle: RootLifecycleService,
) {
const meter = metrics.getMeter('default');
this.counter = meter.createCounter('backend_tasks.task.runs.count', {
@@ -72,12 +76,12 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
},
);
this.shutdownInitiated = new Promise(shutdownInitiated => {
- rootLifecycle?.addShutdownHook(() => shutdownInitiated(true));
+ rootLifecycle.addShutdownHook(() => shutdownInitiated(true));
});
}
async triggerTask(id: string): Promise {
- const localTask = this.localTasksById.get(id);
+ const localTask = this.localWorkersById.get(id);
if (localTask) {
localTask.trigger();
return;
@@ -116,6 +120,7 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
this.logger.child({ task: task.id }),
);
await worker.start(settings, { signal: abortController.signal });
+ this.globalWorkersById.set(task.id, worker);
} else {
const worker = new LocalTaskWorker(
task.id,
@@ -123,7 +128,7 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
this.logger.child({ task: task.id }),
);
worker.start(settings, { signal: abortController.signal });
- this.localTasksById.set(task.id, worker);
+ this.localWorkersById.set(task.id, worker);
}
this.allScheduledTasks.push({
@@ -147,6 +152,47 @@ export class PluginTaskSchedulerImpl implements SchedulerService {
return this.allScheduledTasks;
}
+ getRouter(): express.Router {
+ const router = Router();
+
+ router.get('/.backstage/scheduler/v1/tasks', async (_, res) => {
+ const globalState = await TaskWorker.taskStates(
+ await this.databaseFactory(),
+ );
+
+ const tasks = new Array();
+ for (const task of this.allScheduledTasks) {
+ tasks.push({
+ taskId: task.id,
+ pluginId: this.pluginId,
+ scope: task.scope,
+ settings: task.settings,
+ taskState:
+ this.localWorkersById.get(task.id)?.taskState() ??
+ globalState.get(task.id) ??
+ null,
+ workerState:
+ this.localWorkersById.get(task.id)?.workerState() ??
+ this.globalWorkersById.get(task.id)?.workerState() ??
+ null,
+ });
+ }
+
+ res.json({ tasks });
+ });
+
+ router.post(
+ '/.backstage/scheduler/v1/tasks/:id/trigger',
+ async (req, res) => {
+ const { id } = req.params;
+ await this.triggerTask(id);
+ res.status(200).end();
+ },
+ );
+
+ return router;
+ }
+
private instrumentedFunction(
task: SchedulerServiceTaskInvocationDefinition,
scope: string,
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.test.ts
index b2c6af24cb..bfba35ac30 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.test.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.test.ts
@@ -87,6 +87,8 @@ describe('PluginTaskSchedulerJanitor', () => {
current_run_ticket: null,
current_run_started_at: null,
current_run_expires_at: null,
+ last_run_ended_at: expect.anything(),
+ last_run_error_json: expect.stringContaining('Task timed out'),
}),
);
});
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.ts
index b0cd4572cf..a165dac6a0 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/PluginTaskSchedulerJanitor.ts
@@ -18,7 +18,7 @@ import { LoggerService } from '@backstage/backend-plugin-api';
import { Knex } from 'knex';
import { Duration } from 'luxon';
import { DB_TASKS_TABLE, DbTasksRow } from '../database/tables';
-import { sleep } from './util';
+import { serializeError, sleep } from './util';
/**
* Makes sure to auto-expire and clean up things that time out or for other
@@ -69,6 +69,8 @@ export class PluginTaskSchedulerJanitor {
current_run_ticket: dbNull,
current_run_started_at: dbNull,
current_run_expires_at: dbNull,
+ last_run_ended_at: this.knex.fn.now(),
+ last_run_error_json: serializeError(new Error('Task timed out')),
});
} else {
tasks = await this.knex(DB_TASKS_TABLE)
@@ -77,6 +79,8 @@ export class PluginTaskSchedulerJanitor {
current_run_ticket: dbNull,
current_run_started_at: dbNull,
current_run_expires_at: dbNull,
+ last_run_ended_at: this.knex.fn.now(),
+ last_run_error_json: serializeError(new Error('Task timed out')),
})
.returning(['id']);
}
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts
index 5db82f3ece..de1b6e4caf 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.test.ts
@@ -68,6 +68,17 @@ describe('TaskWorker', () => {
initialDelayDuration: 'PT1S',
timeoutAfterDuration: 'PT1M',
});
+ await expect(TaskWorker.taskStates(knex)).resolves.toEqual(
+ new Map([
+ [
+ 'task1',
+ {
+ status: 'idle',
+ startsAt: expect.anything(),
+ },
+ ],
+ ]),
+ );
await expect(worker.findReadyTask()).resolves.toEqual({
result: 'not-ready-yet',
@@ -101,6 +112,18 @@ describe('TaskWorker', () => {
current_run_expires_at: expect.anything(),
}),
);
+ await expect(TaskWorker.taskStates(knex)).resolves.toEqual(
+ new Map([
+ [
+ 'task1',
+ {
+ status: 'running',
+ startedAt: expect.anything(),
+ timesOutAt: expect.anything(),
+ },
+ ],
+ ]),
+ );
await expect(worker.tryReleaseTask('ticket', settings)).resolves.toBe(
true,
@@ -115,6 +138,18 @@ describe('TaskWorker', () => {
current_run_expires_at: null,
}),
);
+ await expect(TaskWorker.taskStates(knex)).resolves.toEqual(
+ new Map([
+ [
+ 'task1',
+ {
+ status: 'idle',
+ startsAt: expect.anything(),
+ lastRunEndedAt: expect.anything(),
+ },
+ ],
+ ]),
+ );
},
);
@@ -136,8 +171,21 @@ describe('TaskWorker', () => {
const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency);
worker.start(settings, { signal: testScopedSignal() });
- await waitForExpect(() => {
+ await waitForExpect(async () => {
expect(logger.error).toHaveBeenCalled();
+ await expect(TaskWorker.taskStates(knex)).resolves.toEqual(
+ new Map([
+ [
+ 'task1',
+ {
+ status: 'idle',
+ startsAt: expect.anything(),
+ lastRunEndedAt: expect.anything(),
+ lastRunError: expect.stringContaining('failed'),
+ },
+ ],
+ ]),
+ );
});
},
);
@@ -373,7 +421,8 @@ describe('TaskWorker', () => {
// contrived check removes a test flakiness based on wall clock time.
expect(
Math.abs(
- +new Date(row3.next_run_start_at) - +new Date(row2.next_run_start_at),
+ +new Date(row3.next_run_start_at!) -
+ +new Date(row2.next_run_start_at!),
),
).toBeLessThanOrEqual(60_000);
@@ -416,10 +465,10 @@ describe('TaskWorker', () => {
const row1 = (await knex(DB_TASKS_TABLE))[0];
const rowAfterClaimAndReleaseNextStartAt = DateTime.fromJSDate(
- new Date(rowAfterClaimAndRelease.next_run_start_at),
+ new Date(rowAfterClaimAndRelease.next_run_start_at!),
);
const row1NextStartAt = DateTime.fromJSDate(
- new Date(row1.next_run_start_at),
+ new Date(row1.next_run_start_at!),
);
const now = DateTime.now();
expect(
@@ -478,10 +527,10 @@ describe('TaskWorker', () => {
const row1 = (await knex(DB_TASKS_TABLE))[0];
const rowAfterClaimAndReleaseNextStartAt = DateTime.fromJSDate(
- new Date(rowAfterClaimAndRelease.next_run_start_at),
+ new Date(rowAfterClaimAndRelease.next_run_start_at!),
);
const row1NextStartAt = DateTime.fromJSDate(
- new Date(row1.next_run_start_at),
+ new Date(row1.next_run_start_at!),
);
const now = DateTime.now();
expect(
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts
index 48bc81efbf..32e7a5ab4e 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/TaskWorker.ts
@@ -21,8 +21,18 @@ import { Knex } from 'knex';
import { DateTime, Duration } from 'luxon';
import { v4 as uuid } from 'uuid';
import { DB_TASKS_TABLE, DbTasksRow } from '../database/tables';
-import { TaskSettingsV2, taskSettingsV2Schema } from './types';
-import { delegateAbortController, nowPlus, sleep } from './util';
+import {
+ TaskSettingsV2,
+ taskSettingsV2Schema,
+ TaskApiTasksResponse,
+} from './types';
+import {
+ delegateAbortController,
+ nowPlus,
+ sleep,
+ dbTime,
+ serializeError,
+} from './util';
import { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api';
const DEFAULT_WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 });
@@ -33,6 +43,10 @@ const DEFAULT_WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 });
* @private
*/
export class TaskWorker {
+ #workerState: TaskApiTasksResponse['workerState'] = {
+ status: 'idle',
+ };
+
constructor(
private readonly taskId: string,
private readonly fn: SchedulerServiceTaskFunction,
@@ -61,24 +75,17 @@ export class TaskWorker {
}
}
- let attemptNum = 1;
(async () => {
+ let attemptNum = 1;
for (;;) {
try {
- if (settings.initialDelayDuration) {
- await sleep(
- Duration.fromISO(settings.initialDelayDuration),
- options.signal,
- );
- }
+ await this.performInitialWait(settings, options.signal);
while (!options.signal.aborted) {
const runResult = await this.runOnce(options.signal);
-
if (runResult.result === 'abort') {
break;
}
-
await sleep(workCheckFrequency, options.signal);
}
@@ -96,6 +103,24 @@ export class TaskWorker {
})();
}
+ /**
+ * Does the once-at-startup initial wait, if configured.
+ */
+ private async performInitialWait(
+ settings: TaskSettingsV2,
+ signal: AbortSignal,
+ ): Promise {
+ if (settings.initialDelayDuration) {
+ this.#workerState = {
+ status: 'initial-wait',
+ };
+ await sleep(Duration.fromISO(settings.initialDelayDuration), signal);
+ }
+ this.#workerState = {
+ status: 'idle',
+ };
+ }
+
static async trigger(knex: Knex, taskId: string): Promise {
// check if task exists
const rows = await knex(DB_TASKS_TABLE)
@@ -116,6 +141,51 @@ export class TaskWorker {
}
}
+ static async taskStates(
+ knex: Knex,
+ ): Promise> {
+ const rows = await knex(DB_TASKS_TABLE);
+ return new Map(
+ rows.map(row => {
+ const startedAt = row.current_run_started_at
+ ? dbTime(row.current_run_started_at).toISO()!
+ : undefined;
+ const timesOutAt = row.current_run_expires_at
+ ? dbTime(row.current_run_expires_at).toISO()!
+ : undefined;
+ const startsAt = row.next_run_start_at
+ ? dbTime(row.next_run_start_at).toISO()!
+ : undefined;
+ const lastRunEndedAt = row.last_run_ended_at
+ ? dbTime(row.last_run_ended_at).toISO()!
+ : undefined;
+ const lastRunError = row.last_run_error_json || undefined;
+
+ return [
+ row.id,
+ startedAt
+ ? {
+ status: 'running',
+ startedAt,
+ timesOutAt,
+ lastRunEndedAt,
+ lastRunError,
+ }
+ : {
+ status: 'idle',
+ startsAt,
+ lastRunEndedAt,
+ lastRunError,
+ },
+ ];
+ }),
+ );
+ }
+
+ workerState(): TaskApiTasksResponse['workerState'] {
+ return this.#workerState;
+ }
+
/**
* Makes a single attempt at running the task to completion, if ready.
*
@@ -153,13 +223,19 @@ export class TaskWorker {
}, Duration.fromISO(taskSettings.timeoutAfterDuration).as('milliseconds'));
try {
+ this.#workerState = {
+ status: 'running',
+ };
await this.fn(taskAbortController.signal);
taskAbortController.abort(); // releases resources
} catch (e) {
this.logger.error(e);
- await this.tryReleaseTask(ticket, taskSettings);
+ await this.tryReleaseTask(ticket, taskSettings, e);
return { result: 'failed' };
} finally {
+ this.#workerState = {
+ status: 'idle',
+ };
clearTimeout(timeoutHandle);
}
@@ -321,6 +397,7 @@ export class TaskWorker {
async tryReleaseTask(
ticket: string,
settings: TaskSettingsV2,
+ error?: Error,
): Promise {
const isManual = settings?.cadence === 'manual';
const isDuration = settings?.cadence.startsWith('P');
@@ -366,6 +443,10 @@ export class TaskWorker {
current_run_ticket: this.knex.raw('null'),
current_run_started_at: this.knex.raw('null'),
current_run_expires_at: this.knex.raw('null'),
+ last_run_ended_at: this.knex.fn.now(),
+ last_run_error_json: error
+ ? serializeError(error)
+ : this.knex.raw('null'),
});
return rows === 1;
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts
index 173c989543..f691ef78a0 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/types.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import { JsonObject } from '@backstage/types';
import { CronTime } from 'cron';
import { Duration } from 'luxon';
import { z } from 'zod';
@@ -97,3 +98,39 @@ export const taskSettingsV2Schema = z.object({
* The properties that control a scheduled task (version 2).
*/
export type TaskSettingsV2 = z.infer;
+
+/**
+ * The shape of a task definition as returned by the service's REST API.
+ */
+export interface TaskApiTasksResponse {
+ taskId: string;
+ pluginId: string;
+ scope: 'global' | 'local';
+ settings: { version: number } & JsonObject;
+ taskState:
+ | {
+ status: 'running';
+ startedAt: string;
+ timesOutAt?: string;
+ lastRunError?: string;
+ lastRunEndedAt?: string;
+ }
+ | {
+ status: 'idle';
+ startsAt?: string;
+ lastRunError?: string;
+ lastRunEndedAt?: string;
+ }
+ | null;
+ workerState:
+ | {
+ status: 'initial-wait';
+ }
+ | {
+ status: 'idle';
+ }
+ | {
+ status: 'running';
+ }
+ | null;
+}
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts b/packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts
index c475a20b80..3e42d198df 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/lib/util.ts
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-import { InputError } from '@backstage/errors';
+import {
+ InputError,
+ serializeError as internalSerializeError,
+} from '@backstage/errors';
import { Knex } from 'knex';
import { DateTime, Duration } from 'luxon';
@@ -111,3 +114,11 @@ export function delegateAbortController(parent?: AbortSignal): AbortController {
return delegate;
}
+
+export function serializeError(error: Error): string {
+ return JSON.stringify(
+ internalSerializeError(error, {
+ includeStack: process.env.NODE_ENV === 'development',
+ }),
+ );
+}
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts
index 2d84f6ce87..22117d6e8c 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.test.ts
@@ -15,12 +15,17 @@
*/
import { coreServices } from '@backstage/backend-plugin-api';
-import { ServiceFactoryTester } from '@backstage/backend-test-utils';
+import {
+ mockServices,
+ ServiceFactoryTester,
+} from '@backstage/backend-test-utils';
import { schedulerServiceFactory } from './schedulerServiceFactory';
describe('schedulerFactory', () => {
it('creates sidecar database features', async () => {
- const tester = ServiceFactoryTester.from(schedulerServiceFactory);
+ const tester = ServiceFactoryTester.from(schedulerServiceFactory, {
+ dependencies: [mockServices.rootHttpRouter.factory()],
+ });
const scheduler = await tester.getSubject();
await scheduler.scheduleTask({
diff --git a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts
index 3668dbd17a..186e5f6940 100644
--- a/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts
+++ b/packages/backend-defaults/src/entrypoints/scheduler/schedulerServiceFactory.ts
@@ -35,8 +35,22 @@ export const schedulerServiceFactory = createServiceFactory({
database: coreServices.database,
logger: coreServices.logger,
rootLifecycle: coreServices.rootLifecycle,
+ httpRouter: coreServices.httpRouter,
+ pluginMetadata: coreServices.pluginMetadata,
},
- async factory({ database, logger, rootLifecycle }) {
- return DefaultSchedulerService.create({ database, logger, rootLifecycle });
+ async factory({
+ database,
+ logger,
+ rootLifecycle,
+ httpRouter,
+ pluginMetadata,
+ }) {
+ return DefaultSchedulerService.create({
+ database,
+ logger,
+ rootLifecycle,
+ httpRouter,
+ pluginMetadata,
+ });
},
});
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts
index 64feeca52b..246c07631b 100644
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts
+++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GerritUrlReader.test.ts
@@ -412,7 +412,7 @@ describe.skip('GerritUrlReader', () => {
gerritProcessor.readTree(shaTreeUrl, { etag: sha }),
).rejects.toThrow(NotModifiedError);
});
- it('can fetch files for a specifc sha.', async () => {
+ it('can fetch files for a specific sha.', async () => {
const response = await gerritProcessorWithGitiles.readTree(
`https://gerrit.com/gitiles/app/web/+/${sha}/`,
);
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts
index 6274ba8dc3..b7ccf165ad 100644
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts
+++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.test.ts
@@ -26,7 +26,7 @@ import { setupServer } from 'msw/node';
import path from 'path';
import { GitlabUrlReader } from './GitlabUrlReader';
import { DefaultReadTreeResponseFactory } from './tree';
-import { NotModifiedError, NotFoundError } from '@backstage/errors';
+import { NotFoundError, NotModifiedError } from '@backstage/errors';
import {
GitLabIntegration,
readGitLabIntegrationConfig,
@@ -248,6 +248,29 @@ describe('GitlabUrlReader', () => {
const content = await result.buffer();
expect(content.toString()).toBe('foo');
});
+
+ it('should return the file when using a user token', async () => {
+ worker.use(
+ rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => {
+ if (req.headers.get('private-token') !== 'gl-user-token') {
+ return res(
+ ctx.status(403),
+ ctx.json({ message: 'Not Authorized' }),
+ );
+ }
+ return res(ctx.status(200), ctx.json({ id: 12345 }));
+ }),
+ rest.get('*', (_req, res, ctx) => {
+ return res(ctx.status(200), ctx.body('foo'));
+ }),
+ );
+ const result = await reader.readUrl(
+ 'https://gitlab.com/user/project/-/blob/branch/my/path/to/file.yaml',
+ { token: 'gl-user-token' },
+ );
+ const content = await result.buffer();
+ expect(content.toString()).toBe('foo');
+ });
});
describe('readTree', () => {
@@ -276,102 +299,103 @@ describe('GitlabUrlReader', () => {
id: 'sha456def',
},
];
- });
- beforeEach(() => {
- worker.use(
- rest.get(
- 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/archive',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.set('Content-Type', 'application/zip'),
- ctx.set(
- 'content-disposition',
- 'attachment; filename="mock-main-sha123abc.zip"',
+ const projectNames = ['backstage%2Fmock', 'user%2Fproject'];
+ projectNames.forEach(projectName => {
+ worker.use(
+ rest.get(
+ `https://gitlab.com/api/v4/projects/${projectName}/repository/archive`,
+ (_, res, ctx) =>
+ res(
+ ctx.status(200),
+ ctx.set('Content-Type', 'application/zip'),
+ ctx.set(
+ 'content-disposition',
+ 'attachment; filename="mock-main-sha123abc.zip"',
+ ),
+ ctx.body(archiveBuffer),
),
- ctx.body(archiveBuffer),
- ),
- ),
- rest.get(
- 'https://gitlab.com/api/v4/projects/backstage%2Fmock',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.set('Content-Type', 'application/json'),
- ctx.json(projectGitlabApiResponse),
- ),
- ),
- rest.get(
- 'https://gitlab.com/api/v4/projects/backstage%2Fmock/repository/commits',
- (req, res, ctx) => {
- const refName = req.url.searchParams.get('ref_name');
- if (refName === 'main') {
- const filepath = req.url.searchParams.get('path');
- if (filepath === 'testFilepath') {
+ ),
+ rest.get(
+ `https://gitlab.com/api/v4/projects/${projectName}`,
+ (_, res, ctx) =>
+ res(
+ ctx.status(200),
+ ctx.set('Content-Type', 'application/json'),
+ ctx.json(projectGitlabApiResponse),
+ ),
+ ),
+ rest.get(
+ `https://gitlab.com/api/v4/projects/${projectName}/repository/commits`,
+ (req, res, ctx) => {
+ const refName = req.url.searchParams.get('ref_name');
+ if (refName === 'main') {
+ const filepath = req.url.searchParams.get('path');
+ if (filepath === 'testFilepath') {
+ return res(
+ ctx.status(200),
+ ctx.set('Content-Type', 'application/json'),
+ ctx.json(specificPathCommitsGitlabApiResponse),
+ );
+ }
return res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
- ctx.json(specificPathCommitsGitlabApiResponse),
+ ctx.json(commitsGitlabApiResponse),
);
}
- return res(
+ if (refName === 'branchDoesNotExist') {
+ return res(ctx.status(404));
+ }
+ return res();
+ },
+ ),
+ rest.get(
+ `https://gitlab.mycompany.com/api/v4/projects/${projectName}`,
+ (_, res, ctx) =>
+ res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
- ctx.json(commitsGitlabApiResponse),
- );
- }
- if (refName === 'branchDoesNotExist') {
- return res(ctx.status(404));
- }
- return res();
- },
- ),
- rest.get(
- 'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.set('Content-Type', 'application/json'),
- ctx.json(projectGitlabApiResponse),
- ),
- ),
- rest.get(
- 'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock/repository/commits',
- (req, res, ctx) => {
- const refName = req.url.searchParams.get('ref_name');
- if (refName === 'main') {
- const filepath = req.url.searchParams.get('path');
- if (filepath === 'testFilepath') {
+ ctx.json(projectGitlabApiResponse),
+ ),
+ ),
+ rest.get(
+ `https://gitlab.mycompany.com/api/v4/projects/${projectName}/repository/commits`,
+ (req, res, ctx) => {
+ const refName = req.url.searchParams.get('ref_name');
+ if (refName === 'main') {
+ const filepath = req.url.searchParams.get('path');
+ if (filepath === 'testFilepath') {
+ return res(
+ ctx.status(200),
+ ctx.set('Content-Type', 'application/json'),
+ ctx.json(specificPathCommitsGitlabApiResponse),
+ );
+ }
return res(
ctx.status(200),
ctx.set('Content-Type', 'application/json'),
- ctx.json(specificPathCommitsGitlabApiResponse),
+ ctx.json(commitsGitlabApiResponse),
);
}
- return res(
+ return res();
+ },
+ ),
+ rest.get(
+ `https://gitlab.mycompany.com/api/v4/projects/${projectName}/repository/archive`,
+ (_, res, ctx) =>
+ res(
ctx.status(200),
- ctx.set('Content-Type', 'application/json'),
- ctx.json(commitsGitlabApiResponse),
- );
- }
- return res();
- },
- ),
- rest.get(
- 'https://gitlab.mycompany.com/api/v4/projects/backstage%2Fmock/repository/archive',
- (_, res, ctx) =>
- res(
- ctx.status(200),
- ctx.set('Content-Type', 'application/zip'),
- ctx.set(
- 'content-disposition',
- 'attachment; filename="mock-main-sha123abc.zip"',
+ ctx.set('Content-Type', 'application/zip'),
+ ctx.set(
+ 'content-disposition',
+ 'attachment; filename="mock-main-sha123abc.zip"',
+ ),
+ ctx.body(archiveBuffer),
),
- ctx.body(archiveBuffer),
- ),
- ),
- );
+ ),
+ );
+ });
});
it('returns the wanted files from an archive', async () => {
@@ -543,6 +567,28 @@ describe('GitlabUrlReader', () => {
expect(mkDocsFile.toString()).toBe('site_name: Test\n');
expect(indexMarkdownFile.toString()).toBe('# Test\n');
});
+
+ it('should return the file when using a user token', async () => {
+ worker.use(
+ rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => {
+ if (req.headers.get('private-token') !== 'gl-user-token') {
+ return res(
+ ctx.status(403),
+ ctx.json({ message: 'Not Authorized' }),
+ );
+ }
+ return res(ctx.status(200), ctx.json({ id: 12345 }));
+ }),
+ );
+
+ const response = await gitlabProcessor.readTree(
+ 'https://gitlab.com/user/project/tree/main',
+ { token: 'gl-user-token' },
+ );
+
+ const files = await response.files();
+ expect(files.length).toBe(2);
+ });
});
describe('search', () => {
@@ -690,6 +736,15 @@ describe('GitlabUrlReader', () => {
'*/api/v4/projects/group%2Fsubgroup%2Fproject',
(_, res, ctx) => res(ctx.status(200), ctx.json({ id: 12345 })),
),
+ rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => {
+ if (req.headers.get('private-token') !== 'gl-user-token') {
+ return res(
+ ctx.status(403),
+ ctx.json({ message: 'Not Authorized' }),
+ );
+ }
+ return res(ctx.status(200), ctx.json({ id: 12345 }));
+ }),
);
});
it('should fall back to getGitLabFileFetchUrl for blob urls', async () => {
@@ -719,6 +774,16 @@ describe('GitlabUrlReader', () => {
'Failed converting /some/random/endpoint to a project id. Url path must include /blob/.',
);
});
+ it('should resolve the project path using a user token', async () => {
+ await expect(
+ (gitlabProcessor as any).getGitlabFetchUrl(
+ 'https://gitlab.com/user/project/-/blob/branch/my/path/to/file.yaml',
+ 'gl-user-token',
+ ),
+ ).resolves.toEqual(
+ 'https://gitlab.com/api/v4/projects/12345/repository/files/my%2Fpath%2Fto%2Ffile.yaml/raw?ref=branch',
+ );
+ });
});
describe('getGitlabArtifactFetchUrl', () => {
@@ -728,12 +793,19 @@ describe('GitlabUrlReader', () => {
'*/api/v4/projects/group%2Fsubgroup%2Fproject',
(_, res, ctx) => res(ctx.status(200), ctx.json({ id: 12345 })),
),
- );
- worker.use(
rest.get(
'*/api/v4/projects/groupA%2Fsubgroup%2Fproject',
(_, res, ctx) => res(ctx.status(404)),
),
+ rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => {
+ if (req.headers.get('private-token') !== 'gl-user-token') {
+ return res(
+ ctx.status(403),
+ ctx.json({ message: 'Not Authorized' }),
+ );
+ }
+ return res(ctx.status(200), ctx.json({ id: 12345 }));
+ }),
);
});
it('should reject urls that are not for the job artifacts API', async () => {
@@ -765,20 +837,61 @@ describe('GitlabUrlReader', () => {
),
).rejects.toThrow(/^Unable to translate GitLab artifact URL:/);
});
+ it('should resolve the project path using a user token', async () => {
+ await expect(
+ (gitlabProcessor as any).getGitlabArtifactFetchUrl(
+ new URL(
+ 'https://gitlab.com/user/project/-/jobs/artifacts/branch/raw/my/path/to/file.yaml?job=myJob',
+ ),
+ 'gl-user-token',
+ ),
+ ).resolves.toEqual(
+ new URL(
+ 'https://gitlab.com/api/v4/projects/12345/jobs/artifacts/branch/raw/my/path/to/file.yaml?job=myJob',
+ ),
+ );
+ });
});
describe('resolveProjectToId', () => {
- it('should resolve the project path to a valid project id', async () => {
+ beforeEach(() => {
worker.use(
- rest.get('*/api/v4/projects/some%2Fproject', (req, res, ctx) => {
+ rest.get('*/api/v4/projects/group%2Fproject', (req, res, ctx) => {
// the private-token header must be included on API calls
- expect(req.headers.get('private-token')).toBe('gl-dummy-token');
+ if (req.headers.get('private-token') !== 'gl-dummy-token') {
+ return res(
+ ctx.status(403),
+ ctx.json({ message: 'Not Authorized' }),
+ );
+ }
+ return res(ctx.status(200), ctx.json({ id: 12345 }));
+ }),
+ rest.get('*/api/v4/projects/user%2Fproject', (req, res, ctx) => {
+ // the private-token header must be included on API calls
+ if (req.headers.get('private-token') !== 'gl-user-token') {
+ return res(
+ ctx.status(403),
+ ctx.json({ message: 'Not Authorized' }),
+ );
+ }
return res(ctx.status(200), ctx.json({ id: 12345 }));
}),
);
+ });
+
+ it('should resolve the project path to a valid project id', async () => {
await expect(
(gitlabProcessor as any).resolveProjectToId(
- new URL('https://gitlab.com/some/project'),
+ new URL('https://gitlab.com/group/project'),
+ ),
+ ).resolves.toEqual(12345);
+ });
+
+ it('should resolve the project path to a valid project id using a user token', async () => {
+ await expect(
+ (gitlabProcessor as any).resolveProjectToId(
+ new URL('https://gitlab.com/user/project'),
+ 'gl-user-token',
),
).resolves.toEqual(12345);
});
diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts
index 4be6e65b96..3e5497a307 100644
--- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts
+++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GitlabUrlReader.ts
@@ -32,18 +32,18 @@ import {
NotModifiedError,
} from '@backstage/errors';
import {
- GitLabIntegration,
- ScmIntegrations,
getGitLabFileFetchUrl,
getGitLabIntegrationRelativePath,
getGitLabRequestOptions,
+ GitLabIntegration,
+ ScmIntegrations,
} from '@backstage/integration';
import parseGitUrl from 'git-url-parse';
import { trimEnd, trimStart } from 'lodash';
import { Minimatch } from 'minimatch';
import { Readable } from 'stream';
import { ReadUrlResponseFactory } from './ReadUrlResponseFactory';
-import { ReadTreeResponseFactory, ReaderFactory } from './types';
+import { ReaderFactory, ReadTreeResponseFactory } from './types';
import { parseLastModified } from './util';
/**
@@ -79,7 +79,7 @@ export class GitlabUrlReader implements UrlReaderService {
): Promise {
const { etag, lastModifiedAfter, signal, token } = options ?? {};
const isArtifact = url.includes('/-/jobs/artifacts/');
- const builtUrl = await this.getGitlabFetchUrl(url);
+ const builtUrl = await this.getGitlabFetchUrl(url, token);
let response: Response;
try {
@@ -328,23 +328,32 @@ export class GitlabUrlReader implements UrlReaderService {
return `gitlab{host=${host},authed=${Boolean(token)}}`;
}
- private async getGitlabFetchUrl(target: string): Promise {
+ private async getGitlabFetchUrl(
+ target: string,
+ token?: string,
+ ): Promise {
// If the target is for a job artifact then go down that path
const targetUrl = new URL(target);
if (targetUrl.pathname.includes('/-/jobs/artifacts/')) {
- return this.getGitlabArtifactFetchUrl(targetUrl).then(value =>
+ return this.getGitlabArtifactFetchUrl(targetUrl, token).then(value =>
value.toString(),
);
}
// Default to the old behavior of assuming the url is for a file
- return getGitLabFileFetchUrl(target, this.integration.config);
+ return getGitLabFileFetchUrl(target, {
+ ...this.integration.config,
+ ...(token && { token }),
+ });
}
// convert urls of the form:
// https://example.com///-/jobs/artifacts/[/raw/]?job=
// to urls of the form:
// https://example.com/api/v4/projects/:id/jobs/artifacts/:ref_name/raw/*artifact_path?job=
- private async getGitlabArtifactFetchUrl(target: URL): Promise {
+ private async getGitlabArtifactFetchUrl(
+ target: URL,
+ token?: string,
+ ): Promise {
if (!target.pathname.includes('/-/jobs/artifacts/')) {
throw new Error('Unable to process url as an GitLab artifact');
}
@@ -353,7 +362,7 @@ export class GitlabUrlReader implements UrlReaderService {
target.pathname.split('/-/jobs/artifacts/');
const projectPath = new URL(target);
projectPath.pathname = namespaceAndProject;
- const projectId = await this.resolveProjectToId(projectPath);
+ const projectId = await this.resolveProjectToId(projectPath, token);
const relativePath = getGitLabIntegrationRelativePath(
this.integration.config,
);
@@ -367,7 +376,10 @@ export class GitlabUrlReader implements UrlReaderService {
}
}
- private async resolveProjectToId(pathToProject: URL): Promise {
+ private async resolveProjectToId(
+ pathToProject: URL,
+ token?: string,
+ ): Promise {
let project = pathToProject.pathname;
// Check relative path exist and remove it if so
const relativePath = getGitLabIntegrationRelativePath(
@@ -382,7 +394,7 @@ export class GitlabUrlReader implements UrlReaderService {
`${
pathToProject.origin
}${relativePath}/api/v4/projects/${encodeURIComponent(project)}`,
- getGitLabRequestOptions(this.integration.config),
+ getGitLabRequestOptions(this.integration.config, token),
);
const data = await result.json();
if (!result.ok) {
diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md
index 08c1f51dc2..e81fb162c6 100644
--- a/packages/backend-dynamic-feature-service/CHANGELOG.md
+++ b/packages/backend-dynamic-feature-service/CHANGELOG.md
@@ -1,5 +1,61 @@
# @backstage/backend-dynamic-feature-service
+## 0.7.0-next.1
+
+### Patch Changes
+
+- 72d019d: Fixed various typos.
+ `FrontendRemoteResolver`'s misspelled `getAdditionaRemoteInfo` has been deprecated. Use the correct spelling `getAdditionalRemoteInfo` instead.
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@2.0.0-next.1
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/plugin-scaffolder-node@0.8.2-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/plugin-events-backend@0.5.2-next.1
+ - @backstage/plugin-search-backend-node@1.3.11-next.1
+ - @backstage/backend-openapi-utils@0.5.3-next.1
+ - @backstage/cli-common@0.1.15
+ - @backstage/cli-node@0.2.13
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-app-node@0.1.33-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## 0.7.0-next.0
+
+### Minor Changes
+
+- 10f693c: **BREAKING** Removed support for the legacy backend, please migrate to the new backend system
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/plugin-catalog-backend@1.32.2-next.0
+ - @backstage/plugin-scaffolder-node@0.8.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/cli-node@0.2.13
+ - @backstage/config-loader@1.10.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-events-backend@0.5.2-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
+ - @backstage/backend-openapi-utils@0.5.3-next.0
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-app-node@0.1.33-next.0
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-search-common@1.2.17
+
## 0.6.2
### Patch Changes
diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json
index 4e107eb554..e8c2a38398 100644
--- a/packages/backend-dynamic-feature-service/package.json
+++ b/packages/backend-dynamic-feature-service/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-dynamic-feature-service",
- "version": "0.6.2",
+ "version": "0.7.0-next.1",
"description": "Backstage dynamic feature service",
"backstage": {
"role": "node-library"
@@ -69,7 +69,6 @@
"@backstage/types": "workspace:^",
"@manypkg/get-packages": "^1.1.3",
"@module-federation/sdk": "^0.9.0",
- "@types/express": "^4.17.6",
"chokidar": "^3.5.3",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
@@ -83,6 +82,7 @@
"@backstage/cli": "workspace:^",
"@backstage/plugin-app-backend": "workspace:^",
"@backstage/repo-tools": "workspace:^",
+ "@types/express": "^4.17.6",
"triple-beam": "^1.4.1",
"wait-for-expect": "^3.0.2"
},
diff --git a/packages/backend-dynamic-feature-service/report.api.md b/packages/backend-dynamic-feature-service/report.api.md
index 37701abd18..5664b7b76c 100644
--- a/packages/backend-dynamic-feature-service/report.api.md
+++ b/packages/backend-dynamic-feature-service/report.api.md
@@ -5,31 +5,15 @@
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
import { BackstagePackageJson } from '@backstage/cli-node';
-import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
import { Config } from '@backstage/config';
import { ConfigSchema } from '@backstage/config-loader';
-import { DatabaseService } from '@backstage/backend-plugin-api';
-import { DiscoveryService } from '@backstage/backend-plugin-api';
-import { EventBroker } from '@backstage/plugin-events-node';
-import { EventsService } from '@backstage/plugin-events-node';
-import { HttpPostIngressOptions } from '@backstage/plugin-events-node';
-import { IdentityApi } from '@backstage/plugin-auth-node';
-import { IndexBuilder } from '@backstage/plugin-search-backend-node';
import { JsonObject } from '@backstage/types';
-import { Logger } from 'winston';
import { LoggerService } from '@backstage/backend-plugin-api';
import { PackagePlatform } from '@backstage/cli-node';
import { PackageRole } from '@backstage/cli-node';
-import { PermissionEvaluator } from '@backstage/plugin-permission-common';
-import { PermissionPolicy } from '@backstage/plugin-permission-node';
import { RootLoggerService } from '@backstage/backend-plugin-api';
-import { Router } from 'express';
-import { SchedulerService } from '@backstage/backend-plugin-api';
-import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api';
import { ServiceFactory } from '@backstage/backend-plugin-api';
import { ServiceRef } from '@backstage/backend-plugin-api';
-import { TemplateAction } from '@backstage/plugin-scaffolder-node';
-import { UrlReaderService } from '@backstage/backend-plugin-api';
import { WinstonLoggerOptions } from '@backstage/backend-defaults/rootLogger';
// @public (undocumented)
@@ -44,9 +28,7 @@ export interface BackendDynamicPlugin extends BaseDynamicPlugin {
}
// @public (undocumented)
-export type BackendDynamicPluginInstaller =
- | LegacyBackendPluginInstaller
- | NewBackendPluginInstaller;
+export type BackendDynamicPluginInstaller = NewBackendPluginInstaller;
// @public (undocumented)
export interface BackendPluginProvider {
@@ -248,6 +230,9 @@ export type FrontendRemoteResolver = {
getRemoteEntryType?: (
manifestContent: JsonObject,
) => 'manifest' | 'javascript';
+ getAdditionalRemoteInfo?: (
+ manifestContent: JsonObject,
+ ) => AdditionalRemoteInfo;
getAdditionaRemoteInfo?: (
manifestContent: JsonObject,
) => AdditionalRemoteInfo;
@@ -271,48 +256,6 @@ export function isBackendDynamicPluginInstaller(
obj: any,
): obj is BackendDynamicPluginInstaller;
-// @public @deprecated (undocumented)
-export interface LegacyBackendPluginInstaller {
- // (undocumented)
- catalog?(builder: CatalogBuilder, env: LegacyPluginEnvironment): void;
- // (undocumented)
- events?(env: LegacyPluginEnvironment): HttpPostIngressOptions[];
- // (undocumented)
- kind: 'legacy';
- // (undocumented)
- permissions?: {
- policy?: PermissionPolicy;
- };
- // (undocumented)
- router?: {
- pluginID: string;
- createPlugin(env: LegacyPluginEnvironment): Promise;
- };
- // (undocumented)
- scaffolder?(env: LegacyPluginEnvironment): TemplateAction[];
- // (undocumented)
- search?(
- indexBuilder: IndexBuilder,
- schedule: SchedulerServiceTaskRunner,
- env: LegacyPluginEnvironment,
- ): void;
-}
-
-// @public @deprecated (undocumented)
-export type LegacyPluginEnvironment = {
- logger: Logger;
- database: DatabaseService;
- config: Config;
- reader: UrlReaderService;
- discovery: DiscoveryService;
- permissions: PermissionEvaluator;
- scheduler: SchedulerService;
- identity: IdentityApi;
- eventBroker: EventBroker;
- events: EventsService;
- pluginProvider: BackendPluginProvider;
-};
-
// @public (undocumented)
export interface ModuleLoader {
// (undocumented)
diff --git a/packages/backend-dynamic-feature-service/src/features/__fixtures__/node_modules/@backstage/backend-plugin-api/package.json b/packages/backend-dynamic-feature-service/src/features/__fixtures__/node_modules/@backstage/backend-plugin-api/package.json
index 7b2e68e264..47560e9643 100644
--- a/packages/backend-dynamic-feature-service/src/features/__fixtures__/node_modules/@backstage/backend-plugin-api/package.json
+++ b/packages/backend-dynamic-feature-service/src/features/__fixtures__/node_modules/@backstage/backend-plugin-api/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/backend-plugin-api",
"version": "0.0.0",
- "description": "dummy backstage package that should be skipped by the ComonJSLoduleLoader",
+ "description": "dummy backstage package that should be skipped by the CommonJSModuleLoader",
"main": "index.js",
"dependencies": {}
}
diff --git a/packages/backend-dynamic-feature-service/src/features/features.test.ts b/packages/backend-dynamic-feature-service/src/features/features.test.ts
index eb2901340c..99495cf1b0 100644
--- a/packages/backend-dynamic-feature-service/src/features/features.test.ts
+++ b/packages/backend-dynamic-feature-service/src/features/features.test.ts
@@ -558,7 +558,7 @@ Require stack:
for: () => ({
assetsPathFromPackage: 'dist-alternate',
getRemoteEntryType: () => 'javascript',
- getAdditionaRemoteInfo: manifest => ({
+ getAdditionalRemoteInfo: manifest => ({
type: (manifest as any).metaData.remoteEntry.type,
}),
overrideExposedModules: exposedModules =>
diff --git a/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts b/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts
index 50a6028422..28203a2019 100644
--- a/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts
+++ b/packages/backend-dynamic-feature-service/src/loader/CommonJSModuleLoader.ts
@@ -68,7 +68,7 @@ export class CommonJSModuleLoader implements ModuleLoader {
return filtered;
};
- // The whole piece of code below is a way to accomodate the limitations of
+ // The whole piece of code below is a way to accommodate the limitations of
// the current `resolvePackagePath` implementation, which cannot be provided
// some custom locations where it should find the assets of some given packages.
//
@@ -104,7 +104,7 @@ export class CommonJSModuleLoader implements ModuleLoader {
mod?.path &&
!dynamicPluginsPaths.some(p => mod.path.startsWith(p));
- // If not, we don't need the dedicated specfic case below.
+ // If not, we don't need the dedicated specific case below.
if (!resolvingPackageJsonFromBackstageApplication) {
throw errorToThrow;
}
@@ -133,7 +133,7 @@ export class CommonJSModuleLoader implements ModuleLoader {
}
// If a custom resolution is provided, use it.
- // This allows accomodating alternate ways to package dynamic plugins:
+ // This allows accommodating alternate ways to package dynamic plugins:
// static plugin package wrapped inside a distinct dynamic plugin package for example.
if (this.options.customResolveDynamicPackage) {
const resolvedPath = this.options.customResolveDynamicPackage(
diff --git a/packages/backend-dynamic-feature-service/src/manager/index.ts b/packages/backend-dynamic-feature-service/src/manager/index.ts
index 391ea46249..53c86d63c8 100644
--- a/packages/backend-dynamic-feature-service/src/manager/index.ts
+++ b/packages/backend-dynamic-feature-service/src/manager/index.ts
@@ -23,8 +23,6 @@ export type {
BackendDynamicPlugin,
BackendDynamicPluginInstaller,
NewBackendPluginInstaller,
- LegacyBackendPluginInstaller,
- LegacyPluginEnvironment,
DynamicPluginProvider,
FrontendPluginProvider,
BackendPluginProvider,
diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts
index d7133a804a..8d8fd13151 100644
--- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts
+++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.test.ts
@@ -30,13 +30,10 @@ import {
BackendDynamicPlugin,
BaseDynamicPlugin,
DynamicPlugin,
- LegacyBackendPluginInstaller,
NewBackendPluginInstaller,
- LegacyPluginEnvironment,
} from './types';
import { ScannedPluginManifest, ScannedPluginPackage } from '../scanner/types';
import { randomUUID } from 'crypto';
-import { TemplateAction } from '@backstage/plugin-scaffolder-node';
import { createSpecializedBackend } from '@backstage/backend-app-api';
import { ConfigSources } from '@backstage/config-loader';
import { Logs, MockedLogger, LogContent } from '../__testUtils__/testUtils';
@@ -552,49 +549,6 @@ describe('backend-dynamic-feature-service', () => {
]);
},
},
- {
- name: 'should successfully load a legacy backend plugin',
- packageManifest: {
- name: 'backend-dynamic-plugin-test',
- version: '0.0.0',
- backstage: {
- role: 'backend-plugin',
- },
- main: 'dist/index.cjs.js',
- },
- indexFile: {
- relativePath: ['dist', 'index.cjs.js'],
- content:
- 'exports.dynamicPluginInstaller={ kind: "legacy", scaffolder: (env)=>[] }',
- },
- expectedLogs(location) {
- return {
- infos: [
- {
- message: `loaded dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`,
- },
- ],
- };
- },
- checkLoadedPlugins(plugins) {
- expect(plugins).toMatchObject([
- {
- name: 'backend-dynamic-plugin-test',
- version: '0.0.0',
- role: 'backend-plugin',
- platform: 'node',
- installer: {
- kind: 'legacy',
- },
- },
- ]);
- const installer = (plugins[0] as BackendDynamicPlugin)
- .installer as LegacyBackendPluginInstaller;
- expect(installer.scaffolder!({} as LegacyPluginEnvironment)).toEqual<
- TemplateAction[]
- >([]);
- },
- },
{
name: 'should successfully load a frontend plugin',
packageManifest: {
diff --git a/packages/backend-dynamic-feature-service/src/manager/types.ts b/packages/backend-dynamic-feature-service/src/manager/types.ts
index 165f13003a..6bbadbf992 100644
--- a/packages/backend-dynamic-feature-service/src/manager/types.ts
+++ b/packages/backend-dynamic-feature-service/src/manager/types.ts
@@ -14,58 +14,10 @@
* limitations under the License.
*/
-import { Logger } from 'winston';
-import { Config } from '@backstage/config';
-import { Router } from 'express';
-import { IdentityApi } from '@backstage/plugin-auth-node';
-import { PermissionEvaluator } from '@backstage/plugin-permission-common';
-import {
- EventBroker,
- EventsService,
- HttpPostIngressOptions,
-} from '@backstage/plugin-events-node';
-
-import {
- BackendFeature,
- UrlReaderService,
- SchedulerService,
- SchedulerServiceTaskRunner,
- DatabaseService,
- DiscoveryService,
-} from '@backstage/backend-plugin-api';
+import { BackendFeature } from '@backstage/backend-plugin-api';
import { PackagePlatform, PackageRole } from '@backstage/cli-node';
-import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
-import { TemplateAction } from '@backstage/plugin-scaffolder-node';
-import { IndexBuilder } from '@backstage/plugin-search-backend-node';
-import { PermissionPolicy } from '@backstage/plugin-permission-node';
import { ScannedPluginPackage } from '../scanner';
-/**
- * @public
- *
- * @deprecated
- *
- * Support for the legacy backend system will be removed in the future.
- *
- * When adding a legacy plugin installer entrypoint in your plugin,
- * you should always take the opportunity to also implement support
- * for the new backend system if not already done.
- *
- */
-export type LegacyPluginEnvironment = {
- logger: Logger;
- database: DatabaseService;
- config: Config;
- reader: UrlReaderService;
- discovery: DiscoveryService;
- permissions: PermissionEvaluator;
- scheduler: SchedulerService;
- identity: IdentityApi;
- eventBroker: EventBroker;
- events: EventsService;
- pluginProvider: BackendPluginProvider;
-};
-
/**
* @public
*/
@@ -126,9 +78,7 @@ export interface BackendDynamicPlugin extends BaseDynamicPlugin {
/**
* @public
*/
-export type BackendDynamicPluginInstaller =
- | LegacyBackendPluginInstaller
- | NewBackendPluginInstaller;
+export type BackendDynamicPluginInstaller = NewBackendPluginInstaller;
/**
* @public
@@ -139,38 +89,6 @@ export interface NewBackendPluginInstaller {
install(): BackendFeature | BackendFeature[];
}
-/**
- * @public
- * @deprecated
- *
- * Support for the legacy backend system will be removed in the future.
- *
- * When adding a legacy plugin installer entrypoint in your plugin,
- * you should always take the opportunity to also implement support
- * for the new backend system if not already done.
- *
- */
-export interface LegacyBackendPluginInstaller {
- kind: 'legacy';
-
- router?: {
- pluginID: string;
- createPlugin(env: LegacyPluginEnvironment): Promise;
- };
-
- catalog?(builder: CatalogBuilder, env: LegacyPluginEnvironment): void;
- scaffolder?(env: LegacyPluginEnvironment): TemplateAction[];
- search?(
- indexBuilder: IndexBuilder,
- schedule: SchedulerServiceTaskRunner,
- env: LegacyPluginEnvironment,
- ): void;
- events?(env: LegacyPluginEnvironment): HttpPostIngressOptions[];
- permissions?: {
- policy?: PermissionPolicy;
- };
-}
-
/**
* @public
*/
diff --git a/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts b/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts
index fa7e574bf3..f1a245b1ac 100644
--- a/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts
+++ b/packages/backend-dynamic-feature-service/src/server/frontendRemotesServer.ts
@@ -37,7 +37,7 @@ export type AdditionalRemoteInfo = Omit;
* */
export type FrontendRemoteResolver = {
/**
- * Relative path to the module federation assets folder from thr root folder of the plugin package.
+ * Relative path to the module federation assets folder from the root folder of the plugin package.
* Default value is `dist`.
*/
assetsPathFromPackage?: string;
@@ -59,6 +59,14 @@ export type FrontendRemoteResolver = {
/**
* Additional module federation fields, which might be required if the remote entry type is 'javascript'.
*/
+ getAdditionalRemoteInfo?: (
+ manifestContent: JsonObject,
+ ) => AdditionalRemoteInfo;
+
+ /**
+ * Additional module federation fields, which might be required if the remote entry type is 'javascript'.
+ * @deprecated Use `getAdditionalRemoteInfo` instead.
+ */
getAdditionaRemoteInfo?: (
manifestContent: JsonObject,
) => AdditionalRemoteInfo;
diff --git a/packages/backend-dynamic-feature-service/src/server/router.ts b/packages/backend-dynamic-feature-service/src/server/router.ts
index 4233147069..61c97797e7 100644
--- a/packages/backend-dynamic-feature-service/src/server/router.ts
+++ b/packages/backend-dynamic-feature-service/src/server/router.ts
@@ -121,8 +121,10 @@ export async function createRouter({
}
const getAdditionalRemoteInfo =
+ providedResolver?.getAdditionalRemoteInfo ??
providedResolver?.getAdditionaRemoteInfo ??
- defaultResolver.getAdditionaRemoteInfo;
+ defaultResolver.getAdditionalRemoteInfo ??
+ defaultResolver?.getAdditionaRemoteInfo;
const getRemoteEntryType =
providedResolver?.getRemoteEntryType ??
defaultResolver.getRemoteEntryType;
diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md
index f1342d1573..f24aa49ae8 100644
--- a/packages/backend-openapi-utils/CHANGELOG.md
+++ b/packages/backend-openapi-utils/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/backend-openapi-utils
+## 0.5.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## 0.5.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 0.5.2
### Patch Changes
diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json
index 5f22a06b8e..f9ea9294bb 100644
--- a/packages/backend-openapi-utils/package.json
+++ b/packages/backend-openapi-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-openapi-utils",
- "version": "0.5.2",
+ "version": "0.5.3-next.1",
"description": "OpenAPI typescript support.",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md
index 626c914d1f..8314ec21b3 100644
--- a/packages/backend-plugin-api/CHANGELOG.md
+++ b/packages/backend-plugin-api/CHANGELOG.md
@@ -1,5 +1,34 @@
# @backstage/backend-plugin-api
+## 1.3.1-next.1
+
+### Patch Changes
+
+- acea1d4: update documentation
+- 72d019d: Removed various typos
+- d385854: Minor doc comment update
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## 1.3.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-common@0.8.4
+
## 1.3.0
### Minor Changes
diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json
index 5799d78933..906b6ebeb4 100644
--- a/packages/backend-plugin-api/package.json
+++ b/packages/backend-plugin-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-plugin-api",
- "version": "1.3.0",
+ "version": "1.3.1-next.1",
"description": "Core API used by Backstage backend plugins",
"backstage": {
"role": "node-library"
diff --git a/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts b/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts
index 152bb205de..28eb214f70 100644
--- a/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts
+++ b/packages/backend-plugin-api/src/services/definitions/HttpAuthService.ts
@@ -93,7 +93,7 @@ export interface HttpAuthService {
*
* Normally you do not have to specify this option, because the default
* behavior is to extract the credentials from the request that
- * corresponded to the given respnse.
+ * corresponded to the given response.
*/
credentials?: BackstageCredentials;
},
diff --git a/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts b/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts
index 24219228df..b1a0e17df8 100644
--- a/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts
+++ b/packages/backend-plugin-api/src/services/definitions/SchedulerService.ts
@@ -82,13 +82,12 @@ export interface SchedulerServiceTaskScheduleDefinition {
* Overview:
*
* ```
- * ┌────────────── second (optional)
- * │ ┌──────────── minute
- * │ │ ┌────────── hour
- * │ │ │ ┌──────── day of month
- * │ │ │ │ ┌────── month
- * │ │ │ │ │ ┌──── day of week
- * │ │ │ │ │ │
+ * ┌────────────── second (0-60, optional)
+ * │ ┌──────────── minute (0-59)
+ * │ │ ┌────────── hour (0-23)
+ * │ │ │ ┌──────── day of month (1-31)
+ * │ │ │ │ ┌────── month (1-12)
+ * │ │ │ │ │ ┌──── day of week (0-6, 0 is Sunday)
* │ │ │ │ │ │
* * * * * * *
* ```
@@ -180,13 +179,12 @@ export interface SchedulerServiceTaskScheduleDefinitionConfig {
* Overview:
*
* ```
- * ┌────────────── second (optional)
- * │ ┌──────────── minute
- * │ │ ┌────────── hour
- * │ │ │ ┌──────── day of month
- * │ │ │ │ ┌────── month
- * │ │ │ │ │ ┌──── day of week
- * │ │ │ │ │ │
+ * ┌────────────── second (0-60, optional)
+ * │ ┌──────────── minute (0-59)
+ * │ │ ┌────────── hour (0-23)
+ * │ │ │ ┌──────── day of month (1-31)
+ * │ │ │ │ ┌────── month (1-12)
+ * │ │ │ │ │ ┌──── day of week (0-6, 0 is Sunday)
* │ │ │ │ │ │
* * * * * * *
* ```
diff --git a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts
index 9b327cfbf0..e462979707 100644
--- a/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts
+++ b/packages/backend-plugin-api/src/services/definitions/UrlReaderService.ts
@@ -110,7 +110,7 @@ export type UrlReaderServiceReadUrlOptions = {
* @remarks
*
* By default all URL Readers will use the integrations config which is supplied
- * when creating the Readers. Sometimes it might be desireable to use the already
+ * when creating the Readers. Sometimes it might be desirable to use the already
* created URLReaders but with a different token, maybe that's supplied by the user
* at runtime.
*/
@@ -209,7 +209,7 @@ export type UrlReaderServiceReadTreeOptions = {
* @remarks
*
* By default all URL Readers will use the integrations config which is supplied
- * when creating the Readers. Sometimes it might be desireable to use the already
+ * when creating the Readers. Sometimes it might be desirable to use the already
* created URLReaders but with a different token, maybe that's supplied by the user
* at runtime.
*/
@@ -323,7 +323,7 @@ export type UrlReaderServiceSearchOptions = {
* @remarks
*
* By default all URL Readers will use the integrations config which is supplied
- * when creating the Readers. Sometimes it might be desireable to use the already
+ * when creating the Readers. Sometimes it might be desirable to use the already
* created URLReaders but with a different token, maybe that's supplied by the user
* at runtime.
*/
diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md
index 9f7b10e5f9..7ea0d20986 100644
--- a/packages/backend-test-utils/CHANGELOG.md
+++ b/packages/backend-test-utils/CHANGELOG.md
@@ -1,5 +1,41 @@
# @backstage/backend-test-utils
+## 1.5.0-next.1
+
+### Minor Changes
+
+- c6bc67d: Added Valkey support alongside Redis in backend-defaults cache clients, using the new Keyv Valkey package. Also extended backend-test-utils to support Valkey in tests.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/backend-app-api@1.2.3-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 1.5.0-next.0
+
+### Minor Changes
+
+- b3832d1: Add a functional `mockServices.events()`
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/backend-app-api@1.2.3-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 1.4.0
### Minor Changes
diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json
index 03a2dcc598..799db1e589 100644
--- a/packages/backend-test-utils/package.json
+++ b/packages/backend-test-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-test-utils",
- "version": "1.4.0",
+ "version": "1.5.0-next.1",
"description": "Test helpers library for Backstage backends",
"backstage": {
"role": "node-library"
@@ -55,6 +55,7 @@
"@backstage/types": "workspace:^",
"@keyv/memcache": "^2.0.1",
"@keyv/redis": "^4.0.1",
+ "@keyv/valkey": "^1.0.1",
"@types/express": "^4.17.6",
"@types/express-serve-static-core": "^4.17.5",
"@types/keyv": "^4.2.0",
diff --git a/packages/backend-test-utils/report.api.md b/packages/backend-test-utils/report.api.md
index 5497497068..7edcf6e1b7 100644
--- a/packages/backend-test-utils/report.api.md
+++ b/packages/backend-test-utils/report.api.md
@@ -222,14 +222,17 @@ export namespace mockServices {
partialImpl?: Partial | undefined,
) => ServiceMock;
}
+ export function events(): EventsService;
// (undocumented)
export namespace events {
- const // (undocumented)
- factory: () => ServiceFactory;
- const // (undocumented)
- mock: (
- partialImpl?: Partial | undefined,
- ) => ServiceMock;
+ const factory: () => ServiceFactory<
+ EventsService,
+ 'plugin',
+ 'singleton' | 'multiton'
+ >;
+ const mock: (
+ partialImpl?: Partial | undefined,
+ ) => ServiceMock;
}
export function httpAuth(options?: {
pluginId?: string;
@@ -467,7 +470,7 @@ export interface TestBackendOptions {
}
// @public
-export type TestCacheId = 'MEMORY' | 'REDIS_7' | 'MEMCACHED_1';
+export type TestCacheId = 'MEMORY' | 'REDIS_7' | 'VALKEY_8' | 'MEMCACHED_1';
// @public
export class TestCaches {
diff --git a/packages/backend-test-utils/src/cache/TestCaches.ts b/packages/backend-test-utils/src/cache/TestCaches.ts
index e9ba0e5c4f..d4553697bf 100644
--- a/packages/backend-test-utils/src/cache/TestCaches.ts
+++ b/packages/backend-test-utils/src/cache/TestCaches.ts
@@ -19,6 +19,7 @@ import { isDockerDisabledForTests } from '../util/isDockerDisabledForTests';
import { connectToExternalMemcache, startMemcachedContainer } from './memcache';
import { connectToExternalRedis, startRedisContainer } from './redis';
import { Instance, TestCacheId, TestCacheProperties, allCaches } from './types';
+import { connectToExternalValkey, startValkeyContainer } from './valkey';
/**
* Encapsulates the creation of ephemeral test cache instances for use inside
@@ -156,6 +157,8 @@ export class TestCaches {
return this.initMemcached(properties);
case 'redis':
return this.initRedis(properties);
+ case 'valkey':
+ return this.initValkey(properties);
case 'memory':
return {
store: 'memory',
@@ -196,6 +199,19 @@ export class TestCaches {
return await startRedisContainer(properties.dockerImageName!);
}
+ private async initValkey(properties: TestCacheProperties): Promise {
+ // Use the connection string if provided
+ const envVarName = properties.connectionStringEnvironmentVariableName;
+ if (envVarName) {
+ const connectionString = process.env[envVarName];
+ if (connectionString) {
+ return connectToExternalValkey(connectionString);
+ }
+ }
+
+ return await startValkeyContainer(properties.dockerImageName!);
+ }
+
private async shutdown() {
const instances = [...this.instanceById.values()];
this.instanceById.clear();
diff --git a/packages/backend-test-utils/src/cache/types.ts b/packages/backend-test-utils/src/cache/types.ts
index 1aea522042..4781ed42d2 100644
--- a/packages/backend-test-utils/src/cache/types.ts
+++ b/packages/backend-test-utils/src/cache/types.ts
@@ -22,7 +22,7 @@ import { getDockerImageForName } from '../util/getDockerImageForName';
*
* @public
*/
-export type TestCacheId = 'MEMORY' | 'REDIS_7' | 'MEMCACHED_1';
+export type TestCacheId = 'MEMORY' | 'REDIS_7' | 'VALKEY_8' | 'MEMCACHED_1';
export type TestCacheProperties = {
name: string;
@@ -58,4 +58,11 @@ export const allCaches: Record =
name: 'In-memory',
store: 'memory',
},
+ VALKEY_8: {
+ name: 'Valkey 8.x',
+ store: 'valkey',
+ dockerImageName: getDockerImageForName('valkey/valkey:8'),
+ connectionStringEnvironmentVariableName:
+ 'BACKSTAGE_TEST_CACHE_VALKEY8_CONNECTION_STRING',
+ },
});
diff --git a/packages/backend-test-utils/src/cache/valkey.test.ts b/packages/backend-test-utils/src/cache/valkey.test.ts
new file mode 100644
index 0000000000..d706458659
--- /dev/null
+++ b/packages/backend-test-utils/src/cache/valkey.test.ts
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2024 The 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 { isDockerDisabledForTests } from '../util/isDockerDisabledForTests';
+import { v4 as uuid } from 'uuid';
+import { startValkeyContainer } from './valkey';
+
+const itIfDocker = isDockerDisabledForTests() ? it.skip : it;
+
+jest.setTimeout(60_000);
+
+describe('startValkeyContainer', () => {
+ itIfDocker('successfully launches the container', async () => {
+ const { stop, keyv } = await startValkeyContainer('valkey/valkey:8');
+ const value = uuid();
+ await keyv.set('test', value);
+ // eslint-disable-next-line jest/no-standalone-expect
+ await expect(keyv.get('test')).resolves.toBe(value);
+ await stop();
+ });
+});
diff --git a/packages/backend-test-utils/src/cache/valkey.ts b/packages/backend-test-utils/src/cache/valkey.ts
new file mode 100644
index 0000000000..05bb161c94
--- /dev/null
+++ b/packages/backend-test-utils/src/cache/valkey.ts
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2024 The 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 Keyv from 'keyv';
+import KeyvValkey from '@keyv/valkey';
+import { v4 as uuid } from 'uuid';
+import { Instance } from './types';
+
+async function attemptValkeyConnection(connection: string): Promise {
+ const startTime = Date.now();
+
+ for (;;) {
+ try {
+ const store = new KeyvValkey(connection);
+ const keyv = new Keyv({ store });
+ const value = uuid();
+ await keyv.set('test', value);
+ if ((await keyv.get('test')) === value) {
+ return keyv;
+ }
+ } catch (e) {
+ if (Date.now() - startTime > 30_000) {
+ throw new Error(
+ `Timed out waiting for valkey to be ready for connections, ${e}`,
+ );
+ }
+ }
+
+ await new Promise(resolve => setTimeout(resolve, 100));
+ }
+}
+
+export async function connectToExternalValkey(
+ connection: string,
+): Promise {
+ const keyv = await attemptValkeyConnection(connection);
+ return {
+ store: 'valkey',
+ connection,
+ keyv,
+ stop: async () => await keyv.disconnect(),
+ };
+}
+
+export async function startValkeyContainer(image: string): Promise {
+ // Lazy-load to avoid side-effect of importing testcontainers
+ const { GenericContainer } =
+ require('testcontainers') as typeof import('testcontainers');
+
+ const container = await new GenericContainer(image)
+ .withExposedPorts(6379)
+ .start();
+
+ const host = container.getHost();
+ const port = container.getMappedPort(6379);
+ const connection = `redis://${host}:${port}`;
+
+ const keyv = await attemptValkeyConnection(connection);
+
+ return {
+ store: 'valkey',
+ connection,
+ keyv,
+ stop: async () => {
+ await keyv.disconnect();
+ await container.stop({ timeout: 10_000 });
+ },
+ };
+}
diff --git a/packages/backend-test-utils/src/next/services/MockEventsService.test.ts b/packages/backend-test-utils/src/next/services/MockEventsService.test.ts
new file mode 100644
index 0000000000..3cbadd69f5
--- /dev/null
+++ b/packages/backend-test-utils/src/next/services/MockEventsService.test.ts
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2025 The 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 { MockEventsService } from './MockEventsService';
+
+describe('MockEventsService', () => {
+ it('notifies subscribers', async () => {
+ const service = new MockEventsService();
+
+ const listener1 = jest.fn();
+ const listener2 = jest.fn();
+
+ await service.subscribe({
+ id: 'a',
+ topics: ['topic1'],
+ onEvent: listener1,
+ });
+ await service.subscribe({
+ id: 'b',
+ topics: ['topic1', 'topic2'],
+ onEvent: listener2,
+ });
+
+ expect(listener1).not.toHaveBeenCalled();
+ expect(listener2).not.toHaveBeenCalled();
+
+ await service.publish({
+ topic: 'topic1',
+ eventPayload: { payload: 1 },
+ });
+
+ expect(listener1).toHaveBeenCalledTimes(1);
+ expect(listener2).toHaveBeenCalledTimes(1);
+
+ await service.publish({
+ topic: 'topic2',
+ eventPayload: { payload: 1 },
+ });
+
+ expect(listener1).toHaveBeenCalledTimes(1);
+ expect(listener2).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/packages/backend-test-utils/src/next/services/MockEventsService.ts b/packages/backend-test-utils/src/next/services/MockEventsService.ts
new file mode 100644
index 0000000000..abe29e3a8f
--- /dev/null
+++ b/packages/backend-test-utils/src/next/services/MockEventsService.ts
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2025 The 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 {
+ EventParams,
+ EventsService,
+ EventsServiceSubscribeOptions,
+} from '@backstage/plugin-events-node';
+
+export class MockEventsService implements EventsService {
+ #subscribers: EventsServiceSubscribeOptions[];
+
+ constructor() {
+ this.#subscribers = [];
+ }
+
+ async publish(params: EventParams): Promise {
+ for (const subscriber of this.#subscribers) {
+ if (subscriber.topics.includes(params.topic)) {
+ await subscriber.onEvent(params);
+ }
+ }
+ }
+
+ async subscribe(options: EventsServiceSubscribeOptions): Promise {
+ this.#subscribers.push(options);
+ }
+}
diff --git a/packages/backend-test-utils/src/next/services/mockServices.ts b/packages/backend-test-utils/src/next/services/mockServices.ts
index baeae53a77..f68f1337b9 100644
--- a/packages/backend-test-utils/src/next/services/mockServices.ts
+++ b/packages/backend-test-utils/src/next/services/mockServices.ts
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+import { auditorServiceFactory } from '@backstage/backend-defaults/auditor';
import { cacheServiceFactory } from '@backstage/backend-defaults/cache';
import { databaseServiceFactory } from '@backstage/backend-defaults/database';
import { HostDiscovery } from '@backstage/backend-defaults/discovery';
@@ -43,10 +44,7 @@ import {
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { ConfigReader } from '@backstage/config';
-import {
- eventsServiceFactory,
- eventsServiceRef,
-} from '@backstage/plugin-events-node';
+import { EventsService, eventsServiceRef } from '@backstage/plugin-events-node';
import { JsonObject } from '@backstage/types';
import { Knex } from 'knex';
import { MockAuthService } from './MockAuthService';
@@ -54,7 +52,7 @@ import { MockHttpAuthService } from './MockHttpAuthService';
import { MockRootLoggerService } from './MockRootLoggerService';
import { MockUserInfoService } from './MockUserInfoService';
import { mockCredentials } from './mockCredentials';
-import { auditorServiceFactory } from '@backstage/backend-defaults/auditor';
+import { MockEventsService } from './MockEventsService';
/** @internal */
function createLoggerMock() {
@@ -521,8 +519,24 @@ export namespace mockServices {
}));
}
+ /**
+ * Creates a functional mock implementation of the
+ * {@link @backstage/backend-events-node#eventsServiceRef}.
+ */
+ export function events(): EventsService {
+ return new MockEventsService();
+ }
export namespace events {
- export const factory = () => eventsServiceFactory;
+ /**
+ * Creates a functional mock factory for the
+ * {@link @backstage/backend-events-node#eventsServiceRef}.
+ */
+ export const factory = simpleFactoryWithOptions(eventsServiceRef, events);
+ /**
+ * Creates a mock of the
+ * {@link @backstage/backend-events-node#eventsServiceRef}, optionally
+ * with some given method implementations.
+ */
export const mock = simpleMock(eventsServiceRef, () => ({
publish: jest.fn(),
subscribe: jest.fn(),
diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md
index 04020dd9ad..18f850b5da 100644
--- a/packages/backend/CHANGELOG.md
+++ b/packages/backend/CHANGELOG.md
@@ -1,5 +1,83 @@
# example-backend
+## 0.0.38-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.0-next.1
+ - @backstage/plugin-catalog-backend@2.0.0-next.1
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.1
+ - @backstage/plugin-kubernetes-backend@0.19.6-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/plugin-scaffolder-backend@1.33.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-backend@0.7.0-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.1
+ - @backstage/plugin-techdocs-backend@2.0.2-next.1
+ - @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.1
+ - @backstage/plugin-notifications-backend@0.5.6-next.1
+ - @backstage/plugin-signals-backend@0.3.4-next.1
+ - @backstage/plugin-app-backend@0.5.2-next.1
+ - @backstage/plugin-devtools-backend@0.5.5-next.1
+ - @backstage/plugin-events-backend@0.5.2-next.1
+ - @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.1
+ - @backstage/plugin-proxy-backend@0.6.2-next.1
+ - @backstage/plugin-search-backend@2.0.2-next.1
+ - @backstage/plugin-search-backend-node@1.3.11-next.1
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.1
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.1
+ - @backstage/plugin-search-backend-module-catalog@0.3.4-next.1
+ - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.1
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.1
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.1
+ - @backstage/plugin-search-backend-module-explore@0.3.2-next.1
+
+## 0.0.38-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.0
+ - @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.0
+ - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.0
+ - @backstage/plugin-auth-backend@0.25.0-next.0
+ - @backstage/plugin-scaffolder-backend@1.32.2-next.0
+ - @backstage/plugin-permission-backend@0.7.0-next.0
+ - @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.0
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/plugin-catalog-backend@1.32.2-next.0
+ - @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.0
+ - @backstage/plugin-techdocs-backend@2.0.2-next.0
+ - @backstage/plugin-notifications-backend@0.5.6-next.0
+ - @backstage/plugin-signals-backend@0.3.4-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-app-backend@0.5.2-next.0
+ - @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.0
+ - @backstage/plugin-devtools-backend@0.5.5-next.0
+ - @backstage/plugin-events-backend@0.5.2-next.0
+ - @backstage/plugin-kubernetes-backend@0.19.6-next.0
+ - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/plugin-proxy-backend@0.6.2-next.0
+ - @backstage/plugin-search-backend@2.0.2-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.3.4-next.0
+ - @backstage/plugin-search-backend-module-explore@0.3.2-next.0
+ - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.0
+ - @backstage/plugin-search-backend-node@1.3.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.0
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.0
+
## 0.0.37
### Patch Changes
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 99c48c4793..fa51d98d4a 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -1,6 +1,6 @@
{
"name": "example-backend",
- "version": "0.0.37",
+ "version": "0.0.38-next.1",
"backstage": {
"role": "backend"
},
@@ -45,6 +45,7 @@
"@backstage/plugin-catalog-backend-module-unprocessed": "workspace:^",
"@backstage/plugin-devtools-backend": "workspace:^",
"@backstage/plugin-events-backend": "workspace:^",
+ "@backstage/plugin-events-backend-module-google-pubsub": "workspace:^",
"@backstage/plugin-kubernetes-backend": "workspace:^",
"@backstage/plugin-notifications-backend": "workspace:^",
"@backstage/plugin-permission-backend": "workspace:^",
diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts
index 8ae9864a4a..23d814f1ae 100644
--- a/packages/backend/src/index.ts
+++ b/packages/backend/src/index.ts
@@ -61,4 +61,5 @@ backend.add(import('@backstage/plugin-signals-backend'));
backend.add(import('@backstage/plugin-notifications-backend'));
backend.add(import('./instanceMetadata'));
+backend.add(import('@backstage/plugin-events-backend-module-google-pubsub'));
backend.start();
diff --git a/packages/backend/src/instrumentation.js b/packages/backend/src/instrumentation.js
index e3725632c1..ac2f292c27 100644
--- a/packages/backend/src/instrumentation.js
+++ b/packages/backend/src/instrumentation.js
@@ -14,6 +14,11 @@
* limitations under the License.
*/
+// Prevent from running more than once (due to worker threads)
+if (!require('node:worker_threads').isMainThread) {
+ return;
+}
+
const { NodeSDK } = require('@opentelemetry/sdk-node');
const {
getNodeAutoInstrumentations,
diff --git a/packages/canon/CHANGELOG.md b/packages/canon/CHANGELOG.md
index eef0316491..7cb4492505 100644
--- a/packages/canon/CHANGELOG.md
+++ b/packages/canon/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/canon
+## 0.4.0-next.1
+
+### Minor Changes
+
+- ea36f74: **Breaking Change** Icons on Button and IconButton now need to be imported and placed like this: } />
+
+### Patch Changes
+
+- 720033c: For improved a11y, clicking a Select component label now focuses the Select trigger element, and the TextField component's label is now styled to indicate it's interactive.
+- 206ffbe: Fixed an issue with Canon's DataTable.Pagination component showing the wrong number for the "to" count.
+- 72d019d: Removed various typos
+
+## 0.3.2-next.0
+
+### Patch Changes
+
+- e996368: Fix Canon missing dependencies
+
## 0.3.0
### Minor Changes
diff --git a/packages/canon/css/button.css b/packages/canon/css/button.css
index eb0c44ce1b..7aaad3ec48 100644
--- a/packages/canon/css/button.css
+++ b/packages/canon/css/button.css
@@ -78,12 +78,12 @@
height: 32px;
}
-.canon-Button[data-size="small"] .canon-ButtonIcon {
+.canon-ButtonIcon[data-size="small"], .canon-ButtonIcon[data-size="small"] svg {
width: 1rem;
height: 1rem;
}
-.canon-Button[data-size="medium"] .canon-ButtonIcon {
- width: 1.5rem;
- height: 1.5rem;
+.canon-ButtonIcon[data-size="medium"], .canon-ButtonIcon[data-size="medium"] svg {
+ width: 1.25rem;
+ height: 1.25rem;
}
diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css
index f67d0b332d..6496363868 100644
--- a/packages/canon/css/components.css
+++ b/packages/canon/css/components.css
@@ -134,14 +134,14 @@
height: 32px;
}
-.canon-Button[data-size="small"] .canon-ButtonIcon {
+.canon-ButtonIcon[data-size="small"], .canon-ButtonIcon[data-size="small"] svg {
width: 1rem;
height: 1rem;
}
-.canon-Button[data-size="medium"] .canon-ButtonIcon {
- width: 1.5rem;
- height: 1.5rem;
+.canon-ButtonIcon[data-size="medium"], .canon-ButtonIcon[data-size="medium"] svg {
+ width: 1.25rem;
+ height: 1.25rem;
}
.canon-CollapsiblePanel {
@@ -521,14 +521,14 @@
height: 32px;
}
-.canon-IconButton[data-size="small"] .canon-IconButtonIcon {
+.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg {
width: 1rem;
height: 1rem;
}
-.canon-IconButton[data-size="medium"] .canon-IconButtonIcon {
- width: 1.5rem;
- height: 1.5rem;
+.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg {
+ width: 1.25rem;
+ height: 1.25rem;
}
.canon-TextField {
@@ -543,6 +543,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
+ cursor: pointer;
+}
+
+.canon-TextFieldLabel[data-disabled] {
+ cursor: default;
}
.canon-TextFieldDescription {
@@ -836,6 +841,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
+ cursor: pointer;
+}
+
+.canon-SelectLabel[data-disabled] {
+ cursor: default;
}
.canon-SelectDescription {
diff --git a/packages/canon/css/iconbutton.css b/packages/canon/css/iconbutton.css
index e867a1848e..dc3f9d94c5 100644
--- a/packages/canon/css/iconbutton.css
+++ b/packages/canon/css/iconbutton.css
@@ -78,12 +78,12 @@
height: 32px;
}
-.canon-IconButton[data-size="small"] .canon-IconButtonIcon {
+.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg {
width: 1rem;
height: 1rem;
}
-.canon-IconButton[data-size="medium"] .canon-IconButtonIcon {
- width: 1.5rem;
- height: 1.5rem;
+.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg {
+ width: 1.25rem;
+ height: 1.25rem;
}
diff --git a/packages/canon/css/select.css b/packages/canon/css/select.css
index cf8c05622b..1c23f39201 100644
--- a/packages/canon/css/select.css
+++ b/packages/canon/css/select.css
@@ -10,6 +10,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
+ cursor: pointer;
+}
+
+.canon-SelectLabel[data-disabled] {
+ cursor: default;
}
.canon-SelectDescription {
diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css
index 0fa08102fd..b2743220c7 100644
--- a/packages/canon/css/styles.css
+++ b/packages/canon/css/styles.css
@@ -9358,14 +9358,14 @@
height: 32px;
}
-.canon-Button[data-size="small"] .canon-ButtonIcon {
+.canon-ButtonIcon[data-size="small"], .canon-ButtonIcon[data-size="small"] svg {
width: 1rem;
height: 1rem;
}
-.canon-Button[data-size="medium"] .canon-ButtonIcon {
- width: 1.5rem;
- height: 1.5rem;
+.canon-ButtonIcon[data-size="medium"], .canon-ButtonIcon[data-size="medium"] svg {
+ width: 1.25rem;
+ height: 1.25rem;
}
.canon-CollapsiblePanel {
@@ -9745,14 +9745,14 @@
height: 32px;
}
-.canon-IconButton[data-size="small"] .canon-IconButtonIcon {
+.canon-IconButtonIcon[data-size="small"], .canon-IconButtonIcon[data-size="small"] svg {
width: 1rem;
height: 1rem;
}
-.canon-IconButton[data-size="medium"] .canon-IconButtonIcon {
- width: 1.5rem;
- height: 1.5rem;
+.canon-IconButtonIcon[data-size="medium"], .canon-IconButtonIcon[data-size="medium"] svg {
+ width: 1.25rem;
+ height: 1.25rem;
}
.canon-TextField {
@@ -9767,6 +9767,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
+ cursor: pointer;
+}
+
+.canon-TextFieldLabel[data-disabled] {
+ cursor: default;
}
.canon-TextFieldDescription {
@@ -10060,6 +10065,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
+ cursor: pointer;
+}
+
+.canon-SelectLabel[data-disabled] {
+ cursor: default;
}
.canon-SelectDescription {
diff --git a/packages/canon/css/textfield.css b/packages/canon/css/textfield.css
index bf21b3bd74..4caf7cb2e9 100644
--- a/packages/canon/css/textfield.css
+++ b/packages/canon/css/textfield.css
@@ -10,6 +10,11 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
+ cursor: pointer;
+}
+
+.canon-TextFieldLabel[data-disabled] {
+ cursor: default;
}
.canon-TextFieldDescription {
diff --git a/packages/canon/package.json b/packages/canon/package.json
index f343d9d123..04f9bbdb8f 100644
--- a/packages/canon/package.json
+++ b/packages/canon/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/canon",
- "version": "0.3.0",
+ "version": "0.4.0-next.1",
"backstage": {
"role": "web-library"
},
diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md
index 0a315ae105..765a77148f 100644
--- a/packages/canon/report.api.md
+++ b/packages/canon/report.api.md
@@ -15,6 +15,7 @@ import { ForwardRefExoticComponent } from 'react';
import { HTMLAttributes } from 'react';
import { JSX as JSX_2 } from 'react/jsx-runtime';
import { Menu as Menu_2 } from '@base-ui-components/react/menu';
+import { ReactElement } from 'react';
import { ReactNode } from 'react';
import { RefAttributes } from 'react';
import type { RemixiconComponentType } from '@remixicon/react';
@@ -173,8 +174,8 @@ export const buttonPropDefs: {
export interface ButtonProps
extends Omit, 'children'> {
children: React.ReactNode;
- iconEnd?: IconNames;
- iconStart?: IconNames;
+ iconEnd?: ReactElement;
+ iconStart?: ReactElement;
size?: ButtonOwnProps['size'];
variant?: ButtonOwnProps['variant'];
}
@@ -697,7 +698,7 @@ export const iconButtonPropDefs: {
// @public
export interface IconButtonProps
extends Omit, 'children'> {
- icon: IconNames;
+ icon: ReactElement;
size?: IconButtonOwnProps['size'];
variant?: IconButtonOwnProps['variant'];
}
diff --git a/packages/canon/src/components/Button/Button.stories.tsx b/packages/canon/src/components/Button/Button.stories.tsx
index 7377ad497d..fffc31b488 100644
--- a/packages/canon/src/components/Button/Button.stories.tsx
+++ b/packages/canon/src/components/Button/Button.stories.tsx
@@ -19,6 +19,7 @@ import { Button } from './Button';
import { Flex } from '../Flex';
import { Text } from '../Text';
import { ButtonProps } from './types';
+import { Icon } from '../Icon';
const meta = {
title: 'Components/Button',
@@ -55,10 +56,10 @@ export const Variants: Story = {
},
render: () => (
-
+ } variant="primary">
Button
-
+ } variant="secondary">
Button
@@ -71,8 +72,12 @@ export const Sizes: Story = {
},
render: () => (
- Medium
- Small
+ }>
+ Medium
+
+ }>
+ Small
+
),
};
@@ -83,9 +88,13 @@ export const WithIcons: Story = {
},
render: args => (
-
-
-
+ } />
+ } />
+ }
+ iconEnd={ }
+ />
),
};
@@ -96,9 +105,13 @@ export const FullWidth: Story = {
},
render: args => (
-
-
-
+ } />
+ } />
+ }
+ iconEnd={ }
+ />
),
};
@@ -150,22 +163,22 @@ export const Playground: Story = {
Button
}
variant={variant as ButtonProps['variant']}
size={size as ButtonProps['size']}
>
Button
}
variant={variant as ButtonProps['variant']}
size={size as ButtonProps['size']}
>
Button
}
+ iconEnd={ }
style={{ width: '200px' }}
variant={variant as ButtonProps['variant']}
size={size as ButtonProps['size']}
@@ -180,7 +193,7 @@ export const Playground: Story = {
Button
}
variant={variant as ButtonProps['variant']}
size={size as ButtonProps['size']}
disabled
@@ -188,7 +201,7 @@ export const Playground: Story = {
Button
}
variant={variant as ButtonProps['variant']}
size={size as ButtonProps['size']}
disabled
diff --git a/packages/canon/src/components/Button/Button.tsx b/packages/canon/src/components/Button/Button.tsx
index ca652013b4..ab70f168c8 100644
--- a/packages/canon/src/components/Button/Button.tsx
+++ b/packages/canon/src/components/Button/Button.tsx
@@ -15,7 +15,6 @@
*/
import { forwardRef } from 'react';
-import { Icon } from '../Icon';
import clsx from 'clsx';
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
@@ -50,9 +49,25 @@ export const Button = forwardRef(
style={style}
{...rest}
>
- {iconStart && }
+ {iconStart && (
+
+ {iconStart}
+
+ )}
{children}
- {iconEnd && }
+ {iconEnd && (
+
+ {iconEnd}
+
+ )}
);
},
diff --git a/packages/canon/src/components/Button/styles.css b/packages/canon/src/components/Button/styles.css
index 785fbf3b88..a4afd85c9b 100644
--- a/packages/canon/src/components/Button/styles.css
+++ b/packages/canon/src/components/Button/styles.css
@@ -94,12 +94,14 @@
height: 32px;
}
-.canon-Button[data-size='small'] .canon-ButtonIcon {
+.canon-ButtonIcon[data-size='small'],
+.canon-ButtonIcon[data-size='small'] svg {
width: 1rem;
height: 1rem;
}
-.canon-Button[data-size='medium'] .canon-ButtonIcon {
- width: 1.5rem;
- height: 1.5rem;
+.canon-ButtonIcon[data-size='medium'],
+.canon-ButtonIcon[data-size='medium'] svg {
+ width: 1.25rem;
+ height: 1.25rem;
}
diff --git a/packages/canon/src/components/Button/types.ts b/packages/canon/src/components/Button/types.ts
index 0d61e1cda4..4ab62d5683 100644
--- a/packages/canon/src/components/Button/types.ts
+++ b/packages/canon/src/components/Button/types.ts
@@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { IconNames } from '../Icon';
+
import type { ButtonOwnProps } from './Button.props';
+import { ReactElement } from 'react';
/**
* Properties for {@link Button}
@@ -43,10 +44,10 @@ export interface ButtonProps
/**
* Optional icon to display at the start of the button
*/
- iconStart?: IconNames;
+ iconStart?: ReactElement;
/**
* Optional icon to display at the end of the button
*/
- iconEnd?: IconNames;
+ iconEnd?: ReactElement;
}
diff --git a/packages/canon/src/components/Collapsible/Collapsible.stories.tsx b/packages/canon/src/components/Collapsible/Collapsible.stories.tsx
index 4130419a0c..175dadba85 100644
--- a/packages/canon/src/components/Collapsible/Collapsible.stories.tsx
+++ b/packages/canon/src/components/Collapsible/Collapsible.stories.tsx
@@ -19,6 +19,7 @@ import { Collapsible } from './';
import { Button } from '../Button';
import { Box } from '../Box';
import { Text } from '../Text';
+import { Icon } from '../Icon';
const meta = {
title: 'Components/Collapsible',
@@ -42,7 +43,13 @@ export const Default: Story = {
render={(props, state) => (
+ ) : (
+
+ )
+ }
{...props}
>
{state.open ? 'Close Panel' : 'Open Panel'}
diff --git a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx
index eabea109fa..2efa73bf14 100644
--- a/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx
+++ b/packages/canon/src/components/DataTable/Pagination/DataTablePagination.tsx
@@ -21,6 +21,7 @@ import { IconButton } from '../../IconButton';
import clsx from 'clsx';
import { Select } from '../../Select';
import { useDataTable } from '../Root/DataTableRoot';
+import { Icon } from '../../Icon';
/** @public */
const DataTablePagination = forwardRef(
@@ -32,6 +33,12 @@ const DataTablePagination = forwardRef(
const { table } = useDataTable();
const pageIndex = table?.getState().pagination.pageIndex;
const pageSize = table?.getState().pagination.pageSize;
+ const rowCount = table?.getRowCount();
+ const fromCount = (pageIndex ?? 0) * (pageSize ?? 10) + 1;
+ const toCount = Math.min(
+ ((pageIndex ?? 0) + 1) * (pageSize ?? 10),
+ rowCount,
+ );
return (
- {`${(pageIndex ?? 0) * (pageSize ?? 10) + 1} - ${
- ((pageIndex ?? 0) + 1) * (pageSize ?? 10)
- } of ${table?.getRowCount()}`}
+ {`${fromCount} - ${toCount} of ${rowCount}`}
table?.previousPage()}
disabled={!table?.getCanPreviousPage()}
- icon="chevron-left"
+ icon={ }
/>
table?.nextPage()}
disabled={!table?.getCanNextPage()}
- icon="chevron-right"
+ icon={ }
/>
diff --git a/packages/canon/src/components/IconButton/IconButton.stories.tsx b/packages/canon/src/components/IconButton/IconButton.stories.tsx
index edac9bd7e8..d16dcd1fcb 100644
--- a/packages/canon/src/components/IconButton/IconButton.stories.tsx
+++ b/packages/canon/src/components/IconButton/IconButton.stories.tsx
@@ -19,6 +19,7 @@ import { IconButton } from './IconButton';
import { Flex } from '../Flex';
import { Text } from '../Text';
import { IconButtonProps } from './types';
+import { Icon } from '../Icon';
const meta = {
title: 'Components/IconButton',
@@ -44,7 +45,8 @@ type Story = StoryObj;
export const Variants: Story = {
args: {
- icon: 'cloud',
+ icon: ,
+ 'aria-label': 'Cloud icon button',
},
parameters: {
argTypes: {
@@ -63,7 +65,8 @@ export const Variants: Story = {
export const Sizes: Story = {
args: {
- icon: 'cloud',
+ icon: ,
+ 'aria-label': 'Cloud icon button',
},
render: args => (
@@ -75,8 +78,9 @@ export const Sizes: Story = {
export const Disabled: Story = {
args: {
- icon: 'cloud',
+ icon: ,
disabled: true,
+ 'aria-label': 'Cloud icon button',
},
render: args => (
@@ -88,7 +92,8 @@ export const Disabled: Story = {
export const Responsive: Story = {
args: {
- icon: 'cloud',
+ icon: ,
+ 'aria-label': 'Cloud icon button',
variant: {
initial: 'primary',
sm: 'secondary',
@@ -104,7 +109,8 @@ const variants: string[] = ['primary', 'secondary'];
export const Playground: Story = {
args: {
- icon: 'cloud',
+ icon: ,
+ 'aria-label': 'Cloud icon button',
},
render: args => (
@@ -120,13 +126,15 @@ export const Playground: Story = {
/>
}
+ aria-label="Chevron right icon button"
variant={variant as IconButtonProps['variant']}
size={size as IconButtonProps['size']}
/>
}
+ aria-label="Chevron right icon button"
variant={variant as IconButtonProps['variant']}
size={size as IconButtonProps['size']}
/>
diff --git a/packages/canon/src/components/IconButton/IconButton.tsx b/packages/canon/src/components/IconButton/IconButton.tsx
index b2581844bc..bca012cac0 100644
--- a/packages/canon/src/components/IconButton/IconButton.tsx
+++ b/packages/canon/src/components/IconButton/IconButton.tsx
@@ -15,7 +15,6 @@
*/
import { forwardRef } from 'react';
-import { Icon } from '../Icon';
import clsx from 'clsx';
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
@@ -45,7 +44,13 @@ export const IconButton = forwardRef(
style={style}
{...rest}
>
-
+
+ {icon}
+
);
},
diff --git a/packages/canon/src/components/IconButton/styles.css b/packages/canon/src/components/IconButton/styles.css
index 859c512fd6..066b0fbad5 100644
--- a/packages/canon/src/components/IconButton/styles.css
+++ b/packages/canon/src/components/IconButton/styles.css
@@ -94,12 +94,14 @@
width: 32px;
}
-.canon-IconButton[data-size='small'] .canon-IconButtonIcon {
+.canon-IconButtonIcon[data-size='small'],
+.canon-IconButtonIcon[data-size='small'] svg {
width: 1rem;
height: 1rem;
}
-.canon-IconButton[data-size='medium'] .canon-IconButtonIcon {
- width: 1.5rem;
- height: 1.5rem;
+.canon-IconButtonIcon[data-size='medium'],
+.canon-IconButtonIcon[data-size='medium'] svg {
+ width: 1.25rem;
+ height: 1.25rem;
}
diff --git a/packages/canon/src/components/IconButton/types.ts b/packages/canon/src/components/IconButton/types.ts
index 90505d3352..ece1950d1b 100644
--- a/packages/canon/src/components/IconButton/types.ts
+++ b/packages/canon/src/components/IconButton/types.ts
@@ -13,8 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { IconNames } from '../Icon';
+
import type { IconButtonOwnProps } from './IconButton.props';
+import { ReactElement } from 'react';
/**
* Properties for {@link IconButton}
@@ -36,7 +37,7 @@ export interface IconButtonProps
variant?: IconButtonOwnProps['variant'];
/**
- * Icon to display at the start of the button
+ * Icon to display in the button
*/
- icon: IconNames;
+ icon: ReactElement;
}
diff --git a/packages/canon/src/components/Menu/Menu.stories.tsx b/packages/canon/src/components/Menu/Menu.stories.tsx
index d1b8f831c5..5d04a2f19c 100644
--- a/packages/canon/src/components/Menu/Menu.stories.tsx
+++ b/packages/canon/src/components/Menu/Menu.stories.tsx
@@ -17,6 +17,7 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Menu } from './Menu';
import { Button } from '../Button';
+import { Icon } from '../Icon';
const meta = {
title: 'Components/Menu',
@@ -36,7 +37,7 @@ export const Default: Story = {
{...props}
size="medium"
variant="secondary"
- iconEnd="chevron-down"
+ iconEnd={ }
>
Menu
diff --git a/packages/canon/src/components/Select/Select.styles.css b/packages/canon/src/components/Select/Select.styles.css
index fa00d81302..34cb242c7b 100644
--- a/packages/canon/src/components/Select/Select.styles.css
+++ b/packages/canon/src/components/Select/Select.styles.css
@@ -26,6 +26,10 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
+ cursor: pointer;
+}
+.canon-SelectLabel[data-disabled] {
+ cursor: default;
}
.canon-SelectDescription {
diff --git a/packages/canon/src/components/Select/Select.tsx b/packages/canon/src/components/Select/Select.tsx
index be8047ca02..3f25ac26de 100644
--- a/packages/canon/src/components/Select/Select.tsx
+++ b/packages/canon/src/components/Select/Select.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { forwardRef, useId } from 'react';
+import { forwardRef, useCallback, useId, useRef, MouseEvent } from 'react';
import { Select as SelectPrimitive } from '@base-ui-components/react/select';
import { Icon } from '../Icon';
import clsx from 'clsx';
@@ -45,10 +45,27 @@ export const Select = forwardRef((props, ref) => {
const descriptionId = useId();
const errorId = useId();
+ const triggerRef = useRef(null);
+
+ const handleLabelClick = useCallback(
+ (e: MouseEvent) => {
+ if (!props.disabled && triggerRef.current) {
+ e.preventDefault();
+ triggerRef.current.focus();
+ }
+ },
+ [props.disabled],
+ );
+
return (
{label && (
-
+
{label}
{required && (
@@ -59,6 +76,7 @@ export const Select = forwardRef((props, ref) => {
)}
void;
/**
- * Callbak that is called when the select field is opened or closed
+ * Callback that is called when the select field is opened or closed
*/
onOpenChange?: (open: boolean) => void;
diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css
index e2c843f166..7ee0a937dc 100644
--- a/packages/canon/src/components/TextField/TextField.styles.css
+++ b/packages/canon/src/components/TextField/TextField.styles.css
@@ -26,6 +26,10 @@
font-weight: var(--canon-font-weight-regular);
color: var(--canon-fg-primary);
margin-bottom: var(--canon-space-1_5);
+ cursor: pointer;
+}
+.canon-TextFieldLabel[data-disabled] {
+ cursor: default;
}
.canon-TextFieldDescription {
diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx
index 63c7e21435..db56463c3a 100644
--- a/packages/canon/src/components/TextField/TextField.tsx
+++ b/packages/canon/src/components/TextField/TextField.tsx
@@ -32,6 +32,7 @@ export const TextField = forwardRef(
error,
required,
style,
+ disabled,
...rest
} = props;
@@ -50,7 +51,11 @@ export const TextField = forwardRef(
ref={ref}
>
{label && (
-
+
{label}
{required && (
@@ -70,6 +75,7 @@ export const TextField = forwardRef(
})}
data-invalid={error}
required={required}
+ disabled={disabled}
{...rest}
/>
{description && (
diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md
index 6d45557f52..5d8d058518 100644
--- a/packages/catalog-client/CHANGELOG.md
+++ b/packages/catalog-client/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/catalog-client
+## 1.10.0-next.0
+
+### Minor Changes
+
+- 1a003ff: Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+
## 1.9.1
### Patch Changes
diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json
index e0c68750a4..95e5931121 100644
--- a/packages/catalog-client/package.json
+++ b/packages/catalog-client/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-client",
- "version": "1.9.1",
+ "version": "1.10.0-next.0",
"description": "An isomorphic client for the catalog backend",
"backstage": {
"role": "common-library"
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index 289cdba059..8c5fb14d96 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,43 @@
# @backstage/cli
+## 0.32.1-next.1
+
+### Patch Changes
+
+- d649df0: Internal code cleanup
+- 9aaec54: Internal refactor of opaque type handling.
+- 9285385: Added `info` object to the context of the alpha CLI.
+- 6cc9507: Updated dependency `@octokit/request` to `^8.0.0`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/cli-node@0.2.13
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.1.10
+ - @backstage/release-manifests@0.0.12
+ - @backstage/types@1.2.1
+
+## 0.32.1-next.0
+
+### Patch Changes
+
+- 5cd3c54: Updated dependency `react-refresh` to `^0.17.0`.
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/cli-node@0.2.13
+ - @backstage/config-loader@1.10.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.1.10
+ - @backstage/release-manifests@0.0.12
+ - @backstage/types@1.2.1
+
## 0.32.0
### Minor Changes
diff --git a/packages/cli/cli-report.backstage-cli-alpha.md b/packages/cli/cli-report.backstage-cli-alpha.md
index 917a915fe5..386a642c21 100644
--- a/packages/cli/cli-report.backstage-cli-alpha.md
+++ b/packages/cli/cli-report.backstage-cli-alpha.md
@@ -56,12 +56,11 @@ Commands:
### `backstage-cli-alpha config docs`
```
-Usage:
+Usage: backstage-cli config docs [options]
Options:
- --help
- --package
- --version
+ --package
+ -h, --help
```
### `backstage-cli-alpha config schema`
diff --git a/packages/cli/config/jestCachingModuleLoader.js b/packages/cli/config/jestCachingModuleLoader.js
index dd22f25f6f..b2b6a4f8ae 100644
--- a/packages/cli/config/jestCachingModuleLoader.js
+++ b/packages/cli/config/jestCachingModuleLoader.js
@@ -19,8 +19,8 @@ const { default: JestRuntime } = require('jest-runtime');
const scriptTransformCache = new Map();
module.exports = class CachingJestRuntime extends JestRuntime {
- constructor(config, ...restAgs) {
- super(config, ...restAgs);
+ constructor(config, ...restArgs) {
+ super(config, ...restArgs);
this.allowLoadAsEsm = config.extensionsToTreatAsEsm.includes('.mts');
}
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 5d9e883bc6..1be77acd70 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/cli",
- "version": "0.32.0",
+ "version": "0.32.1-next.1",
"description": "CLI for developing Backstage plugins and apps",
"backstage": {
"role": "cli"
@@ -63,7 +63,7 @@
"@octokit/graphql": "^5.0.0",
"@octokit/graphql-schema": "^13.7.0",
"@octokit/oauth-app": "^4.2.0",
- "@octokit/request": "^6.0.0",
+ "@octokit/request": "^8.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.7",
"@rollup/plugin-commonjs": "^26.0.0",
"@rollup/plugin-json": "^6.0.0",
@@ -137,7 +137,7 @@
"process": "^0.11.10",
"raw-loader": "^4.0.2",
"react-dev-utils": "^12.0.0-next.60",
- "react-refresh": "^0.14.0",
+ "react-refresh": "^0.17.0",
"recursive-readdir": "^2.2.2",
"replace-in-file": "^7.1.0",
"rollup": "^4.27.3",
diff --git a/packages/cli/src/commands/start/command.ts b/packages/cli/src/commands/start/command.ts
deleted file mode 100644
index addae78fb1..0000000000
--- a/packages/cli/src/commands/start/command.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import fs from 'fs-extra';
-import { OptionValues } from 'commander';
-import { resolve as resolvePath } from 'node:path';
-import { PackageRole } from '@backstage/cli-node';
-import { findRoleFromCommand } from '../../lib/role';
-import { startBackend, startBackendPlugin } from './startBackend';
-import { startFrontend } from './startFrontend';
-import { ForwardedError } from '@backstage/errors';
-
-export async function command(opts: OptionValues): Promise {
- const role = await findRoleFromCommand(opts);
-
- if (opts.link) {
- const dir = resolvePath(opts.link);
- if (!fs.pathExistsSync(dir)) {
- throw new Error(
- `Invalid workspace link, directory does not exist: ${dir}`,
- );
- }
- const pkgJson = await fs
- .readJson(resolvePath(dir, 'package.json'))
- .catch(error => {
- throw new ForwardedError(
- 'Failed to read package.json in linked workspace',
- error,
- );
- });
-
- if (!pkgJson.workspaces) {
- throw new Error(
- `Invalid workspace link, directory is not a workspace: ${dir}`,
- );
- }
- }
-
- const options = {
- configPaths: opts.config as string[],
- checksEnabled: Boolean(opts.check),
- linkedWorkspace: opts.link,
- inspectEnabled: opts.inspect,
- inspectBrkEnabled: opts.inspectBrk,
- require: opts.require,
- };
-
- switch (role) {
- case 'backend':
- return startBackend(options);
- case 'backend-plugin':
- case 'backend-plugin-module':
- case 'node-library':
- return startBackendPlugin(options);
- case 'frontend':
- return startFrontend({
- ...options,
- entry: 'src/index',
- verifyVersions: true,
- });
- case 'web-library':
- case 'frontend-plugin':
- case 'frontend-plugin-module':
- return startFrontend({ entry: 'dev/index', ...options });
- case 'frontend-dynamic-container' as PackageRole: // experimental
- return startFrontend({
- entry: 'src/index',
- ...options,
- skipOpenBrowser: true,
- isModuleFederationRemote: true,
- });
- default:
- throw new Error(
- `Start command is not supported for package role '${role}'`,
- );
- }
-}
diff --git a/packages/cli/src/commands/start/startBackend.ts b/packages/cli/src/commands/start/startBackend.ts
deleted file mode 100644
index 84f9eb0890..0000000000
--- a/packages/cli/src/commands/start/startBackend.ts
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import fs from 'fs-extra';
-import { paths } from '../../lib/paths';
-import { runBackend } from '../../lib/runner';
-
-interface StartBackendOptions {
- checksEnabled: boolean;
- inspectEnabled: boolean;
- inspectBrkEnabled: boolean;
- linkedWorkspace?: string;
- require?: string;
-}
-
-export async function startBackend(options: StartBackendOptions) {
- const waitForExit = await runBackend({
- entry: 'src/index',
- inspectEnabled: options.inspectEnabled,
- inspectBrkEnabled: options.inspectBrkEnabled,
- linkedWorkspace: options.linkedWorkspace,
- require: options.require,
- });
-
- await waitForExit();
-}
-
-export async function startBackendPlugin(options: StartBackendOptions) {
- const hasDevIndexEntry = await fs.pathExists(
- paths.resolveTarget('dev', 'index.ts'),
- );
- if (!hasDevIndexEntry) {
- console.warn(
- `The 'dev' directory is missing. Please create a proper dev/index.ts in order to start the plugin.`,
- );
- return;
- }
-
- const waitForExit = await runBackend({
- entry: 'dev/index',
- inspectEnabled: options.inspectEnabled,
- inspectBrkEnabled: options.inspectBrkEnabled,
- require: options.require,
- linkedWorkspace: options.linkedWorkspace,
- });
-
- await waitForExit();
-}
diff --git a/packages/cli/src/commands/start/startFrontend.ts b/packages/cli/src/commands/start/startFrontend.ts
deleted file mode 100644
index e5b3b0dfbc..0000000000
--- a/packages/cli/src/commands/start/startFrontend.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { readJson } from 'fs-extra';
-import { resolve as resolvePath } from 'path';
-import {
- getModuleFederationOptions,
- serveBundle,
-} from '../../modules/build/lib/bundler';
-import { paths } from '../../lib/paths';
-import { BackstagePackageJson } from '@backstage/cli-node';
-
-interface StartAppOptions {
- verifyVersions?: boolean;
- entry: string;
-
- checksEnabled: boolean;
- configPaths: string[];
- skipOpenBrowser?: boolean;
- isModuleFederationRemote?: boolean;
- linkedWorkspace?: string;
-}
-
-export async function startFrontend(options: StartAppOptions) {
- const packageJson = (await readJson(
- paths.resolveTarget('package.json'),
- )) as BackstagePackageJson;
-
- const waitForExit = await serveBundle({
- entry: options.entry,
- checksEnabled: options.checksEnabled,
- configPaths: options.configPaths,
- verifyVersions: options.verifyVersions,
- skipOpenBrowser: options.skipOpenBrowser,
- linkedWorkspace: options.linkedWorkspace,
- moduleFederation: await getModuleFederationOptions(
- packageJson,
- resolvePath(paths.targetDir),
- options.isModuleFederationRemote,
- ),
- });
-
- await waitForExit();
-}
diff --git a/packages/cli/src/commands/repo/optionsParser.ts b/packages/cli/src/lib/optionsParser.ts
similarity index 100%
rename from packages/cli/src/commands/repo/optionsParser.ts
rename to packages/cli/src/lib/optionsParser.ts
diff --git a/packages/cli/src/modules/build/commands/repo/build.ts b/packages/cli/src/modules/build/commands/repo/build.ts
index 44fbc13887..d0b9de8867 100644
--- a/packages/cli/src/modules/build/commands/repo/build.ts
+++ b/packages/cli/src/modules/build/commands/repo/build.ts
@@ -27,7 +27,7 @@ import {
import { runParallelWorkers } from '../../../../lib/parallel';
import { buildFrontend } from '../../lib/buildFrontend';
import { buildBackend } from '../../lib/buildBackend';
-import { createScriptOptionsParser } from '../../../../commands/repo/optionsParser';
+import { createScriptOptionsParser } from '../../../../lib/optionsParser';
export async function command(opts: OptionValues, cmd: Command): Promise {
let packages = await PackageGraph.listTargetPackages();
diff --git a/packages/cli/src/modules/config/alpha.ts b/packages/cli/src/modules/config/alpha.ts
index a0f2624922..43bce858a1 100644
--- a/packages/cli/src/modules/config/alpha.ts
+++ b/packages/cli/src/modules/config/alpha.ts
@@ -40,22 +40,21 @@ export default createCliPlugin({
reg.addCommand({
path: ['config', 'docs'],
description: 'Browse the configuration reference documentation',
- execute: async ({ args }) => {
- const argv = await yargs
- .options({
- package: { type: 'string' },
- })
- .help()
- .parse(args);
- const m =
- (await require('./commands/docs')) as typeof import('./commands/docs');
- await m.default(argv);
+ execute: async ({ args, info }) => {
+ await new Command(info.usage)
+ .option(
+ '--package ',
+ 'Only include the schema that applies to the given package',
+ )
+ .description(info.description)
+ .action(lazy(() => import('./commands/docs'), 'default'))
+ .parseAsync(args, { from: 'user' });
},
});
reg.addCommand({
path: ['config:print'],
description: 'Print the app configuration for the current package',
- execute: async ({ args }) => {
+ execute: async ({ args, info }) => {
const argv = await yargs
.options({
package: { type: 'string' },
@@ -63,8 +62,9 @@ export default createCliPlugin({
frontend: { type: 'boolean' },
'with-secrets': { type: 'boolean' },
format: { type: 'string' },
- config: { type: 'string', array: true },
+ config: { type: 'string', array: true, default: [] },
})
+ .usage('$0', info.description)
.help()
.parse(args);
const m =
diff --git a/packages/cli/src/modules/lint/commands/repo/lint.ts b/packages/cli/src/modules/lint/commands/repo/lint.ts
index ab6597c70a..72bb02a693 100644
--- a/packages/cli/src/modules/lint/commands/repo/lint.ts
+++ b/packages/cli/src/modules/lint/commands/repo/lint.ts
@@ -26,7 +26,7 @@ import {
} from '@backstage/cli-node';
import { paths } from '../../../../lib/paths';
import { runWorkerQueueThreads } from '../../../../lib/parallel';
-import { createScriptOptionsParser } from '../../../../commands/repo/optionsParser';
+import { createScriptOptionsParser } from '../../../../lib/optionsParser';
import { SuccessCache } from '../../../../lib/cache/SuccessCache';
function depCount(pkg: BackstagePackageJson) {
diff --git a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts
index bce9888842..a309ddb192 100644
--- a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts
+++ b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts
@@ -131,10 +131,10 @@ const lockfileMock = `${HEADER}
// Avoid flakes by comparing sorted log lines. File system access is async, which leads to the log line order being indeterministic
const expectLogsToMatch = (
- recievedLogs: String[],
+ receivedLogs: String[],
expected: String[],
): void => {
- expect(recievedLogs.filter(Boolean).sort()).toEqual(expected.sort());
+ expect(receivedLogs.filter(Boolean).sort()).toEqual(expected.sort());
};
describe('bump', () => {
diff --git a/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts b/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts
index 7a0a1269c4..9ff00fd285 100644
--- a/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts
+++ b/packages/cli/src/modules/migrate/commands/versions/migrate.test.ts
@@ -51,8 +51,8 @@ jest.mock('../../../../lib/run', () => {
};
});
-function expectLogsToMatch(recievedLogs: String[], expected: String[]): void {
- expect(recievedLogs.filter(Boolean).sort()).toEqual(expected.sort());
+function expectLogsToMatch(receivedLogs: String[], expected: String[]): void {
+ expect(receivedLogs.filter(Boolean).sort()).toEqual(expected.sort());
}
describe('versions:migrate', () => {
@@ -169,7 +169,7 @@ describe('versions:migrate', () => {
});
});
- it('should replace the occurences of the moved package in files inside the correct package', async () => {
+ it('should replace the occurrences of the moved package in files inside the correct package', async () => {
mockDir.setContent({
'package.json': JSON.stringify({
workspaces: {
@@ -259,7 +259,7 @@ describe('versions:migrate', () => {
);
});
- it('should replaces the occurences of changed packages, and is careful', async () => {
+ it('should replaces the occurrences of changed packages, and is careful', async () => {
mockDir.setContent({
'package.json': JSON.stringify({
workspaces: {
diff --git a/packages/cli/src/modules/start/commands/repo/start.ts b/packages/cli/src/modules/start/commands/repo/start.ts
index b0bb8f5c01..8905aec4b1 100644
--- a/packages/cli/src/modules/start/commands/repo/start.ts
+++ b/packages/cli/src/modules/start/commands/repo/start.ts
@@ -69,7 +69,7 @@ export async function findTargetPackages(
const packages = await PackageGraph.listTargetPackages();
- // Priorotize plugin options, so that the `start` script can contain a list of packages,
+ // Prioritize plugin options, so that the `start` script can contain a list of packages,
// but make them easy to override by running for example `yarn start --plugin catalog`
for (const pluginId of pluginIds) {
const matchingPackages = packages.filter(pkg => {
diff --git a/packages/cli/src/wiring/CliInitializer.ts b/packages/cli/src/wiring/CliInitializer.ts
index d3cba34f60..778700fe6b 100644
--- a/packages/cli/src/wiring/CliInitializer.ts
+++ b/packages/cli/src/wiring/CliInitializer.ts
@@ -15,7 +15,7 @@
*/
import { CommandGraph } from './CommandGraph';
-import { CliFeature, InternalCliFeature, InternalCliPlugin } from './types';
+import { CliFeature, OpaqueCliPlugin } from './types';
import { CommandRegistry } from './CommandRegistry';
import { Command } from 'commander';
import { version } from '../lib/version';
@@ -42,10 +42,11 @@ export class CliInitializer {
}
async #register(feature: CliFeature) {
- if (isCliPlugin(feature)) {
- await feature.init(this.commandRegistry);
+ if (OpaqueCliPlugin.isType(feature)) {
+ const internal = OpaqueCliPlugin.toInternal(feature);
+ await internal.init(this.commandRegistry);
} else {
- throw new Error(`Unsupported feature type: ${feature.$$type}`);
+ throw new Error(`Unsupported feature type: ${(feature as any).$$type}`);
}
}
@@ -62,9 +63,11 @@ export class CliInitializer {
async run() {
await this.#doInit();
+ const programName = 'backstage-cli';
+
const program = new Command();
program
- .name('backstage-cli')
+ .name(programName)
.version(version)
.allowUnknownOption(true)
.allowExcessArguments(true);
@@ -117,6 +120,10 @@ export class CliInitializer {
}
await node.command.execute({
args: [...positionalArgs, ...args.unknown],
+ info: {
+ usage: [programName, ...node.command.path].join(' '),
+ description: node.command.description,
+ },
});
process.exit(0);
} catch (error) {
@@ -146,26 +153,6 @@ export class CliInitializer {
}
}
-function toInternalCliFeature(feature: CliFeature): InternalCliFeature {
- if (feature.$$type !== '@backstage/CliFeature') {
- throw new Error(`Invalid CliFeature, bad type '${feature.$$type}'`);
- }
- const internal = feature as InternalCliFeature;
- if (internal.version !== 'v1') {
- throw new Error(`Invalid CliFeature, bad version '${internal.version}'`);
- }
- return internal;
-}
-
-function isCliPlugin(feature: CliFeature): feature is InternalCliPlugin {
- const internal = toInternalCliFeature(feature);
- if (internal.featureType === 'plugin') {
- return true;
- }
- // Backwards compatibility for v1 registrations that use duck typing
- return 'plugin' in internal;
-}
-
/** @internal */
export function unwrapFeature(
feature: CliFeature | { default: CliFeature },
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/alpha.ts b/packages/cli/src/wiring/describeParentCallSite.ts
similarity index 65%
rename from plugins/catalog-backend-module-incremental-ingestion/src/alpha.ts
rename to packages/cli/src/wiring/describeParentCallSite.ts
index 776dd937bf..35603e33b0 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/alpha.ts
+++ b/packages/cli/src/wiring/describeParentCallSite.ts
@@ -14,11 +14,7 @@
* limitations under the License.
*/
-import { default as feature } from './module';
-
-/**
- * @alpha
- * @deprecated Please import from `@backstage/plugin-catalog-backend-module-incremental-ingestion` instead
- */
-const _feature = feature;
-export default _feature;
+// Single re-export to avoid doing this import in multiple places, but still
+// avoid duplicate declarations because this one is a bit tricky.
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+export { describeParentCallSite } from '../../../frontend-plugin-api/src/routing/describeParentCallSite';
diff --git a/packages/cli/src/wiring/factory.ts b/packages/cli/src/wiring/factory.ts
index eec058e180..6374f37c14 100644
--- a/packages/cli/src/wiring/factory.ts
+++ b/packages/cli/src/wiring/factory.ts
@@ -14,19 +14,18 @@
* limitations under the License.
*/
-import { CommandRegistry } from './CommandRegistry';
-import { InternalCliPlugin } from './types';
+import { describeParentCallSite } from './describeParentCallSite';
+import { BackstageCommand, CliPlugin, OpaqueCliPlugin } from './types';
export function createCliPlugin(options: {
pluginId: string;
- init: (registry: CommandRegistry) => Promise;
-}): InternalCliPlugin {
- return {
- id: options.pluginId,
+ init: (registry: {
+ addCommand: (command: BackstageCommand) => void;
+ }) => Promise;
+}): CliPlugin {
+ return OpaqueCliPlugin.createInstance('v1', {
+ pluginId: options.pluginId,
init: options.init,
- $$type: '@backstage/CliFeature',
- version: 'v1',
- featureType: 'plugin',
- description: 'A Backstage CLI plugin',
- };
+ description: `created at '${describeParentCallSite()}'`,
+ });
}
diff --git a/packages/cli/src/wiring/types.ts b/packages/cli/src/wiring/types.ts
index 0decdc6479..fc2c2b454d 100644
--- a/packages/cli/src/wiring/types.ts
+++ b/packages/cli/src/wiring/types.ts
@@ -13,35 +13,44 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { CommandRegistry } from './CommandRegistry';
+import { OpaqueType } from '@internal/opaque';
export interface BackstageCommand {
path: string[];
description: string;
deprecated?: boolean;
- execute: (options: { args: string[] }) => Promise;
+ execute: (context: {
+ args: string[];
+ info: {
+ /**
+ * The usage string of the current command, for example: "backstage-cli repo test"
+ */
+ usage: string;
+ /**
+ * The description provided for the command
+ */
+ description: string;
+ };
+ }) => Promise;
}
-export interface CliFeature {
- $$type: '@backstage/CliFeature';
-}
+export type CliFeature = CliPlugin;
export interface CliPlugin {
- id: string;
- init: (registry: CommandRegistry) => Promise;
- $$type: '@backstage/CliFeature';
+ readonly pluginId: string;
+ readonly $$type: '@backstage/CliPlugin';
}
-/**
- * @public
- */
-export interface InternalCliPlugin extends CliFeature {
- version: 'v1';
- featureType: 'plugin';
- description: string;
- id: string;
- init: (registry: CommandRegistry) => Promise;
-}
-
-/** @internal */
-export type InternalCliFeature = InternalCliPlugin;
+export const OpaqueCliPlugin = OpaqueType.create<{
+ public: CliPlugin;
+ versions: {
+ readonly version: 'v1';
+ readonly description: string;
+ init: (registry: {
+ addCommand: (command: BackstageCommand) => void;
+ }) => Promise;
+ };
+}>({
+ type: '@backstage/CliPlugin',
+ versions: ['v1'],
+});
diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md
index 2b01760982..5305433cb9 100644
--- a/packages/config-loader/CHANGELOG.md
+++ b/packages/config-loader/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/config-loader
+## 1.10.1-next.0
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 1.10.0
### Minor Changes
diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json
index 2478796995..6f846fddbd 100644
--- a/packages/config-loader/package.json
+++ b/packages/config-loader/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/config-loader",
- "version": "1.10.0",
+ "version": "1.10.1-next.0",
"description": "Config loading functionality used by Backstage backend, and CLI",
"backstage": {
"role": "node-library"
diff --git a/packages/config-loader/src/sources/RemoteConfigSource.ts b/packages/config-loader/src/sources/RemoteConfigSource.ts
index d8fa07d708..5fd086f8da 100644
--- a/packages/config-loader/src/sources/RemoteConfigSource.ts
+++ b/packages/config-loader/src/sources/RemoteConfigSource.ts
@@ -147,7 +147,7 @@ export class RemoteConfigSource implements ConfigSource {
if (rawData === undefined) {
/**
* This error message is/was coupled to the implementation and with refactoring it is no longer truly accurate
- * This behavior is also inconsistent with {@link FileConfigSource}, which doesn't error on unparseable or empty
+ * This behavior is also inconsistent with {@link FileConfigSource}, which doesn't error on unparsable or empty
* content
*
* Preserving to not make a breaking change
diff --git a/packages/config-loader/src/sources/types.ts b/packages/config-loader/src/sources/types.ts
index fedce213c5..a8cfccc3c6 100644
--- a/packages/config-loader/src/sources/types.ts
+++ b/packages/config-loader/src/sources/types.ts
@@ -78,7 +78,7 @@ export interface ConfigSource {
}
/**
- * A custom function to be used for substitution withing configuration files.
+ * A custom function to be used for substitution within configuration files.
*
* @remarks
*
diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md
index 741c3d46c4..58d8e5df47 100644
--- a/packages/core-compat-api/CHANGELOG.md
+++ b/packages/core-compat-api/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/core-compat-api
+## 0.4.2-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/version-bridge@1.0.11
+
+## 0.4.2-next.0
+
+### Patch Changes
+
+- 6eedf46: Updated dependency `@backstage-community/plugin-puppetdb` to `^0.6.0`.
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/version-bridge@1.0.11
+
## 0.4.1
### Patch Changes
diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json
index 80ba090b44..51a48c130f 100644
--- a/packages/core-compat-api/package.json
+++ b/packages/core-compat-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/core-compat-api",
- "version": "0.4.1",
+ "version": "0.4.2-next.1",
"backstage": {
"role": "web-library"
},
@@ -38,7 +38,7 @@
"lodash": "^4.17.21"
},
"devDependencies": {
- "@backstage-community/plugin-puppetdb": "^0.1.18",
+ "@backstage-community/plugin-puppetdb": "^0.6.0",
"@backstage-community/plugin-stackstorm": "^0.1.16",
"@backstage/cli": "workspace:^",
"@backstage/core-app-api": "workspace:^",
diff --git a/packages/core-compat-api/src/collectEntityPageContents.ts b/packages/core-compat-api/src/collectEntityPageContents.ts
index 6748c8939a..582dee38f9 100644
--- a/packages/core-compat-api/src/collectEntityPageContents.ts
+++ b/packages/core-compat-api/src/collectEntityPageContents.ts
@@ -30,7 +30,7 @@ import { normalizeRoutePath } from './normalizeRoutePath';
const ENTITY_SWITCH_KEY = 'core.backstage.entitySwitch';
const ENTITY_ROUTE_KEY = 'plugin.catalog.entityLayoutRoute';
-// Placeholder to make sure internal types here are consitent
+// Placeholder to make sure internal types here are consistent
type Entity = { apiVersion: string; kind: string };
type EntityFilter = (entity: Entity, ctx: { apis: ApiHolder }) => boolean;
diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx
index f65856ec08..425f3684b6 100644
--- a/packages/core-compat-api/src/collectLegacyRoutes.tsx
+++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx
@@ -304,7 +304,7 @@ export function collectLegacyRoutes(
for (const [plugin, extensions] of pluginExtensions) {
output.push(
createFrontendPlugin({
- id: plugin.getId(),
+ pluginId: plugin.getId(),
extensions: [
...extensions,
...Array.from(plugin.getApis()).map(factory =>
diff --git a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx
index 3faaf124fd..151fb21067 100644
--- a/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx
+++ b/packages/core-compat-api/src/compatWrapper/BackwardsCompatProvider.tsx
@@ -85,7 +85,7 @@ export function toLegacyPlugin(
// TODO: Currently a very naive implementation, may need some more work
function toNewPlugin(plugin: LegacyBackstagePlugin): NewFrontendPlugin {
return createNewPlugin({
- id: plugin.getId(),
+ pluginId: plugin.getId(),
});
}
diff --git a/packages/core-compat-api/src/convertLegacyPlugin.ts b/packages/core-compat-api/src/convertLegacyPlugin.ts
index 5fd5e585e6..bf315563ef 100644
--- a/packages/core-compat-api/src/convertLegacyPlugin.ts
+++ b/packages/core-compat-api/src/convertLegacyPlugin.ts
@@ -32,7 +32,7 @@ export function convertLegacyPlugin(
ApiBlueprint.make({ name: factory.api.id, params: { factory } }),
);
return createFrontendPlugin({
- id: legacyPlugin.getId(),
+ pluginId: legacyPlugin.getId(),
featureFlags: [...legacyPlugin.getFeatureFlags()],
routes: convertLegacyRouteRefs(legacyPlugin.routes ?? {}),
externalRoutes: convertLegacyRouteRefs(legacyPlugin.externalRoutes ?? {}),
diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md
index a3d5430dab..ed75738a7a 100644
--- a/packages/core-components/CHANGELOG.md
+++ b/packages/core-components/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/core-components
+## 0.17.2-next.0
+
+### Patch Changes
+
+- e0d1025: `LogViewer` now supports a `textWrap` prop that wraps log lines to the next line for overflowing content instead of using horizontal scroll
+- bb84534: Fix the hidden sidebar's sub-menu when the sidebar is scrollable
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/version-bridge@1.0.11
+
## 0.17.1
### Patch Changes
diff --git a/packages/core-components/package.json b/packages/core-components/package.json
index 4805c376bc..d55c977c87 100644
--- a/packages/core-components/package.json
+++ b/packages/core-components/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/core-components",
- "version": "0.17.1",
+ "version": "0.17.2-next.0",
"description": "Core components used by Backstage plugins and apps",
"backstage": {
"role": "web-library"
diff --git a/packages/core-components/report.api.md b/packages/core-components/report.api.md
index 6e1057c5a3..90fa9ced22 100644
--- a/packages/core-components/report.api.md
+++ b/packages/core-components/report.api.md
@@ -750,6 +750,7 @@ export interface LogViewerProps {
root?: string;
};
text: string;
+ textWrap?: boolean;
}
// Warning: (ae-forgotten-export) The symbol "Props_8" needs to be exported by the entry point index.d.ts
diff --git a/packages/core-components/src/components/AutoLogout/AutoLogout.tsx b/packages/core-components/src/components/AutoLogout/AutoLogout.tsx
index b328705e51..932ac7432b 100644
--- a/packages/core-components/src/components/AutoLogout/AutoLogout.tsx
+++ b/packages/core-components/src/components/AutoLogout/AutoLogout.tsx
@@ -42,7 +42,7 @@ type AutoLogoutTrackableEvent = EventsType;
export type AutoLogoutProps = {
/**
* Enable/disable the AutoLogoutMechanism.
- * defauls to true.
+ * defaults to true.
*/
enabled?: boolean;
/**
diff --git a/packages/core-components/src/components/Chip/Chip.stories.tsx b/packages/core-components/src/components/Chip/Chip.stories.tsx
index 4423a2ca58..a20a6d5699 100644
--- a/packages/core-components/src/components/Chip/Chip.stories.tsx
+++ b/packages/core-components/src/components/Chip/Chip.stories.tsx
@@ -58,7 +58,7 @@ export default {
export const Default = (args: ChipProps) => ;
Default.args = defaultArgs;
-export const Deleteable = (args: ChipProps) => (
+export const Deletable = (args: ChipProps) => (
({})} />
);
-Deleteable.args = defaultArgs;
+Deletable.args = defaultArgs;
diff --git a/packages/core-components/src/components/LogViewer/LogLine.tsx b/packages/core-components/src/components/LogViewer/LogLine.tsx
index 4ec7e21d2f..8b11d7b9de 100644
--- a/packages/core-components/src/components/LogViewer/LogLine.tsx
+++ b/packages/core-components/src/components/LogViewer/LogLine.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { useMemo } from 'react';
+import { useEffect, useMemo, useRef } from 'react';
import { AnsiChunk, AnsiLine, ChunkModifiers } from './AnsiProcessor';
import startCase from 'lodash/startCase';
import classnames from 'classnames';
@@ -159,6 +159,7 @@ export interface LogLineProps {
classes: ReturnType;
searchText: string;
highlightResultIndex?: number;
+ setRowHeight?: (index: number, size: number) => void;
}
export function LogLine({
@@ -166,12 +167,20 @@ export function LogLine({
classes,
searchText,
highlightResultIndex,
+ setRowHeight,
}: LogLineProps) {
+ const lineRef = useRef(null);
const chunks = useMemo(
() => calculateHighlightedChunks(line, searchText),
[line, searchText],
);
+ useEffect(() => {
+ if (lineRef.current && setRowHeight) {
+ setRowHeight(line.lineNumber, lineRef.current.offsetHeight);
+ }
+ }, [line.lineNumber, setRowHeight]);
+
const elements = useMemo(
() =>
chunks.map(({ text, modifiers, highlight }, index) => (
@@ -184,13 +193,14 @@ export function LogLine({
(highlight === highlightResultIndex
? classes.textSelectedHighlight
: classes.textHighlight),
+ { [classes.textWrap]: !!setRowHeight },
)}
>
{text}
)),
- [chunks, highlightResultIndex, classes],
+ [chunks, highlightResultIndex, classes, setRowHeight],
);
- return <>{elements}>;
+ return {elements} ;
}
diff --git a/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx b/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx
index 86a2b2ebcd..984477a6f9 100644
--- a/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx
+++ b/packages/core-components/src/components/LogViewer/LogViewer.stories.tsx
@@ -83,3 +83,9 @@ export const ExampleLogViewer = () => (
);
+
+export const WithTextWrap = () => (
+
+
+
+);
diff --git a/packages/core-components/src/components/LogViewer/LogViewer.tsx b/packages/core-components/src/components/LogViewer/LogViewer.tsx
index afe20b9dc9..26ddecfcfc 100644
--- a/packages/core-components/src/components/LogViewer/LogViewer.tsx
+++ b/packages/core-components/src/components/LogViewer/LogViewer.tsx
@@ -33,6 +33,10 @@ export interface LogViewerProps {
* The LogViewer component is optimized for appending content at the end of the text.
*/
text: string;
+ /**
+ * Determines if the overflow text should be wrapped or shown via a single line in a horizontal scrollbar.
+ */
+ textWrap?: boolean;
/**
* Styling overrides for classes within the LogViewer component.
*/
diff --git a/packages/core-components/src/components/LogViewer/RealLogViewer.tsx b/packages/core-components/src/components/LogViewer/RealLogViewer.tsx
index 8430ac4bd0..e384a8e882 100644
--- a/packages/core-components/src/components/LogViewer/RealLogViewer.tsx
+++ b/packages/core-components/src/components/LogViewer/RealLogViewer.tsx
@@ -18,10 +18,10 @@ import Box from '@material-ui/core/Box';
import IconButton from '@material-ui/core/IconButton';
import CopyIcon from '@material-ui/icons/FileCopy';
import classnames from 'classnames';
-import { useEffect, useMemo, useState } from 'react';
+import { useEffect, useMemo, useRef, useState } from 'react';
import { useLocation } from 'react-router-dom';
import AutoSizer from 'react-virtualized-auto-sizer';
-import { FixedSizeList } from 'react-window';
+import { VariableSizeList, FixedSizeList } from 'react-window';
import { AnsiLine, AnsiProcessor } from './AnsiProcessor';
import { LogLine } from './LogLine';
@@ -32,14 +32,17 @@ import { useLogViewerSelection } from './useLogViewerSelection';
export interface RealLogViewerProps {
text: string;
+ textWrap?: boolean;
classes?: { root?: string };
}
export function RealLogViewer(props: RealLogViewerProps) {
const classes = useStyles({ classes: props.classes });
- const [fixedListInstance, setFixedListInstance] = useState | null>(null);
+ const [listInstance, setListInstance] = useState<
+ VariableSizeList | FixedSizeList | null
+ >(null);
+ const shouldTextWrap = props.textWrap ?? false;
+ const heights = useRef<{ [key: number]: number }>({});
// The processor keeps state that optimizes appending to the text
const processor = useMemo(() => new AnsiProcessor(), []);
@@ -50,21 +53,21 @@ export function RealLogViewer(props: RealLogViewerProps) {
const location = useLocation();
useEffect(() => {
- if (fixedListInstance) {
- fixedListInstance.scrollToItem(lines.length - 1, 'end');
+ if (listInstance) {
+ listInstance.scrollToItem(lines.length - 1, 'end');
}
- }, [fixedListInstance, lines]);
+ }, [listInstance, lines]);
useEffect(() => {
- if (!fixedListInstance) {
+ if (!listInstance) {
return;
}
if (search.resultLine) {
- fixedListInstance.scrollToItem(search.resultLine - 1, 'center');
+ listInstance.scrollToItem(search.resultLine - 1, 'center');
} else {
- fixedListInstance.scrollToItem(lines.length - 1, 'end');
+ listInstance.scrollToItem(lines.length - 1, 'end');
}
- }, [fixedListInstance, search.resultLine, lines]);
+ }, [listInstance, search.resultLine, lines]);
useEffect(() => {
if (location.hash) {
@@ -81,70 +84,103 @@ export function RealLogViewer(props: RealLogViewerProps) {
selection.setSelection(line, event.shiftKey);
};
+ function setRowHeight(index: number, size: number) {
+ if (shouldTextWrap && listInstance) {
+ (listInstance as VariableSizeList).resetAfterIndex(0);
+ // lineNumber is 1-based but index is 0-based
+ heights.current[index - 1] = size;
+ }
+ }
+
+ function getRowHeight(index: number) {
+ return heights.current[index] || 20;
+ }
+
return (
- {({ height, width }: { height?: number; width?: number }) => (
-
-
-
-
- ) => {
- setFixedListInstance(instance);
- }}
- className={classes.log}
- height={(height || 480) - HEADER_SIZE}
- width={width || 640}
- itemData={search.lines}
- itemSize={20}
- itemCount={search.lines.length}
- >
- {({ index, style, data }) => {
- const line = data[index];
- const { lineNumber } = line;
- return (
- {
+ const commonProps = {
+ ref: setListInstance,
+ className: classes.log,
+ height: (height || 480) - HEADER_SIZE,
+ width: width || 640,
+ itemData: search.lines,
+ itemCount: search.lines.length,
+ };
+
+ const renderItem = ({
+ index,
+ style,
+ data,
+ }: {
+ index: number;
+ style: React.CSSProperties;
+ data: AnsiLine[];
+ }) => {
+ const line = data[index];
+ const { lineNumber } = line;
+ return (
+
+ {selection.shouldShowButton(lineNumber) && (
+ selection.copySelection()}
>
- {selection.shouldShowButton(lineNumber) && (
- selection.copySelection()}
- >
-
-
- )}
- handleSelectLine(lineNumber, event)}
- onKeyPress={event => handleSelectLine(lineNumber, event)}
- >
- {lineNumber}
-
-
-
- );
- }}
-
-
- )}
+
+
+ )}
+ handleSelectLine(lineNumber, event)}
+ onKeyPress={event => handleSelectLine(lineNumber, event)}
+ >
+ {lineNumber}
+
+
+
+ );
+ };
+
+ return (
+
+
+
+
+ {shouldTextWrap ? (
+
+ {...commonProps}
+ itemSize={getRowHeight}
+ >
+ {renderItem}
+
+ ) : (
+ {...commonProps} itemSize={20}>
+ {renderItem}
+
+ )}
+
+ );
+ }}
);
}
diff --git a/packages/core-components/src/components/LogViewer/styles.ts b/packages/core-components/src/components/LogViewer/styles.ts
index 2028809543..76e045ca32 100644
--- a/packages/core-components/src/components/LogViewer/styles.ts
+++ b/packages/core-components/src/components/LogViewer/styles.ts
@@ -66,10 +66,13 @@ export const useStyles = makeStyles(
log: {
fontFamily: '"Monaco", monospace',
fontSize: theme.typography.pxToRem(12),
+ lineHeight: '20px',
},
line: {
position: 'relative',
whiteSpace: 'pre',
+ display: 'flex',
+ alignItems: 'flex-start',
'&:hover': {
background: theme.palette.action.hover,
@@ -93,6 +96,7 @@ export const useStyles = makeStyles(
width: 60,
marginRight: theme.spacing(1),
cursor: 'pointer',
+ flexShrink: 0,
},
textHighlight: {
background: alpha(theme.palette.info.main, 0.15),
@@ -163,6 +167,10 @@ export const useStyles = makeStyles(
modifierBackgroundGrey: {
background: colors.grey[500],
},
+ textWrap: {
+ whiteSpace: 'pre-wrap',
+ wordBreak: 'break-all',
+ },
}),
{ name: 'BackstageLogViewer' },
);
diff --git a/packages/core-components/src/hooks/useQueryParamState.ts b/packages/core-components/src/hooks/useQueryParamState.ts
index 16cd9e18c8..498cd7143e 100644
--- a/packages/core-components/src/hooks/useQueryParamState.ts
+++ b/packages/core-components/src/hooks/useQueryParamState.ts
@@ -58,7 +58,7 @@ type SetQueryParams = (params: T) => void;
export function useQueryParamState(
stateName: string,
- /** @deprecated Don't configure a custom debouceTime */
+ /** @deprecated Don't configure a custom debounceTime */
debounceTime: number = 250,
): [T | undefined, SetQueryParams] {
const [searchParams, setSearchParams] = useSearchParams();
diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx
index a73c0375cd..00fe6f96ae 100644
--- a/packages/core-components/src/layout/Sidebar/Bar.tsx
+++ b/packages/core-components/src/layout/Sidebar/Bar.tsx
@@ -41,15 +41,21 @@ import { coreComponentsTranslationRef } from '../../translation';
export type SidebarClassKey = 'drawer' | 'drawerOpen';
const useStyles = makeStyles(
theme => ({
- drawer: {
- display: 'flex',
- flexFlow: 'column nowrap',
- alignItems: 'flex-start',
- position: 'fixed',
+ root: {
left: 0,
top: 0,
bottom: 0,
zIndex: theme.zIndex.appBar,
+ position: 'fixed',
+ },
+ drawer: {
+ display: 'flex',
+ flexFlow: 'column nowrap',
+ alignItems: 'flex-start',
+ left: 0,
+ top: 0,
+ bottom: 0,
+ position: 'absolute',
background: theme.palette.navigation.background,
overflowX: 'hidden',
msOverflowStyle: 'none',
diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md
index 5d94e4d92a..2587a880a8 100644
--- a/packages/create-app/CHANGELOG.md
+++ b/packages/create-app/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/create-app
+## 0.6.2-next.1
+
+### Patch Changes
+
+- Bumped create-app version.
+- Updated dependencies
+ - @backstage/cli-common@0.1.15
+
+## 0.6.2-next.0
+
+### Patch Changes
+
+- Bumped create-app version.
+- Updated dependencies
+ - @backstage/cli-common@0.1.15
+
## 0.6.1
### Patch Changes
diff --git a/packages/create-app/package.json b/packages/create-app/package.json
index 21c5447edf..015a84409f 100644
--- a/packages/create-app/package.json
+++ b/packages/create-app/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/create-app",
- "version": "0.6.1",
+ "version": "0.6.2-next.1",
"description": "A CLI that helps you create your own Backstage app",
"backstage": {
"role": "cli"
diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md
index 00255dcd3c..5d3eeaf067 100644
--- a/packages/dev-utils/CHANGELOG.md
+++ b/packages/dev-utils/CHANGELOG.md
@@ -1,5 +1,33 @@
# @backstage/dev-utils
+## 1.1.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/app-defaults@1.6.2-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+
+## 1.1.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/app-defaults@1.6.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/theme@0.6.5
+
## 1.1.9
### Patch Changes
diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json
index e0964234cf..9bdc8a25ac 100644
--- a/packages/dev-utils/package.json
+++ b/packages/dev-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/dev-utils",
- "version": "1.1.9",
+ "version": "1.1.10-next.1",
"description": "Utilities for developing Backstage plugins.",
"backstage": {
"role": "web-library"
diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md
index 4b77247d38..5575b8f05b 100644
--- a/packages/e2e-test/CHANGELOG.md
+++ b/packages/e2e-test/CHANGELOG.md
@@ -1,5 +1,23 @@
# e2e-test
+## 0.2.28-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.6.2-next.1
+ - @backstage/cli-common@0.1.15
+ - @backstage/errors@1.2.7
+
+## 0.2.28-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.6.2-next.0
+ - @backstage/cli-common@0.1.15
+ - @backstage/errors@1.2.7
+
## 0.2.27
### Patch Changes
diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json
index 1b41846206..a27bdcc4b3 100644
--- a/packages/e2e-test/package.json
+++ b/packages/e2e-test/package.json
@@ -1,6 +1,6 @@
{
"name": "e2e-test",
- "version": "0.2.27",
+ "version": "0.2.28-next.1",
"description": "E2E test for verifying Backstage packages",
"backstage": {
"role": "cli"
diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md
index 7c126a305b..95c44da351 100644
--- a/packages/frontend-app-api/CHANGELOG.md
+++ b/packages/frontend-app-api/CHANGELOG.md
@@ -1,5 +1,34 @@
# @backstage/frontend-app-api
+## 0.11.2-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/frontend-defaults@0.2.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
+## 0.11.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-defaults@0.2.2-next.0
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
## 0.11.1
### Patch Changes
diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json
index 43b3f0a7e1..e64b6b8b2c 100644
--- a/packages/frontend-app-api/package.json
+++ b/packages/frontend-app-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-app-api",
- "version": "0.11.1",
+ "version": "0.11.2-next.1",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-app-api/src/routing/collectRouteIds.test.ts b/packages/frontend-app-api/src/routing/collectRouteIds.test.ts
index d74ab43e33..395886f982 100644
--- a/packages/frontend-app-api/src/routing/collectRouteIds.test.ts
+++ b/packages/frontend-app-api/src/routing/collectRouteIds.test.ts
@@ -35,7 +35,7 @@ describe('collectRouteIds', () => {
const collected = collectRouteIds([
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
routes: { ref },
externalRoutes: { extRef },
}),
diff --git a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts
index 3f4f84c1ce..29e8767fb3 100644
--- a/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts
+++ b/packages/frontend-app-api/src/routing/extractRouteInfoFromAppNode.test.ts
@@ -81,7 +81,7 @@ function createTestExtension(options: {
function routeInfoFromExtensions(extensions: ExtensionDefinition[]) {
const plugin = createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions,
});
diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts
index a80c4cb35b..c486370563 100644
--- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts
+++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.test.ts
@@ -100,7 +100,7 @@ describe('resolveAppNodeSpecs', () => {
it('should override attachment points', () => {
const b = makeExt('b');
const pluginA = createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [makeExtDef('a')],
});
expect(
@@ -135,7 +135,7 @@ describe('resolveAppNodeSpecs', () => {
const a = makeExt('test/a');
const b = makeExt('test/b');
const plugin = createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [makeExtDef('a'), makeExtDef('b')],
});
expect(
@@ -182,7 +182,7 @@ describe('resolveAppNodeSpecs', () => {
const b = makeExt('b', 'disabled');
expect(
resolveAppNodeSpecs({
- features: [createFrontendPlugin({ id: 'empty', extensions: [] })],
+ features: [createFrontendPlugin({ pluginId: 'empty', extensions: [] })],
builtinExtensions: [a, b],
parameters: [
{
@@ -221,7 +221,7 @@ describe('resolveAppNodeSpecs', () => {
const g = makeExt('g', 'disabled');
expect(
resolveAppNodeSpecs({
- features: [createFrontendPlugin({ id: 'empty', extensions: [] })],
+ features: [createFrontendPlugin({ pluginId: 'empty', extensions: [] })],
builtinExtensions: [a, b, c, d, e, f, g],
parameters: [
{ id: 'e', disabled: false },
@@ -277,7 +277,7 @@ describe('resolveAppNodeSpecs', () => {
it('should apply module overrides', () => {
const plugin = createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [makeExtDef('a'), makeExtDef('b')],
});
const aOverride = makeExt('test/a', 'enabled', 'other');
@@ -329,7 +329,7 @@ describe('resolveAppNodeSpecs', () => {
const result = resolveAppNodeSpecs({
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
makeExtDef('a', 'disabled'),
makeExtDef('b', 'disabled'),
@@ -364,7 +364,7 @@ describe('resolveAppNodeSpecs', () => {
resolveAppNodeSpecs({
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [makeExtDef('forbidden')],
}),
],
@@ -382,7 +382,7 @@ describe('resolveAppNodeSpecs', () => {
resolveAppNodeSpecs({
features: [
createFrontendPlugin({
- id: 'forbidden',
+ pluginId: 'forbidden',
extensions: [],
}),
createFrontendModule({
diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx
index 96450f06fb..00551e16e3 100644
--- a/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx
+++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.test.tsx
@@ -45,7 +45,7 @@ describe('createSpecializedApp', () => {
const app = createSpecializedApp({
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
createExtension({
attachTo: { id: 'root', input: 'app' },
@@ -66,7 +66,7 @@ describe('createSpecializedApp', () => {
const app = createSpecializedApp({
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
createExtension({
attachTo: { id: 'root', input: 'app' },
@@ -78,7 +78,7 @@ describe('createSpecializedApp', () => {
],
}),
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
createExtension({
attachTo: { id: 'root', input: 'app' },
@@ -102,7 +102,7 @@ describe('createSpecializedApp', () => {
config: mockApis.config({ data: { test: 'foo' } }),
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
createExtension({
attachTo: { id: 'root', input: 'app' },
@@ -128,7 +128,7 @@ describe('createSpecializedApp', () => {
const app = createSpecializedApp({
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
featureFlags: [{ name: 'a' }, { name: 'b' }],
extensions: [
createExtension({
@@ -244,13 +244,13 @@ describe('createSpecializedApp', () => {
`);
});
- it('should intitialize the APIs in the correct order to allow for overrides', () => {
+ it('should initialize the APIs in the correct order to allow for overrides', () => {
const mockAnalyticsApi = jest.fn(() => ({ captureEvent: jest.fn() }));
const app = createSpecializedApp({
features: [
createFrontendPlugin({
- id: 'first',
+ pluginId: 'first',
extensions: [
ApiBlueprint.make({
params: {
@@ -266,7 +266,7 @@ describe('createSpecializedApp', () => {
],
}),
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
featureFlags: [{ name: 'a' }, { name: 'b' }],
extensions: [
createExtension({
@@ -321,7 +321,7 @@ describe('createSpecializedApp', () => {
]),
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
createExtension({
attachTo: { id: 'root', input: 'app' },
@@ -368,7 +368,7 @@ describe('createSpecializedApp', () => {
const { tree } = createSpecializedApp({
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
createExtension({
attachTo: { id: 'root', input: 'app' },
@@ -446,7 +446,7 @@ describe('createSpecializedApp', () => {
const extRouteRef = createExternalRouteRef();
const pluginA = createFrontendPlugin({
- id: 'a',
+ pluginId: 'a',
externalRoutes: {
ext: extRouteRef,
},
@@ -489,7 +489,7 @@ describe('createSpecializedApp', () => {
],
});
const pluginB = createFrontendPlugin({
- id: 'b',
+ pluginId: 'b',
routes: {
root: routeRef,
},
@@ -531,7 +531,7 @@ describe('createSpecializedApp', () => {
createSpecializedApp({
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
createExtension({
name: 'root',
@@ -611,7 +611,7 @@ describe('createSpecializedApp', () => {
const app = createSpecializedApp({
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
createExtension({
attachTo: { id: 'root', input: 'app' },
diff --git a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx
index 21fde6547b..1a4c7dbc78 100644
--- a/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx
+++ b/packages/frontend-app-api/src/wiring/createSpecializedApp.tsx
@@ -195,7 +195,7 @@ class RouteResolutionApiProxy implements RouteResolutionApi {
/**
* Creates an empty app without any default features. This is a low-level API is
- * intended for use in tests or specialized setups. Typically wou want to use
+ * intended for use in tests or specialized setups. Typically you want to use
* `createApp` from `@backstage/frontend-defaults` instead.
*
* @public
diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md
index eb340c0b06..9dc4b7be1c 100644
--- a/packages/frontend-defaults/CHANGELOG.md
+++ b/packages/frontend-defaults/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/frontend-defaults
+## 0.2.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/plugin-app@0.1.9-next.1
+ - @backstage/frontend-app-api@0.11.2-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## 0.2.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-app-api@0.11.2-next.0
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/plugin-app@0.1.9-next.0
+
## 0.2.1
### Patch Changes
diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json
index 90da1ad843..15285842b6 100644
--- a/packages/frontend-defaults/package.json
+++ b/packages/frontend-defaults/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-defaults",
- "version": "0.2.1",
+ "version": "0.2.2-next.1",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-defaults/src/createApp.test.tsx b/packages/frontend-defaults/src/createApp.test.tsx
index 569416fca7..4fd1ba4c5b 100644
--- a/packages/frontend-defaults/src/createApp.test.tsx
+++ b/packages/frontend-defaults/src/createApp.test.tsx
@@ -55,7 +55,7 @@ describe('createApp', () => {
}),
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
ThemeBlueprint.make({
name: 'derp',
@@ -84,7 +84,7 @@ describe('createApp', () => {
configLoader: async () => ({ config: mockApis.config() }),
features: [
createFrontendPlugin({
- id: duplicatedFeatureId,
+ pluginId: duplicatedFeatureId,
extensions: [
PageBlueprint.make({
params: {
@@ -95,7 +95,7 @@ describe('createApp', () => {
],
}),
createFrontendPlugin({
- id: duplicatedFeatureId,
+ pluginId: duplicatedFeatureId,
extensions: [
PageBlueprint.make({
params: {
@@ -128,7 +128,7 @@ describe('createApp', () => {
return {
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
PageBlueprint.make({
params: {
@@ -193,7 +193,7 @@ describe('createApp', () => {
],
}),
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
featureFlags: [{ name: 'test-1' }],
extensions: [
createExtension({
@@ -219,7 +219,7 @@ describe('createApp', () => {
],
}),
createFrontendPlugin({
- id: 'other',
+ pluginId: 'other',
featureFlags: [{ name: 'test-2' }],
extensions: [],
}),
@@ -241,7 +241,7 @@ describe('createApp', () => {
features: [
appPlugin,
createFrontendPlugin({
- id: 'my-plugin',
+ pluginId: 'my-plugin',
extensions: [
PageBlueprint.make({
params: {
@@ -402,7 +402,7 @@ describe('createApp', () => {
features: [
appPlugin,
createFrontendPlugin({
- id: 'test-plugin',
+ pluginId: 'test-plugin',
extensions: [
PageBlueprint.make({
name: 'test-page',
diff --git a/packages/frontend-defaults/src/discovery.test.ts b/packages/frontend-defaults/src/discovery.test.ts
index 52a2fcde11..d1738554a4 100644
--- a/packages/frontend-defaults/src/discovery.test.ts
+++ b/packages/frontend-defaults/src/discovery.test.ts
@@ -45,7 +45,7 @@ describe('discoverAvailableFeatures', () => {
});
it('should discover a plugin', () => {
- const testPlugin = createFrontendPlugin({ id: 'test' });
+ const testPlugin = createFrontendPlugin({ pluginId: 'test' });
globalSpy.mockReturnValue({
modules: [{ default: testPlugin }],
});
@@ -86,9 +86,9 @@ describe('discoverAvailableFeatures', () => {
});
it('should discover multiple plugins', () => {
- const test1Plugin = createFrontendPlugin({ id: 'test1' });
- const test2Plugin = createFrontendPlugin({ id: 'test2' });
- const test3Plugin = createFrontendPlugin({ id: 'test3' });
+ const test1Plugin = createFrontendPlugin({ pluginId: 'test1' });
+ const test2Plugin = createFrontendPlugin({ pluginId: 'test2' });
+ const test3Plugin = createFrontendPlugin({ pluginId: 'test3' });
globalSpy.mockReturnValue({
modules: [
{ default: test1Plugin },
diff --git a/packages/frontend-defaults/src/resolution.test.ts b/packages/frontend-defaults/src/resolution.test.ts
index 609b975a33..b39e302b24 100644
--- a/packages/frontend-defaults/src/resolution.test.ts
+++ b/packages/frontend-defaults/src/resolution.test.ts
@@ -38,7 +38,7 @@ describe('resolveAsyncFeatures', () => {
config: mockApis.config(),
features: [
createFrontendPlugin({
- id: 'test-feature',
+ pluginId: 'test-feature',
extensions: [
PageBlueprint.make({
params: {
@@ -80,7 +80,7 @@ describe('resolveAsyncFeatures', () => {
return {
features: [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
PageBlueprint.make({
params: {
@@ -145,7 +145,7 @@ describe('resolveAsyncFeatures', () => {
async loader({ config: _ }) {
return [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
PageBlueprint.make({
params: {
diff --git a/packages/frontend-dynamic-feature-loader/CHANGELOG.md b/packages/frontend-dynamic-feature-loader/CHANGELOG.md
index f0de9ebf3f..dbbda03911 100644
--- a/packages/frontend-dynamic-feature-loader/CHANGELOG.md
+++ b/packages/frontend-dynamic-feature-loader/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/frontend-dynamic-feature-loader
+## 0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/config@1.3.2
+
## 0.1.0
### Minor Changes
diff --git a/packages/frontend-dynamic-feature-loader/package.json b/packages/frontend-dynamic-feature-loader/package.json
index b10b3985cc..3513ac9cc5 100644
--- a/packages/frontend-dynamic-feature-loader/package.json
+++ b/packages/frontend-dynamic-feature-loader/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-dynamic-feature-loader",
- "version": "0.1.0",
+ "version": "0.1.1-next.0",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-dynamic-feature-loader/src/loader.test.tsx b/packages/frontend-dynamic-feature-loader/src/loader.test.tsx
index 87ddc7770f..4b872a5414 100644
--- a/packages/frontend-dynamic-feature-loader/src/loader.test.tsx
+++ b/packages/frontend-dynamic-feature-loader/src/loader.test.tsx
@@ -191,7 +191,7 @@ describe('dynamicFrontendFeaturesLoader', () => {
mocks.federation.get.mockReturnValue({
default: createFrontendPlugin({
- id: 'test-plugin',
+ pluginId: 'test-plugin',
extensions: [],
}),
});
@@ -311,13 +311,13 @@ describe('dynamicFrontendFeaturesLoader', () => {
mocks.federation.get.mockReturnValueOnce({
default: createFrontendPlugin({
- id: 'plugin-1',
+ pluginId: 'plugin-1',
extensions: [],
}),
});
mocks.federation.get.mockReturnValueOnce({
default: createFrontendPlugin({
- id: 'plugin-2',
+ pluginId: 'plugin-2',
extensions: [],
}),
});
@@ -428,13 +428,13 @@ describe('dynamicFrontendFeaturesLoader', () => {
mocks.federation.get.mockReturnValueOnce({
default: createFrontendPlugin({
- id: 'test-plugin',
+ pluginId: 'test-plugin',
extensions: [],
}),
});
mocks.federation.get.mockReturnValueOnce({
default: createFrontendPlugin({
- id: 'test-plugin-alpha',
+ pluginId: 'test-plugin-alpha',
extensions: [],
}),
});
@@ -527,7 +527,7 @@ describe('dynamicFrontendFeaturesLoader', () => {
mocks.federation.get.mockReturnValueOnce({
default: createFrontendPlugin({
- id: 'test-plugin',
+ pluginId: 'test-plugin',
extensions: [],
}),
});
@@ -591,7 +591,7 @@ describe('dynamicFrontendFeaturesLoader', () => {
mocks.federation.get.mockReturnValue({
default: createFrontendPlugin({
- id: 'test-plugin',
+ pluginId: 'test-plugin',
extensions: [],
}),
});
@@ -640,7 +640,7 @@ describe('dynamicFrontendFeaturesLoader', () => {
mocks.federation.get.mockReturnValue({
default: createFrontendPlugin({
- id: 'test-plugin',
+ pluginId: 'test-plugin',
extensions: [],
}),
});
@@ -746,7 +746,7 @@ describe('dynamicFrontendFeaturesLoader', () => {
mocks.federation.get.mockReturnValueOnce(undefined);
mocks.federation.get.mockReturnValueOnce({
default: createFrontendPlugin({
- id: 'plugin-2',
+ pluginId: 'plugin-2',
extensions: [],
}),
});
@@ -876,7 +876,7 @@ describe('dynamicFrontendFeaturesLoader', () => {
});
mocks.federation.get.mockReturnValueOnce({
default: createFrontendPlugin({
- id: 'plugin-2',
+ pluginId: 'plugin-2',
extensions: [],
}),
});
@@ -989,7 +989,7 @@ describe('dynamicFrontendFeaturesLoader', () => {
mocks.federation.get.mockReturnValueOnce({
default: createFrontendPlugin({
- id: 'plugin-2',
+ pluginId: 'plugin-2',
extensions: [],
}),
});
@@ -1099,7 +1099,7 @@ describe('dynamicFrontendFeaturesLoader', () => {
mocks.federation.get.mockReturnValueOnce({
default: createFrontendPlugin({
- id: 'plugin-2',
+ pluginId: 'plugin-2',
extensions: [],
}),
});
diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md
index 0944b03aca..42e102807a 100644
--- a/packages/frontend-internal/CHANGELOG.md
+++ b/packages/frontend-internal/CHANGELOG.md
@@ -1,5 +1,14 @@
# @internal/frontend
+## 0.0.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
## 0.0.8
### Patch Changes
diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json
index a13b9296d2..70276bf9c6 100644
--- a/packages/frontend-internal/package.json
+++ b/packages/frontend-internal/package.json
@@ -1,6 +1,6 @@
{
"name": "@internal/frontend",
- "version": "0.0.8",
+ "version": "0.0.9-next.0",
"backstage": {
"role": "web-library",
"inline": true
diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md
index b07e8f12df..a43fcec1d8 100644
--- a/packages/frontend-plugin-api/CHANGELOG.md
+++ b/packages/frontend-plugin-api/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/frontend-plugin-api
+## 0.10.2-next.0
+
+### Patch Changes
+
+- fb58f20: The `id` option of `createFrontendPlugin` has been renamed to `pluginId` in order to better align with similar APIs in the frontend and backend systems.
+
+ The old `id` option is deprecated and will be removed in a future release.
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
## 0.10.1
### Patch Changes
diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json
index 4b74be026a..5877a3800c 100644
--- a/packages/frontend-plugin-api/package.json
+++ b/packages/frontend-plugin-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-plugin-api",
- "version": "0.10.1",
+ "version": "0.10.2-next.0",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md
index 0d29eec03f..cd5d89401d 100644
--- a/packages/frontend-plugin-api/report.api.md
+++ b/packages/frontend-plugin-api/report.api.md
@@ -774,6 +774,25 @@ export function createFrontendPlugin<
MakeSortedExtensionsMap
>;
+// @public @deprecated (undocumented)
+export function createFrontendPlugin<
+ TId extends string,
+ TRoutes extends AnyRoutes = {},
+ TExternalRoutes extends AnyExternalRoutes = {},
+ TExtensions extends readonly ExtensionDefinition[] = [],
+>(
+ options: Omit<
+ PluginOptions,
+ 'pluginId'
+ > & {
+ id: string;
+ },
+): FrontendPlugin<
+ TRoutes,
+ TExternalRoutes,
+ MakeSortedExtensionsMap
+>;
+
// @public
export function createRouteRef<
TParams extends
@@ -1495,7 +1514,7 @@ export interface PluginOptions<
// (undocumented)
featureFlags?: FeatureFlagConfig[];
// (undocumented)
- id: TId;
+ pluginId: TId;
// (undocumented)
routes?: TRoutes;
}
diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts
index 07a1bfcdb0..00dd981e26 100644
--- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts
+++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts
@@ -669,7 +669,7 @@ describe('createExtension', () => {
},
});
- const overriden = testExtension.override({
+ const overridden = testExtension.override({
config: {
schema: {
bar: z => z.string().default('hello'),
@@ -684,12 +684,12 @@ describe('createExtension', () => {
},
});
- expect(createExtensionTester(overriden).get(stringDataRef)).toBe(
+ expect(createExtensionTester(overridden).get(stringDataRef)).toBe(
'foo-boom-override-hello',
);
expect(
- createExtensionTester(overriden, {
+ createExtensionTester(overridden, {
config: { foo: 'hello', bar: 'world' },
}).get(stringDataRef),
).toBe('foo-hello-override-world');
diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.test.ts
index e8e2e90bf7..63ce4cb284 100644
--- a/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.test.ts
+++ b/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.test.ts
@@ -60,7 +60,7 @@ describe('createFrontendFeatureLoader', () => {
);
return [
createFrontendPlugin({
- id: `${pluginIdPrefix}-1`,
+ pluginId: `${pluginIdPrefix}-1`,
extensions: [
createExtension({
name: '1',
@@ -76,7 +76,7 @@ describe('createFrontendFeatureLoader', () => {
],
}) as FrontendFeature | FrontendFeatureLoader,
createFrontendPlugin({
- id: `${pluginIdPrefix}-2`,
+ pluginId: `${pluginIdPrefix}-2`,
extensions: [
createExtension({
name: '2',
@@ -92,7 +92,7 @@ describe('createFrontendFeatureLoader', () => {
],
}) as FrontendFeature | FrontendFeatureLoader,
createFrontendPlugin({
- id: `${pluginIdPrefix}-output`,
+ pluginId: `${pluginIdPrefix}-output`,
extensions: [
createExtension({
name: 'output',
@@ -166,7 +166,7 @@ describe('createFrontendFeatureLoader', () => {
async loader(_) {
return [
createFrontendPlugin({
- id: 'plugin-0',
+ pluginId: 'plugin-0',
extensions: [
createExtension({
name: '0',
@@ -184,7 +184,7 @@ describe('createFrontendFeatureLoader', () => {
createFrontendFeatureLoader({
async *loader(__) {
yield createFrontendPlugin({
- id: 'plugin-1',
+ pluginId: 'plugin-1',
extensions: [
createExtension({
name: '1',
@@ -202,7 +202,7 @@ describe('createFrontendFeatureLoader', () => {
yield createFrontendFeatureLoader({
loader: async ___ => [
createFrontendPlugin({
- id: 'plugin-2',
+ pluginId: 'plugin-2',
extensions: [
createExtension({
name: '2',
@@ -222,7 +222,7 @@ describe('createFrontendFeatureLoader', () => {
},
}),
createFrontendPlugin({
- id: 'plugin-output',
+ pluginId: 'plugin-output',
extensions: [
createExtension({
name: 'output',
@@ -278,7 +278,7 @@ describe('createFrontendFeatureLoader', () => {
[
nestedFeatureLoaderHolder.loader,
createFrontendPlugin({
- id: 'plugin',
+ pluginId: 'plugin',
extensions: [
createExtension({
name: 'output',
@@ -319,7 +319,7 @@ describe('createFrontendFeatureLoader', () => {
it('should support multiple output formats', async () => {
const feature = createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
});
const dynamicFeature = Promise.resolve({ default: feature });
@@ -411,7 +411,7 @@ describe('createFrontendFeatureLoader', () => {
it('should limit feature loading recursion', async () => {
const plugin = createFrontendPlugin({
- id: 'plugin',
+ pluginId: 'plugin',
extensions: [
createExtension({
name: 'output',
diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts
index 6553157885..359f9da417 100644
--- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts
+++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.test.ts
@@ -134,6 +134,13 @@ function createTestAppRoot({
describe('createFrontendPlugin', () => {
it('should create an empty plugin', () => {
+ const plugin = createFrontendPlugin({ pluginId: 'test' });
+
+ expect(plugin).toBeDefined();
+ expect(String(plugin)).toBe('Plugin{id=test}');
+ });
+
+ it('should create an empty plugin with deprecated id option', () => {
const plugin = createFrontendPlugin({ id: 'test' });
expect(plugin).toBeDefined();
@@ -142,7 +149,7 @@ describe('createFrontendPlugin', () => {
it('should create a plugin with extension instances', async () => {
const plugin = createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [Extension1, Extension2, outputExtension],
});
expect(plugin).toBeDefined();
@@ -189,7 +196,7 @@ describe('createFrontendPlugin', () => {
it('should create a plugin with nested extension instances', async () => {
const plugin = createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [Extension1, Extension2, Extension3, Child, outputExtension],
});
expect(plugin).toBeDefined();
@@ -221,7 +228,7 @@ describe('createFrontendPlugin', () => {
it('should create a plugin with nested extension instances and multiple children', async () => {
const plugin = createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
Extension1,
Extension2,
@@ -254,14 +261,14 @@ describe('createFrontendPlugin', () => {
it('should throw on duplicate extensions', async () => {
expect(() =>
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [Extension1, Extension1],
}),
).toThrow("Plugin 'test' provided duplicate extensions: test/1");
expect(() =>
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [
Extension1,
Extension2,
@@ -277,7 +284,7 @@ describe('createFrontendPlugin', () => {
describe('overrides', () => {
it('should return a plugin instance with the correct namespace', () => {
const plugin = createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [Extension1, Extension2],
});
@@ -308,7 +315,7 @@ describe('createFrontendPlugin', () => {
it('should allow overriding extensions that have a matching ID, while keeping old extensions that do not have overlapping IDs', async () => {
const plugin = createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions: [Extension1, Extension2, outputExtension],
});
diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts
index 09ff96a4e6..b7c530a0c0 100644
--- a/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts
+++ b/packages/frontend-plugin-api/src/wiring/createFrontendPlugin.ts
@@ -51,7 +51,7 @@ export interface PluginOptions<
TExternalRoutes extends AnyExternalRoutes,
TExtensions extends readonly ExtensionDefinition[],
> {
- id: TId;
+ pluginId: TId;
routes?: TRoutes;
externalRoutes?: TExternalRoutes;
extensions?: TExtensions;
@@ -70,7 +70,49 @@ export function createFrontendPlugin<
TRoutes,
TExternalRoutes,
MakeSortedExtensionsMap
+>;
+/**
+ * @public
+ * @deprecated The `id` option is deprecated, use `pluginId` instead.
+ */
+export function createFrontendPlugin<
+ TId extends string,
+ TRoutes extends AnyRoutes = {},
+ TExternalRoutes extends AnyExternalRoutes = {},
+ TExtensions extends readonly ExtensionDefinition[] = [],
+>(
+ options: Omit<
+ PluginOptions,
+ 'pluginId'
+ > & { id: string },
+): FrontendPlugin<
+ TRoutes,
+ TExternalRoutes,
+ MakeSortedExtensionsMap
+>;
+export function createFrontendPlugin<
+ TId extends string,
+ TRoutes extends AnyRoutes = {},
+ TExternalRoutes extends AnyExternalRoutes = {},
+ TExtensions extends readonly ExtensionDefinition[] = [],
+>(
+ options:
+ | PluginOptions
+ | (Omit<
+ PluginOptions,
+ 'pluginId'
+ > & { id: string }),
+): FrontendPlugin<
+ TRoutes,
+ TExternalRoutes,
+ MakeSortedExtensionsMap
> {
+ const pluginId = 'pluginId' in options ? options.pluginId : options.id;
+ if (!pluginId) {
+ throw new Error(
+ "Either 'id' or 'pluginId' must be provided to createFrontendPlugin",
+ );
+ }
const extensions = new Array>();
const extensionDefinitionsById = new Map<
string,
@@ -79,11 +121,11 @@ export function createFrontendPlugin<
for (const def of options.extensions ?? []) {
const internal = OpaqueExtensionDefinition.toInternal(def);
- const ext = resolveExtensionDefinition(def, { namespace: options.id });
+ const ext = resolveExtensionDefinition(def, { namespace: pluginId });
extensions.push(ext);
extensionDefinitionsById.set(ext.id, {
...internal,
- namespace: options.id,
+ namespace: pluginId,
});
}
@@ -96,14 +138,14 @@ export function createFrontendPlugin<
);
// TODO(Rugvip): This could provide some more information about the kind + name of the extensions
throw new Error(
- `Plugin '${options.id}' provided duplicate extensions: ${duplicates.join(
+ `Plugin '${pluginId}' provided duplicate extensions: ${duplicates.join(
', ',
)}`,
);
}
return OpaqueFrontendPlugin.createInstance('v1', {
- id: options.id,
+ id: pluginId,
routes: options.routes ?? ({} as TRoutes),
externalRoutes: options.externalRoutes ?? ({} as TExternalRoutes),
featureFlags: options.featureFlags ?? [],
@@ -112,28 +154,29 @@ export function createFrontendPlugin<
const ext = extensionDefinitionsById.get(id);
if (!ext) {
throw new Error(
- `Attempted to get non-existent extension '${id}' from plugin '${options.id}'`,
+ `Attempted to get non-existent extension '${id}' from plugin '${pluginId}'`,
);
}
return ext;
},
toString() {
- return `Plugin{id=${options.id}}`;
+ return `Plugin{id=${pluginId}}`;
},
withOverrides(overrides) {
const overriddenExtensionIds = new Set(
overrides.extensions.map(
- e => resolveExtensionDefinition(e, { namespace: options.id }).id,
+ e => resolveExtensionDefinition(e, { namespace: pluginId }).id,
),
);
const nonOverriddenExtensions = (options.extensions ?? []).filter(
e =>
!overriddenExtensionIds.has(
- resolveExtensionDefinition(e, { namespace: options.id }).id,
+ resolveExtensionDefinition(e, { namespace: pluginId }).id,
),
);
return createFrontendPlugin({
...options,
+ pluginId,
extensions: [...nonOverriddenExtensions, ...overrides.extensions],
});
},
diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md
index ac34834d1e..38048e3fd3 100644
--- a/packages/frontend-test-utils/CHANGELOG.md
+++ b/packages/frontend-test-utils/CHANGELOG.md
@@ -1,5 +1,32 @@
# @backstage/frontend-test-utils
+## 0.3.2-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/plugin-app@0.1.9-next.1
+ - @backstage/frontend-app-api@0.11.2-next.1
+ - @backstage/test-utils@1.7.8-next.0
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
+## 0.3.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/frontend-app-api@0.11.2-next.0
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/test-utils@1.7.7
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-app@0.1.9-next.0
+
## 0.3.1
### Patch Changes
diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json
index 9fdbb3c49e..5bdefbbb49 100644
--- a/packages/frontend-test-utils/package.json
+++ b/packages/frontend-test-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-test-utils",
- "version": "0.3.1",
+ "version": "0.3.2-next.1",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx
index 84171646df..d4010a92ad 100644
--- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx
+++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx
@@ -198,7 +198,7 @@ export function renderInTestApp(
const features: FrontendFeature[] = [
createFrontendPlugin({
- id: 'test',
+ pluginId: 'test',
extensions,
}),
appPluginOverride,
diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md
index 06e18b8fd4..a3440c8d2c 100644
--- a/packages/integration-react/CHANGELOG.md
+++ b/packages/integration-react/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/integration-react
+## 1.2.7-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.6
+
+## 1.2.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.6
+
## 1.2.6
### Patch Changes
diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json
index 1c69110f77..f49fc4dc69 100644
--- a/packages/integration-react/package.json
+++ b/packages/integration-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/integration-react",
- "version": "1.2.6",
+ "version": "1.2.7-next.1",
"description": "Frontend package for managing integrations towards external systems",
"backstage": {
"role": "web-library"
diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md
index c9a96d88ed..535d154f6e 100644
--- a/packages/integration/CHANGELOG.md
+++ b/packages/integration/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/integration
+## 1.16.4-next.1
+
+### Patch Changes
+
+- acea1d4: update documentation
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## 1.16.4-next.0
+
+### Patch Changes
+
+- f3381d3: Added missing `organizations` property to `azure` section in `config.d.ts` file
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 1.16.3
### Patch Changes
diff --git a/packages/integration/package.json b/packages/integration/package.json
index 7663044452..53a380b1c9 100644
--- a/packages/integration/package.json
+++ b/packages/integration/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/integration",
- "version": "1.16.3",
+ "version": "1.16.4-next.1",
"description": "Helpers for managing integrations towards external systems",
"backstage": {
"role": "common-library"
diff --git a/packages/integration/src/gitlab/core.ts b/packages/integration/src/gitlab/core.ts
index c7f8dd036f..4a17b2a15e 100644
--- a/packages/integration/src/gitlab/core.ts
+++ b/packages/integration/src/gitlab/core.ts
@@ -49,6 +49,7 @@ export async function getGitLabFileFetchUrl(
* Gets the request options necessary to make requests to a given provider.
*
* @param config - The relevant provider config
+ * @param token - An optional auth token to use for communicating with GitLab. By default uses the integration token
* @public
*/
export function getGitLabRequestOptions(
diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md
index ed4488353d..8dcd1341f7 100644
--- a/packages/repo-tools/CHANGELOG.md
+++ b/packages/repo-tools/CHANGELOG.md
@@ -1,5 +1,30 @@
# @backstage/repo-tools
+## 0.13.3-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/cli-node@0.2.13
+ - @backstage/errors@1.2.7
+
+## 0.13.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/cli-node@0.2.13
+ - @backstage/config-loader@1.10.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/errors@1.2.7
+
## 0.13.2
### Patch Changes
diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json
index 154cb015aa..a8481d81c9 100644
--- a/packages/repo-tools/package.json
+++ b/packages/repo-tools/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/repo-tools",
- "version": "0.13.2",
+ "version": "0.13.3-next.1",
"description": "CLI for Backstage repo tooling ",
"backstage": {
"role": "cli"
diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts
index b737d918bd..7515feb8a4 100644
--- a/packages/repo-tools/src/commands/index.ts
+++ b/packages/repo-tools/src/commands/index.ts
@@ -187,7 +187,7 @@ export function registerCommands(program: Command) {
)
.option(
'-o, --omit-messages ',
- 'select some message code to be omited on the API Extractor (comma separated values i.e ae-cyclic-inherit-doc,ae-missing-getter )',
+ 'select some message code to be omitted on the API Extractor (comma separated values i.e ae-cyclic-inherit-doc,ae-missing-getter )',
)
.option(
'--validate-release-tags',
diff --git a/packages/scaffolder-internal/CHANGELOG.md b/packages/scaffolder-internal/CHANGELOG.md
index 60c28d708c..78d4f59df4 100644
--- a/packages/scaffolder-internal/CHANGELOG.md
+++ b/packages/scaffolder-internal/CHANGELOG.md
@@ -1,5 +1,21 @@
# @internal/scaffolder
+## 0.0.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/plugin-scaffolder-react@1.16.0-next.1
+
+## 0.0.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-react@1.16.0-next.0
+ - @backstage/frontend-plugin-api@0.10.1
+
## 0.0.8
### Patch Changes
diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json
index ee7634ac85..44e409cb48 100644
--- a/packages/scaffolder-internal/package.json
+++ b/packages/scaffolder-internal/package.json
@@ -1,6 +1,6 @@
{
"name": "@internal/scaffolder",
- "version": "0.0.8",
+ "version": "0.0.9-next.1",
"backstage": {
"role": "web-library",
"inline": true
diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md
index 256f6e60a9..570bfbd446 100644
--- a/packages/techdocs-cli-embedded-app/CHANGELOG.md
+++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md
@@ -1,5 +1,43 @@
# techdocs-cli-embedded-app
+## 0.2.108-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/cli@0.32.1-next.1
+ - @backstage/plugin-techdocs@1.12.6-next.1
+ - @backstage/plugin-catalog@1.29.1-next.1
+ - @backstage/plugin-techdocs-react@1.2.17-next.0
+ - @backstage/app-defaults@1.6.2-next.0
+ - @backstage/test-utils@1.7.8-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+
+## 0.2.108-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli@0.32.1-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/plugin-techdocs@1.12.6-next.0
+ - @backstage/app-defaults@1.6.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/test-utils@1.7.7
+ - @backstage/theme@0.6.5
+ - @backstage/plugin-catalog@1.29.1-next.0
+ - @backstage/plugin-techdocs-react@1.2.16
+
## 0.2.107
### Patch Changes
diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json
index 18c1528a0b..875c80a2b1 100644
--- a/packages/techdocs-cli-embedded-app/package.json
+++ b/packages/techdocs-cli-embedded-app/package.json
@@ -1,6 +1,6 @@
{
"name": "techdocs-cli-embedded-app",
- "version": "0.2.107",
+ "version": "0.2.108-next.1",
"backstage": {
"role": "frontend"
},
diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md
index 603bab2e80..a116a77b24 100644
--- a/packages/techdocs-cli/CHANGELOG.md
+++ b/packages/techdocs-cli/CHANGELOG.md
@@ -1,5 +1,27 @@
# @techdocs/cli
+## 1.9.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-techdocs-node@1.13.3-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+
+## 1.9.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/plugin-techdocs-node@1.13.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+
## 1.9.2
### Patch Changes
diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json
index 1f2c9b662c..3ff6938e9c 100644
--- a/packages/techdocs-cli/package.json
+++ b/packages/techdocs-cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@techdocs/cli",
- "version": "1.9.2",
+ "version": "1.9.3-next.1",
"description": "Utility CLI for managing TechDocs sites in Backstage.",
"backstage": {
"role": "cli"
diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md
index 1a70ba6827..42bfd5315e 100644
--- a/packages/test-utils/CHANGELOG.md
+++ b/packages/test-utils/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/test-utils
+## 1.7.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/config@1.3.2
+ - @backstage/core-app-api@1.16.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
## 1.7.7
### Patch Changes
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
index e3caa6c277..fae2069bbc 100644
--- a/packages/test-utils/package.json
+++ b/packages/test-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/test-utils",
- "version": "1.7.7",
+ "version": "1.7.8-next.0",
"description": "Utilities to test Backstage plugins and apps.",
"backstage": {
"role": "web-library"
diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md
index bb2c6c2b70..e7e3b98236 100644
--- a/packages/theme/CHANGELOG.md
+++ b/packages/theme/CHANGELOG.md
@@ -1,5 +1,11 @@
# @backstage/theme
+## 0.6.6-next.0
+
+### Patch Changes
+
+- 1b14572: Show arrow when MuiTableSortLabel receives focus
+
## 0.6.5
### Patch Changes
diff --git a/packages/theme/package.json b/packages/theme/package.json
index dfe53db843..acf9b891d4 100644
--- a/packages/theme/package.json
+++ b/packages/theme/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/theme",
- "version": "0.6.5",
+ "version": "0.6.6-next.0",
"description": "material-ui theme for use with Backstage.",
"backstage": {
"role": "web-library"
diff --git a/packages/theme/src/v5/defaultComponentThemes.ts b/packages/theme/src/v5/defaultComponentThemes.ts
index 55547a191c..1d07c318a0 100644
--- a/packages/theme/src/v5/defaultComponentThemes.ts
+++ b/packages/theme/src/v5/defaultComponentThemes.ts
@@ -162,6 +162,10 @@ export const defaultComponentThemes: ThemeOptions['components'] = {
'&:focus': {
color: 'inherit',
},
+ // Targets the arrow when the label is focused
+ '&:focus svg': {
+ opacity: 0.5,
+ },
// Bold font for highlighting selected column
'&.Mui-active': {
fontWeight: 'bold',
diff --git a/packages/yarn-plugin/CHANGELOG.md b/packages/yarn-plugin/CHANGELOG.md
index 6b7ba0c9fc..41ebade0a3 100644
--- a/packages/yarn-plugin/CHANGELOG.md
+++ b/packages/yarn-plugin/CHANGELOG.md
@@ -1,5 +1,14 @@
# yarn-plugin-backstage
+## 0.0.5-next.0
+
+### Patch Changes
+
+- fd70d86: Add both `npm:` and `backstage:` ranges to the lockfile to ensure compatibility with tools that parse the lockfile and ensure dependencies stay locked when building dist workspaces.
+- Updated dependencies
+ - @backstage/cli-common@0.1.15
+ - @backstage/release-manifests@0.0.12
+
## 0.0.4
### Patch Changes
diff --git a/packages/yarn-plugin/README.md b/packages/yarn-plugin/README.md
index c1d5647b8e..073669b9a5 100644
--- a/packages/yarn-plugin/README.md
+++ b/packages/yarn-plugin/README.md
@@ -38,3 +38,47 @@ The plugin can be manually tested in any repository running at least yarn 4.1.1.
Sadly it can't be manually tested directly in the Backstage monorepo - since
packages in this repository use `workspace:^` dependencies, there's no use case
for the yarn plugin.
+
+## Architecture
+
+This section is intended for people working directly on this package. It
+describes the architecture of the plugin, and the means by which it manages npm
+package versions.
+
+The Backstage yarn plugin operates on `backstage:^` version ranges in
+package.json files using the following three components:
+
+### `reduceDependency` hook
+
+_Converts `backstage:^` to `backstage:^::backstage=1.34.0&npm=1.2.3`_
+
+This hook is called by yarn when resolving direct and indirect dependencies in
+the workspace, and allows modifying the version range. The yarn plugin uses this
+hook to parameterize `backstage:^` ranges with the current Backstage version and
+the corresponding npm package version from the manifest. This uses the system
+built into yarn for adding parameters to version ranges.
+
+### `BackstageNpmResolver`
+
+_Resolves the appropriate npm package for `backstage:^` ranges and adds the
+`npm` range as a dependency_
+
+The `BackstageNpmResolver` ensures that the lockfile contains entries for _both_
+the `backstage:^` range, and the corresponding `npm:^` range. Including
+an entry for the `backstage:^` range means that tools that reconcile the
+lockfile and package.json can match entries for Backstage packages together.
+Including an entry for the corresponding `npm:` range ensures that dependencies
+are not unlocked when switching between `backstage:^` and `npm:` ranges in the
+lockfile, as happens when publishing the package or building a dist workspace
+using `backstage-cli build-workspace`.
+
+### `beforeWorkspacePacking` hook
+
+_Replaces `backstage:^` ranges with the corresponding npm version ranges when
+packing packages for publishing_
+
+The yarn plugin is strictly optional, and intended to be opted-into in a
+specific Backstage repository. As such, when publishing packages, all
+`backstage:^` versions should be removed from the package.json and replaced with
+the appropriate npm version ranges. This is handled by the
+`beforeWorkspacePacking` hook.
diff --git a/packages/yarn-plugin/package.json b/packages/yarn-plugin/package.json
index 742b9bbe93..78d34c2788 100644
--- a/packages/yarn-plugin/package.json
+++ b/packages/yarn-plugin/package.json
@@ -1,6 +1,6 @@
{
"name": "yarn-plugin-backstage",
- "version": "0.0.4",
+ "version": "0.0.5-next.0",
"description": "Yarn plugin for working with Backstage monorepos",
"backstage": {
"role": "node-library"
@@ -32,17 +32,19 @@
"dependencies": {
"@backstage/cli-common": "workspace:^",
"@backstage/release-manifests": "workspace:^",
- "@yarnpkg/core": "^4.0.3",
- "@yarnpkg/fslib": "^3.0.2",
- "@yarnpkg/plugin-pack": "^4.0.0",
+ "@yarnpkg/core": "^4.4.0",
+ "@yarnpkg/fslib": "^3.1.2",
+ "@yarnpkg/plugin-npm": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch",
+ "@yarnpkg/plugin-pack": "^4.0.1",
"semver": "^7.6.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
- "@yarnpkg/builder": "^4.0.0",
+ "@yarnpkg/builder": "^4.2.1",
"fs-extra": "^11.2.0",
"nodemon": "^3.0.1",
+ "snyk-nodejs-lockfile-parser": "^1.58.14",
"yaml": "^2.0.0"
}
}
diff --git a/packages/yarn-plugin/src/handlers/reduceDependency.test.ts b/packages/yarn-plugin/src/handlers/reduceDependency.test.ts
index dfe29485b6..86b9037a74 100644
--- a/packages/yarn-plugin/src/handlers/reduceDependency.test.ts
+++ b/packages/yarn-plugin/src/handlers/reduceDependency.test.ts
@@ -107,7 +107,7 @@ describe('reduceDependency', () => {
),
project,
),
- ).rejects.toThrow(/unexpected version selector/i);
+ ).rejects.toThrow(/invalid backstage: version range/i);
},
);
@@ -127,20 +127,31 @@ describe('reduceDependency', () => {
);
});
- it('replaces the range with the corresponding npm package range', async () => {
- await expect(
- reduceDependency(
- structUtils.makeDescriptor(
- structUtils.makeIdent('backstage', 'core'),
- 'backstage:^',
- ),
- project,
- ),
- ).resolves.toEqual(
+ it('adds the current Backstage version as a parameter on the range', async () => {
+ const result = await reduceDependency(
structUtils.makeDescriptor(
structUtils.makeIdent('backstage', 'core'),
- 'npm:^6.7.8',
+ 'backstage:^',
),
+ project,
+ );
+
+ await expect(
+ structUtils.parseRange(result.range).params?.backstage,
+ ).toEqual('1.23.45');
+ });
+
+ it('adds the appropriate npm package version based on the Backstage manifest as a parameter on the range', async () => {
+ const result = await reduceDependency(
+ structUtils.makeDescriptor(
+ structUtils.makeIdent('backstage', 'core'),
+ 'backstage:^',
+ ),
+ project,
+ );
+
+ await expect(structUtils.parseRange(result.range).params?.npm).toEqual(
+ '6.7.8',
);
});
diff --git a/packages/yarn-plugin/src/handlers/reduceDependency.ts b/packages/yarn-plugin/src/handlers/reduceDependency.ts
index e00a39c27c..8182256ad4 100644
--- a/packages/yarn-plugin/src/handlers/reduceDependency.ts
+++ b/packages/yarn-plugin/src/handlers/reduceDependency.ts
@@ -15,7 +15,7 @@
*/
import { Descriptor, Project, structUtils } from '@yarnpkg/core';
-import { getPackageVersion } from '../util';
+import { getCurrentBackstageVersion, getPackageVersion } from '../util';
import { PROTOCOL } from '../constants';
export const reduceDependency = async (
@@ -24,12 +24,18 @@ export const reduceDependency = async (
) => {
const range = structUtils.parseRange(dependency.range);
- if (range.protocol === PROTOCOL) {
- return structUtils.makeDescriptor(
- dependency,
- `npm:^${await getPackageVersion(dependency, project.configuration)}`,
+ if (range.protocol !== PROTOCOL) {
+ return dependency;
+ }
+
+ if (range.selector !== '^') {
+ throw new Error(
+ `Invalid backstage: version range found: ${dependency.range}`,
);
}
- return dependency;
+ return structUtils.bindDescriptor(dependency, {
+ backstage: getCurrentBackstageVersion(),
+ npm: await getPackageVersion(dependency, project.configuration),
+ });
};
diff --git a/packages/yarn-plugin/src/index.test.ts b/packages/yarn-plugin/src/index.test.ts
index d58387bca9..75f120c157 100644
--- a/packages/yarn-plugin/src/index.test.ts
+++ b/packages/yarn-plugin/src/index.test.ts
@@ -18,6 +18,7 @@ import { join as joinPath } from 'path';
import { spawn, SpawnOptionsWithoutStdio } from 'child_process';
import fs from 'fs-extra';
import yaml from 'yaml';
+import { buildDepTreeFromFiles } from 'snyk-nodejs-lockfile-parser';
import { findPaths } from '@backstage/cli-common';
import { createMockDirectory } from '@backstage/backend-test-utils';
@@ -66,10 +67,10 @@ const runYarnInstall = () =>
const nonWorkspaceEntries = (lockFileString: string = '') => {
const lockFile = yaml.parse(lockFileString);
- const result: Record = {};
+ const result = [];
for (const key of Object.keys(lockFile)) {
if (lockFile[key].version !== '0.0.0-use.local') {
- result[key] = lockFile[key];
+ result.push(lockFile[key]);
}
}
@@ -231,15 +232,17 @@ describe('Backstage yarn plugin', () => {
'utf-8',
);
- const lockFile = yaml.parse(lockFileContent);
+ const descriptors = Object.keys(yaml.parse(lockFileContent)).flatMap(
+ entry => entry.split(', '),
+ );
// Versions from old manifest no longer appear in lockfile
- expect(lockFile['@backstage/cli-common@npm:^0.1.1']).toBeUndefined();
- expect(lockFile['@backstage/config@npm:^0.1.2']).toBeUndefined();
+ expect(descriptors).not.toContain('@backstage/cli-common@npm:^0.1.1');
+ expect(descriptors).not.toContain('@backstage/config@npm:^0.1.2');
// Versions from new manifest have been added to lockfile
- expect(lockFile['@backstage/cli-common@npm:^0.1.8']).toBeDefined();
- expect(lockFile['@backstage/config@npm:^1.0.0']).toBeDefined();
+ expect(descriptors).toContain('@backstage/cli-common@npm:^0.1.8');
+ expect(descriptors).toContain('@backstage/config@npm:^1.0.0');
});
describe('after removing backstage:^ dependencies', () => {
@@ -301,6 +304,23 @@ describe('Backstage yarn plugin', () => {
});
},
);
+
+ it('successfully parses the lockfile with snyk-nodejs-lockfile-parser', async () => {
+ await expect(
+ buildDepTreeFromFiles(
+ mockDir.path,
+ 'packages/b/package.json',
+ 'yarn.lock',
+ ),
+ ).resolves.toEqual(
+ expect.objectContaining({
+ dependencies: expect.objectContaining({
+ '@backstage/cli-common': expect.anything(),
+ '@backstage/config': expect.anything(),
+ }),
+ }),
+ );
+ });
});
});
});
diff --git a/packages/yarn-plugin/src/index.ts b/packages/yarn-plugin/src/index.ts
index dcf2fe6943..ad18deb0ff 100644
--- a/packages/yarn-plugin/src/index.ts
+++ b/packages/yarn-plugin/src/index.ts
@@ -24,6 +24,7 @@
import { Plugin, Hooks, semverUtils, YarnVersion } from '@yarnpkg/core';
import { Hooks as PackHooks } from '@yarnpkg/plugin-pack';
import { beforeWorkspacePacking, reduceDependency } from './handlers';
+import { BackstageNpmResolver } from './resolvers';
// All dependencies of the yarn plugin are bundled during the build. Chalk
// triples the size of the plugin bundle when included, so we avoid the
@@ -48,6 +49,7 @@ const plugin: Plugin = {
reduceDependency,
beforeWorkspacePacking,
},
+ resolvers: [BackstageNpmResolver],
};
export default plugin;
diff --git a/packages/yarn-plugin/src/resolvers/BackstageNpmResolver.test.ts b/packages/yarn-plugin/src/resolvers/BackstageNpmResolver.test.ts
new file mode 100644
index 0000000000..46621fe12b
--- /dev/null
+++ b/packages/yarn-plugin/src/resolvers/BackstageNpmResolver.test.ts
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2025 The 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 { ResolveOptions, structUtils } from '@yarnpkg/core';
+import { BackstageNpmResolver } from './BackstageNpmResolver';
+import { NpmSemverResolver } from '@yarnpkg/plugin-npm';
+
+const mockGetCandidates = jest.fn();
+const mockGetSatisfying = jest.fn();
+jest.mock('@yarnpkg/plugin-npm', () => {
+ return {
+ ...jest.requireActual('@yarnpkg/plugin-npm'),
+ NpmSemverResolver: function MockNpmSemverResolver() {
+ return {
+ getCandidates: mockGetCandidates,
+ getSatisfying: mockGetSatisfying,
+ };
+ } as unknown as NpmSemverResolver,
+ };
+});
+
+/*
+ * Copyright 2025 The 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.
+ */
+describe('BackstageNpmResolver', () => {
+ const ident = structUtils.makeIdent('backstage', 'core');
+
+ afterEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('getCandidates', () => {
+ it('should throw an error if the npm param is missing from the descriptor', async () => {
+ const resolver = new BackstageNpmResolver();
+
+ const descriptor = structUtils.bindDescriptor(
+ structUtils.makeDescriptor(ident, 'backstage:^'),
+ { backstage: '1.0.0' },
+ );
+
+ await expect(
+ resolver.getCandidates(descriptor, undefined!, undefined!),
+ ).rejects.toThrow(/Missing npm parameter/);
+ });
+
+ it('should call the npm resolver with the correct descriptor', async () => {
+ const resolver = new BackstageNpmResolver();
+
+ mockGetCandidates.mockResolvedValue([
+ structUtils.makeLocator(ident, '1.2.5'),
+ structUtils.makeLocator(ident, '1.2.4'),
+ structUtils.makeLocator(ident, '1.2.3'),
+ ]);
+
+ const descriptor = structUtils.bindDescriptor(
+ structUtils.makeDescriptor(ident, 'backstage:^'),
+ { backstage: '1.0.0', npm: '1.2.3' },
+ );
+
+ const deps = {};
+ const opts = {};
+
+ await expect(
+ resolver.getCandidates(descriptor, deps, opts as ResolveOptions),
+ ).resolves.toEqual([
+ structUtils.makeLocator(ident, '1.2.5'),
+ structUtils.makeLocator(ident, '1.2.4'),
+ structUtils.makeLocator(ident, '1.2.3'),
+ ]);
+
+ expect(mockGetCandidates).toHaveBeenCalledWith(
+ structUtils.makeDescriptor(descriptor, 'npm:^1.2.3'),
+ deps,
+ opts,
+ );
+ });
+ });
+
+ describe('getResolutionDependencies', () => {
+ it('should return the correct resolution dependencies', () => {
+ const resolver = new BackstageNpmResolver();
+
+ const descriptor = structUtils.bindDescriptor(
+ structUtils.makeDescriptor(ident, 'backstage:^'),
+ { backstage: '1.0.0', npm: '1.2.3' },
+ );
+
+ expect(resolver.getResolutionDependencies(descriptor)).toEqual({
+ '@backstage/core': {
+ descriptorHash: expect.any(String),
+ identHash: descriptor.identHash,
+ name: 'core',
+ range: 'npm:^1.2.3',
+ scope: 'backstage',
+ },
+ });
+ });
+ });
+
+ describe('getSatisfying', () => {
+ it('should return the correct satisfying locator', async () => {
+ const resolver = new BackstageNpmResolver();
+
+ mockGetSatisfying.mockResolvedValue({
+ locators: [
+ structUtils.makeLocator(ident, '1.2.5'),
+ structUtils.makeLocator(ident, '1.2.4'),
+ structUtils.makeLocator(ident, '1.2.3'),
+ ],
+ sorted: false,
+ });
+
+ const descriptor = structUtils.bindDescriptor(
+ structUtils.makeDescriptor(ident, 'backstage:^'),
+ { backstage: '1.0.0', npm: '1.2.3' },
+ );
+
+ await expect(
+ resolver.getSatisfying(descriptor, {}, [], {} as ResolveOptions),
+ ).resolves.toEqual({
+ locators: [
+ structUtils.makeLocator(ident, '1.2.5'),
+ structUtils.makeLocator(ident, '1.2.4'),
+ structUtils.makeLocator(ident, '1.2.3'),
+ ],
+ sorted: false,
+ });
+
+ expect(mockGetSatisfying).toHaveBeenCalledWith(
+ {
+ descriptorHash: expect.any(String),
+ identHash: ident.identHash,
+ name: 'core',
+ range: 'npm:^1.2.3',
+ scope: 'backstage',
+ },
+ {},
+ [],
+ {} as ResolveOptions,
+ );
+ });
+ });
+});
diff --git a/packages/yarn-plugin/src/resolvers/BackstageNpmResolver.ts b/packages/yarn-plugin/src/resolvers/BackstageNpmResolver.ts
new file mode 100644
index 0000000000..cd38a8c5c6
--- /dev/null
+++ b/packages/yarn-plugin/src/resolvers/BackstageNpmResolver.ts
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2024 The 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 {
+ structUtils,
+ Descriptor,
+ Locator,
+ Package,
+ Resolver,
+ ResolveOptions,
+} from '@yarnpkg/core';
+import { NpmSemverResolver } from '@yarnpkg/plugin-npm';
+import { PROTOCOL } from '../constants';
+
+export class BackstageNpmResolver implements Resolver {
+ static protocol = PROTOCOL;
+
+ /**
+ * Target only descriptors using the `backstage:` protocol
+ */
+ supportsDescriptor = (descriptor: Descriptor) =>
+ descriptor.range.startsWith(BackstageNpmResolver.protocol);
+
+ /**
+ * We treat any `backstage:` descriptor as if it's targeting the npm package
+ * version from the manifest for the current version of Backstage, by pulling
+ * in the `NpmSemverResolver` and deferring to its `getCandidates` method.
+ *
+ * The version itself comes from the `npm` parameter on the incoming
+ * descriptor, which is set by the `reduceDependency` hook.
+ */
+ async getCandidates(
+ descriptor: Descriptor,
+ dependencies: Record,
+ opts: ResolveOptions,
+ ): Promise {
+ const npmVersion = structUtils.parseRange(descriptor.range).params?.npm;
+
+ if (!npmVersion || Array.isArray(npmVersion)) {
+ throw new Error(
+ `Missing npm parameter on backstage: range "${descriptor.range}"`,
+ );
+ }
+
+ return new NpmSemverResolver().getCandidates(
+ structUtils.makeDescriptor(descriptor, `npm:^${npmVersion}`),
+ dependencies,
+ opts,
+ );
+ }
+
+ /**
+ * We insert the `npm:^` descriptor as an additional dependency to
+ * ensure that dependencies remain locked when adding and removing the plugin
+ * from repositories. This is relevant for example when building packed
+ * production-like workspaces for testing using `backstage-cli
+ * build-workspace`.
+ */
+ getResolutionDependencies(
+ descriptor: Descriptor,
+ ): Record {
+ const npmVersion = structUtils.parseRange(descriptor.range).params?.npm;
+
+ if (!npmVersion) {
+ throw new Error(
+ `Missing npm parameter on backstage: range "${descriptor.range}".`,
+ );
+ }
+
+ return {
+ [structUtils.stringifyIdent(descriptor)]: structUtils.makeDescriptor(
+ descriptor,
+ `npm:^${npmVersion}`,
+ ),
+ };
+ }
+
+ /**
+ * This method is called when deduplicating locators. We first convert any
+ * `backstage:` locators into the corresponding `npm:` locator, and then defer
+ * to the implementation from `NpmSemverResolver`.
+ */
+ async getSatisfying(
+ descriptor: Descriptor,
+ dependencies: Record,
+ locators: Array,
+ opts: ResolveOptions,
+ ) {
+ let npmDescriptor = descriptor;
+ const range = structUtils.parseRange(npmDescriptor.range);
+
+ if (range.protocol === PROTOCOL) {
+ const npmVersion = range.params?.npm;
+ npmDescriptor = structUtils.makeDescriptor(
+ descriptor,
+ `npm:^${npmVersion}`,
+ );
+ }
+
+ return new NpmSemverResolver().getSatisfying(
+ npmDescriptor,
+ dependencies,
+ locators,
+ opts,
+ );
+ }
+
+ /**
+ * Stub - no descriptor binding is needed in this resolver (note though that
+ * it does rely on the binding performed in the `reduceDependency` hook).
+ */
+ bindDescriptor = (descriptor: Descriptor) => descriptor;
+
+ /**
+ * This plugin does not need to support any locators itself, since the
+ * `getCandidates` method will always convert `backstage:` versions into
+ * `npm:` versions which are resolved using the `NpmSemverResolver`.
+ */
+ supportsLocator = () => false;
+
+ /**
+ * This method should never be called, since all emitted locators use the
+ * `npm:` protocol.
+ */
+ shouldPersistResolution = () => {
+ throw new Error(
+ 'Unreachable: BackstageNpmResolver should never persist resolution as it uses npm: protocol',
+ );
+ };
+
+ /**
+ * This method should never be called, since all emitted locators use the
+ * `npm:` protocol.
+ */
+ resolve = async () => {
+ throw new Error(
+ 'Unreachable: BackstageNpmResolver should never resolve as it uses npm: protocol',
+ );
+ };
+}
diff --git a/packages/cli/src/commands/start/index.ts b/packages/yarn-plugin/src/resolvers/index.ts
similarity index 84%
rename from packages/cli/src/commands/start/index.ts
rename to packages/yarn-plugin/src/resolvers/index.ts
index 680fe9e11d..3452967e80 100644
--- a/packages/cli/src/commands/start/index.ts
+++ b/packages/yarn-plugin/src/resolvers/index.ts
@@ -1,5 +1,5 @@
/*
- * Copyright 2022 The Backstage Authors
+ * Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -13,5 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-export { command } from './command';
+export { BackstageNpmResolver } from './BackstageNpmResolver';
diff --git a/packages/yarn-plugin/src/util/index.ts b/packages/yarn-plugin/src/util/index.ts
index 139c033468..fd8bc397d6 100644
--- a/packages/yarn-plugin/src/util/index.ts
+++ b/packages/yarn-plugin/src/util/index.ts
@@ -14,4 +14,5 @@
* limitations under the License.
*/
+export { getCurrentBackstageVersion } from './getCurrentBackstageVersion';
export { getPackageVersion } from './getPackageVersion';
diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md
index 27029694a1..78358a8970 100644
--- a/plugins/api-docs/CHANGELOG.md
+++ b/plugins/api-docs/CHANGELOG.md
@@ -1,5 +1,37 @@
# @backstage/plugin-api-docs
+## 0.12.7-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-catalog@1.29.1-next.1
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## 0.12.7-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/plugin-catalog@1.29.1-next.0
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-react@0.4.33
+
## 0.12.6
### Patch Changes
diff --git a/plugins/api-docs/README-alpha.md b/plugins/api-docs/README-alpha.md
index ddff429135..5cdd386b89 100644
--- a/plugins/api-docs/README-alpha.md
+++ b/plugins/api-docs/README-alpha.md
@@ -77,12 +77,12 @@ To link that a component provides or consumes an API, see the [`providesApis`](h
# Shows a table of components that provides a particular api
- entity-card:api-docs/providing-components:
config:
- # Presenting the card ony for entites of kind api
+ # Presenting the card only for entities of kind api
filter: kind:api
# Shows a table of components that consumes a particular api
- entity-card:api-docs/consuming-components:
config:
- # Presenting the card ony for entites of kind api
+ # Presenting the card only for entities of kind api
filter: kind:api
# Enabling some contents
# The contents will be displayed in the same order it appears in this setting list
@@ -285,7 +285,7 @@ export default createFrontendModule({
pluginId: 'api-docs',
extensions: [
createPageExtension({
- // Ommitting name since we are overriding a plugin index page
+ // Omitting name since we are overriding a plugin index page
// It's up to you whether to use the original default path or not, but links that are hardcoded to the default path won't work if you change it
defaultPath: '/api-docs',
// Associating the page with a different route ref may result in the sidebar item or external plugin route pointing to an unreachable page
@@ -370,7 +370,7 @@ export default createFrontendModule({
createEntityCardExtension({
// Name is necessary so the system knows that this extension will override the default 'has-apis' entity card extension provided by the 'api-docs' plugin
name: 'has-apis',
- // Returing a custom card component
+ // Returning a custom card component
loader: () =>
import('./components').then(m => ),
}),
@@ -443,7 +443,7 @@ export default createFrontendModule({
createEntityCardExtension({
// Name is necessary so the system knows that this extension will override the default 'definition' entity card extension provided by the 'api-docs' plugin
name: 'definition',
- // Returing a custom card component
+ // Returning a custom card component
loader: () =>
import('./components').then(m => ),
}),
@@ -516,7 +516,7 @@ export default createFrontendModule({
createEntityCardExtension({
// Name is necessary so the system knows that this extension will override the default 'provided-apis' entity card extension provided by the 'api-docs' plugin
name: 'provided-apis',
- // Returing a custom card component
+ // Returning a custom card component
loader: () =>
import('./components').then(m => ),
}),
@@ -589,7 +589,7 @@ export default createFrontendModule({
createEntityCardExtension({
// Name is necessary so the system knows that this extension will override the default 'consumed-apis' entity card extension provided by the 'api-docs' plugin
name: 'consumed-apis',
- // Returing a custom card component
+ // Returning a custom card component
loader: () =>
import('./components').then(m => ),
}),
@@ -662,7 +662,7 @@ export default createFrontendModule({
createEntityCardExtension({
// Name is necessary so the system knows that this extension will override the default 'providing-components' entity card extension provided by the 'api-docs' plugin
name: 'providing-components',
- // Returing a custom card component
+ // Returning a custom card component
loader: () =>
import('./components').then(m => (
@@ -737,7 +737,7 @@ export default createFrontendModule({
createEntityCardExtension({
// Name is necessary so the system knows that this extension will override the default 'consuming-components' entity card extension provided by the 'api-docs' plugin
name: 'consuming-components',
- // Returing a custom card component
+ // Returning a custom card component
loader: () =>
import('./components').then(m => (
@@ -906,7 +906,7 @@ export default createFrontendModule({
createEntityContentExtension({
// Name is necessary so the system knows that this extension will override the default 'apis' entity content extension provided by the 'api-docs' plugin
name: 'apis',
- // Returing a custom content component
+ // Returning a custom content component
loader: () =>
import('./components').then(m => ),
}),
@@ -935,7 +935,7 @@ This is an example with a made-up renderer for SQL schemas:
```tsx
import {
createFrontendModule,
- createApiExtenion,
+ createApiExtension,
createApiFactory,
} from '@backstage/frontend-plugin-api';
import { ApiEntity } from '@backstage/catalog-model';
@@ -949,7 +949,7 @@ import { SqlRenderer } from '...';
export default createFrontendModule({
pluginId: 'api-docs',
extensions: [
- createApiExtenion({
+ createApiExtension({
factory: createApiFactory({
api: apiDocsConfigRef,
deps: {},
@@ -989,7 +989,7 @@ Override the config api to configure a [`requestInterceptor` for Swagger UI](htt
```tsx
import {
createFrontendModule,
- createApiExtenion,
+ createApiExtension,
createApiFactory,
} from '@backstage/frontend-plugin-api';
import {
@@ -1002,7 +1002,7 @@ import { ApiEntity } from '@backstage/catalog-model';
export default createFrontendModule({
pluginId: 'api-docs',
extensions: [
- createApiExtenion({
+ createApiExtension({
factory: createApiFactory({
api: apiDocsConfigRef,
deps: {},
@@ -1051,7 +1051,7 @@ If you want to limit the HTTP methods available for the `Try It Out` feature of
```tsx
import {
createFrontendModule,
- createApiExtenion,
+ createApiExtension,
createApiFactory,
} from '@backstage/frontend-plugin-api';
import {
@@ -1064,7 +1064,7 @@ import { ApiEntity } from '@backstage/catalog-model';
export default createFrontendModule({
pluginId: 'api-docs',
extensions: [
- createApiExtenion({
+ createApiExtension({
factory: createApiFactory({
api: apiDocsConfigRef,
deps: {},
diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json
index 578464e7b6..389f8265b6 100644
--- a/plugins/api-docs/package.json
+++ b/plugins/api-docs/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-api-docs",
- "version": "0.12.6",
+ "version": "0.12.7-next.1",
"description": "A Backstage plugin that helps represent API entities in the frontend",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/api-docs/src/alpha.tsx b/plugins/api-docs/src/alpha.tsx
index f85df511ec..cb55f620a7 100644
--- a/plugins/api-docs/src/alpha.tsx
+++ b/plugins/api-docs/src/alpha.tsx
@@ -74,7 +74,7 @@ const apiDocsConfigApi = ApiBlueprint.make({
const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({
config: {
schema: {
- // Ommiting columns and actions for now as their types are too complex to map to zod
+ // Omitting columns and actions for now as their types are too complex to map to zod
initiallySelectedFilter: z =>
z.enum(['owned', 'starred', 'all']).optional(),
},
@@ -98,7 +98,7 @@ const apiDocsExplorerPage = PageBlueprint.makeWithOverrides({
const apiDocsHasApisEntityCard = EntityCardBlueprint.make({
name: 'has-apis',
params: {
- // Ommiting configSchema for now
+ // Omitting configSchema for now
// We are skipping variants and columns are too complex to map to zod
// See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252
filter: entity => {
@@ -132,7 +132,7 @@ const apiDocsDefinitionEntityCard = EntityCardBlueprint.make({
const apiDocsConsumedApisEntityCard = EntityCardBlueprint.make({
name: 'consumed-apis',
params: {
- // Ommiting configSchema for now
+ // Omitting configSchema for now
// We are skipping variants and columns are too complex to map to zod
// See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252
filter: 'kind:component',
@@ -146,7 +146,7 @@ const apiDocsConsumedApisEntityCard = EntityCardBlueprint.make({
const apiDocsProvidedApisEntityCard = EntityCardBlueprint.make({
name: 'provided-apis',
params: {
- // Ommiting configSchema for now
+ // Omitting configSchema for now
// We are skipping variants and columns are too complex to map to zod
// See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252
filter: 'kind:component',
@@ -160,7 +160,7 @@ const apiDocsProvidedApisEntityCard = EntityCardBlueprint.make({
const apiDocsConsumingComponentsEntityCard = EntityCardBlueprint.make({
name: 'consuming-components',
params: {
- // Ommiting configSchema for now
+ // Omitting configSchema for now
// We are skipping variants
// See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252
filter: 'kind:api',
@@ -174,7 +174,7 @@ const apiDocsConsumingComponentsEntityCard = EntityCardBlueprint.make({
const apiDocsProvidingComponentsEntityCard = EntityCardBlueprint.make({
name: 'providing-components',
params: {
- // Ommiting configSchema for now
+ // Omitting configSchema for now
// We are skipping variants
// See: https://github.com/backstage/backstage/pull/22619#discussion_r1477333252
filter: 'kind:api',
@@ -227,7 +227,7 @@ const apiDocsApisEntityContent = EntityContentBlueprint.make({
});
export default createFrontendPlugin({
- id: 'api-docs',
+ pluginId: 'api-docs',
routes: {
root: convertLegacyRouteRef(rootRoute),
},
diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md
index ffa2c5ed22..966860f088 100644
--- a/plugins/app-backend/CHANGELOG.md
+++ b/plugins/app-backend/CHANGELOG.md
@@ -1,5 +1,31 @@
# @backstage/plugin-app-backend
+## 0.5.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-app-node@0.1.33-next.1
+
+## 0.5.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config-loader@1.10.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-app-node@0.1.33-next.0
+
## 0.5.1
### Patch Changes
diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json
index 7a3d5a352d..432e17151a 100644
--- a/plugins/app-backend/package.json
+++ b/plugins/app-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app-backend",
- "version": "0.5.1",
+ "version": "0.5.2-next.1",
"description": "A Backstage backend plugin that serves the Backstage frontend app",
"backstage": {
"role": "backend-plugin",
diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md
index d8e940a713..6d1e1a2368 100644
--- a/plugins/app-node/CHANGELOG.md
+++ b/plugins/app-node/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-app-node
+## 0.1.33-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config-loader@1.10.1-next.0
+
+## 0.1.33-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config-loader@1.10.0
+
## 0.1.32
### Patch Changes
diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json
index 21a72cf685..b92d045fd8 100644
--- a/plugins/app-node/package.json
+++ b/plugins/app-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app-node",
- "version": "0.1.32",
+ "version": "0.1.33-next.1",
"description": "Node.js library for the app plugin",
"backstage": {
"role": "node-library",
diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md
index 56a25e0061..4f3b6f2178 100644
--- a/plugins/app-visualizer/CHANGELOG.md
+++ b/plugins/app-visualizer/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-app-visualizer
+## 0.1.19-next.0
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+
## 0.1.18
### Patch Changes
diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json
index 3fdd75ce38..057a383b20 100644
--- a/plugins/app-visualizer/package.json
+++ b/plugins/app-visualizer/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app-visualizer",
- "version": "0.1.18",
+ "version": "0.1.19-next.0",
"description": "Visualizes the Backstage app structure",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/app-visualizer/src/plugin.tsx b/plugins/app-visualizer/src/plugin.tsx
index 465f5100ce..92ccd00159 100644
--- a/plugins/app-visualizer/src/plugin.tsx
+++ b/plugins/app-visualizer/src/plugin.tsx
@@ -45,6 +45,6 @@ export const appVisualizerNavItem = NavItemBlueprint.make({
/** @public */
export const visualizerPlugin = createFrontendPlugin({
- id: 'app-visualizer',
+ pluginId: 'app-visualizer',
extensions: [appVisualizerPage, appVisualizerNavItem],
});
diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md
index dc7bc1896c..a45b254f79 100644
--- a/plugins/app/CHANGELOG.md
+++ b/plugins/app/CHANGELOG.md
@@ -1,5 +1,32 @@
# @backstage/plugin-app
+## 0.1.9-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/theme@0.6.6-next.0
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## 0.1.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/theme@0.6.5
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-react@0.4.33
+
## 0.1.8
### Patch Changes
diff --git a/plugins/app/package.json b/plugins/app/package.json
index 28f4019e03..5cada40ebc 100644
--- a/plugins/app/package.json
+++ b/plugins/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app",
- "version": "0.1.8",
+ "version": "0.1.9-next.1",
"backstage": {
"role": "frontend-plugin",
"pluginId": "app",
diff --git a/plugins/app/src/plugin.ts b/plugins/app/src/plugin.ts
index 809e101d0f..b166423fa1 100644
--- a/plugins/app/src/plugin.ts
+++ b/plugins/app/src/plugin.ts
@@ -41,7 +41,7 @@ import { apis } from './defaultApis';
/** @public */
export const appPlugin = createFrontendPlugin({
- id: 'app',
+ pluginId: 'app',
extensions: [
...apis,
App,
diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md
index 36140e6def..2ce29207f7 100644
--- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-auth-backend-module-atlassian-provider
+## 0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## 0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
## 0.4.2
### Patch Changes
diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json
index 3575f8ab1e..e458540b8f 100644
--- a/plugins/auth-backend-module-atlassian-provider/package.json
+++ b/plugins/auth-backend-module-atlassian-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-atlassian-provider",
- "version": "0.4.2",
+ "version": "0.4.3-next.1",
"description": "The atlassian-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md
index 02bb3f0bd6..c14a7d28bd 100644
--- a/plugins/auth-backend-module-auth0-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-auth0-provider/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-auth-backend-module-auth0-provider
+## 0.2.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## 0.2.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
## 0.2.2
### Patch Changes
diff --git a/plugins/auth-backend-module-auth0-provider/package.json b/plugins/auth-backend-module-auth0-provider/package.json
index c6df6b61a8..8a8b878ff2 100644
--- a/plugins/auth-backend-module-auth0-provider/package.json
+++ b/plugins/auth-backend-module-auth0-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-auth0-provider",
- "version": "0.2.2",
+ "version": "0.2.3-next.1",
"description": "The auth0-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md
index af8b009abd..687bd3a63f 100644
--- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-auth-backend-module-aws-alb-provider
+## 0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/errors@1.2.7
+
+## 0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/errors@1.2.7
+
## 0.4.2
### Patch Changes
diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json
index 6c4c50adb9..9b98642ab1 100644
--- a/plugins/auth-backend-module-aws-alb-provider/package.json
+++ b/plugins/auth-backend-module-aws-alb-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-aws-alb-provider",
- "version": "0.4.2",
+ "version": "0.4.3-next.1",
"description": "The aws-alb provider module for the Backstage auth backend.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md
index 8adde2f7e8..da8c7e6e81 100644
--- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-auth-backend-module-azure-easyauth-provider
+## 0.2.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+
+## 0.2.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+
## 0.2.7
### Patch Changes
diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json
index 29e8cd2c6b..837ed7c9d1 100644
--- a/plugins/auth-backend-module-azure-easyauth-provider/package.json
+++ b/plugins/auth-backend-module-azure-easyauth-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-azure-easyauth-provider",
- "version": "0.2.7",
+ "version": "0.2.8-next.1",
"description": "The azure-easyauth-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md
index caf9a0230f..fe706216c2 100644
--- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-auth-backend-module-bitbucket-provider
+## 0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## 0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
## 0.3.2
### Patch Changes
diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json
index 2ee5eb1027..55182829cb 100644
--- a/plugins/auth-backend-module-bitbucket-provider/package.json
+++ b/plugins/auth-backend-module-bitbucket-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-bitbucket-provider",
- "version": "0.3.2",
+ "version": "0.3.3-next.1",
"description": "The bitbucket-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md
index bb155bb041..e4fc051072 100644
--- a/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-bitbucket-server-provider/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-auth-backend-module-bitbucket-server-provider
+## 0.2.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## 0.2.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
## 0.2.2
### Patch Changes
diff --git a/plugins/auth-backend-module-bitbucket-server-provider/package.json b/plugins/auth-backend-module-bitbucket-server-provider/package.json
index ea02df97c1..dff0e32895 100644
--- a/plugins/auth-backend-module-bitbucket-server-provider/package.json
+++ b/plugins/auth-backend-module-bitbucket-server-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-bitbucket-server-provider",
- "version": "0.2.2",
+ "version": "0.2.3-next.1",
"description": "The bitbucket-server-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md
index c120d83410..1495904a13 100644
--- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-auth-backend-module-cloudflare-access-provider
+## 0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## 0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.4.2
### Patch Changes
diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json
index f7fdad4ba1..3f8e18b5bb 100644
--- a/plugins/auth-backend-module-cloudflare-access-provider/package.json
+++ b/plugins/auth-backend-module-cloudflare-access-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-cloudflare-access-provider",
- "version": "0.4.2",
+ "version": "0.4.3-next.1",
"description": "The cloudflare-access-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md
index 2c0b8cc2f9..6af6a5844b 100644
--- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-auth-backend-module-gcp-iap-provider
+## 0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## 0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 0.4.2
### Patch Changes
diff --git a/plugins/auth-backend-module-gcp-iap-provider/package.json b/plugins/auth-backend-module-gcp-iap-provider/package.json
index 1bbbdb55f7..7ee3097075 100644
--- a/plugins/auth-backend-module-gcp-iap-provider/package.json
+++ b/plugins/auth-backend-module-gcp-iap-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-gcp-iap-provider",
- "version": "0.4.2",
+ "version": "0.4.3-next.1",
"description": "A GCP IAP auth provider module for the Backstage auth backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md
index 97c47fc654..dc2635956e 100644
--- a/plugins/auth-backend-module-github-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/plugin-auth-backend-module-github-provider
+## 0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## 0.3.3-next.0
+
+### Patch Changes
+
+- 5cdfe05: Added missing types package
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
## 0.3.2
### Patch Changes
diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json
index dd0ec788a3..dabe2c4c5b 100644
--- a/plugins/auth-backend-module-github-provider/package.json
+++ b/plugins/auth-backend-module-github-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-github-provider",
- "version": "0.3.2",
+ "version": "0.3.3-next.1",
"description": "The github-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md
index 92390293a2..6ecb16c14a 100644
--- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-auth-backend-module-gitlab-provider
+## 0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## 0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
## 0.3.2
### Patch Changes
diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json
index f723963b18..fd726ebed5 100644
--- a/plugins/auth-backend-module-gitlab-provider/package.json
+++ b/plugins/auth-backend-module-gitlab-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-gitlab-provider",
- "version": "0.3.2",
+ "version": "0.3.3-next.1",
"description": "The gitlab-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md
index e79eedf3a8..6271e93dec 100644
--- a/plugins/auth-backend-module-google-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-auth-backend-module-google-provider
+## 0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## 0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
## 0.3.2
### Patch Changes
diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json
index 56558e4510..cdc365ddfb 100644
--- a/plugins/auth-backend-module-google-provider/package.json
+++ b/plugins/auth-backend-module-google-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-google-provider",
- "version": "0.3.2",
+ "version": "0.3.3-next.1",
"description": "A Google auth provider module for the Backstage auth backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md
index 02d92760b1..3bb742c6ef 100644
--- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-auth-backend-module-guest-provider
+## 0.2.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+
+## 0.2.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+
## 0.2.7
### Patch Changes
diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json
index 718361bfa0..80fe709602 100644
--- a/plugins/auth-backend-module-guest-provider/package.json
+++ b/plugins/auth-backend-module-guest-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-guest-provider",
- "version": "0.2.7",
+ "version": "0.2.8-next.1",
"description": "The guest-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md
index 3640bbd38e..2375a09efd 100644
--- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-auth-backend-module-microsoft-provider
+## 0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## 0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
## 0.3.2
### Patch Changes
diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json
index 90648b80ce..a64b9d04d4 100644
--- a/plugins/auth-backend-module-microsoft-provider/package.json
+++ b/plugins/auth-backend-module-microsoft-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-microsoft-provider",
- "version": "0.3.2",
+ "version": "0.3.3-next.1",
"description": "The microsoft-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md
index b67c10c432..e8359616eb 100644
--- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-auth-backend-module-oauth2-provider
+## 0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## 0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
## 0.4.2
### Patch Changes
diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json
index 5dfcf9bbe0..4b9f9b6c02 100644
--- a/plugins/auth-backend-module-oauth2-provider/package.json
+++ b/plugins/auth-backend-module-oauth2-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-oauth2-provider",
- "version": "0.4.2",
+ "version": "0.4.3-next.1",
"description": "The oauth2-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md
index f65b5d72a5..a4ed6a4989 100644
--- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/plugin-auth-backend-module-oauth2-proxy-provider
+## 0.2.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/errors@1.2.7
+
+## 0.2.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/errors@1.2.7
+
## 0.2.7
### Patch Changes
diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/package.json b/plugins/auth-backend-module-oauth2-proxy-provider/package.json
index ad30a8113d..c1df361682 100644
--- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json
+++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider",
- "version": "0.2.7",
+ "version": "0.2.8-next.1",
"description": "The oauth2-proxy-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md
index bc0c21e6e7..33520a8bde 100644
--- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-auth-backend-module-oidc-provider
+## 0.4.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
+## 0.4.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend@0.25.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
## 0.4.2
### Patch Changes
diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json
index 27e4eaad58..65c1d8f208 100644
--- a/plugins/auth-backend-module-oidc-provider/package.json
+++ b/plugins/auth-backend-module-oidc-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-oidc-provider",
- "version": "0.4.2",
+ "version": "0.4.3-next.1",
"description": "The oidc-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md
index 9fc2c07df8..3f7838609e 100644
--- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-auth-backend-module-okta-provider
+## 0.2.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## 0.2.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
## 0.2.2
### Patch Changes
diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json
index 14bc4e35d2..70e528d0b1 100644
--- a/plugins/auth-backend-module-okta-provider/package.json
+++ b/plugins/auth-backend-module-okta-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-okta-provider",
- "version": "0.2.2",
+ "version": "0.2.3-next.1",
"description": "The okta-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md
index 1da177ee26..410f2eda91 100644
--- a/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-onelogin-provider/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-auth-backend-module-onelogin-provider
+## 0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+
+## 0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+
## 0.3.2
### Patch Changes
diff --git a/plugins/auth-backend-module-onelogin-provider/package.json b/plugins/auth-backend-module-onelogin-provider/package.json
index 1092b52bf2..1e9efc21ea 100644
--- a/plugins/auth-backend-module-onelogin-provider/package.json
+++ b/plugins/auth-backend-module-onelogin-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-onelogin-provider",
- "version": "0.3.2",
+ "version": "0.3.3-next.1",
"description": "The onelogin-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md
index ac2dd252df..7c1ee827b6 100644
--- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-auth-backend-module-pinniped-provider
+## 0.3.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
+## 0.3.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
## 0.3.2
### Patch Changes
diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json
index 50dc294e5a..940443cf7e 100644
--- a/plugins/auth-backend-module-pinniped-provider/package.json
+++ b/plugins/auth-backend-module-pinniped-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-pinniped-provider",
- "version": "0.3.2",
+ "version": "0.3.3-next.1",
"description": "The pinniped-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md
index 9b69e6c6e0..fc085d8c31 100644
--- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/plugin-auth-backend-module-vmware-cloud-provider
+## 0.5.3-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+
+## 0.5.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/catalog-model@1.7.3
+
## 0.5.2
### Patch Changes
diff --git a/plugins/auth-backend-module-vmware-cloud-provider/package.json b/plugins/auth-backend-module-vmware-cloud-provider/package.json
index c4bd264171..bf209f69fd 100644
--- a/plugins/auth-backend-module-vmware-cloud-provider/package.json
+++ b/plugins/auth-backend-module-vmware-cloud-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-vmware-cloud-provider",
- "version": "0.5.2",
+ "version": "0.5.3-next.1",
"description": "The vmware-cloud-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md
index 4421cafd88..8f12cd8305 100644
--- a/plugins/auth-backend/CHANGELOG.md
+++ b/plugins/auth-backend/CHANGELOG.md
@@ -1,5 +1,66 @@
# @backstage/plugin-auth-backend
+## 0.25.0-next.1
+
+### Patch Changes
+
+- 0d606ac: Added the configuration flag `auth.omitIdentityTokenOwnershipClaim` that causes issued user tokens to no longer contain the `ent` claim that represents the ownership references of the user.
+
+ The benefit of this new flag is that issued user tokens will be much smaller in
+ size, but they will no longer be self-contained. This means that any consumers
+ of the token that require access to the ownership claims now need to call the
+ `/api/auth/v1/userinfo` endpoint instead. Within the Backstage ecosystem this is
+ done automatically, as clients will still receive the full set of claims during
+ authentication, while plugin backends will need to use the `UserInfoService`
+ which already calls the user info endpoint if necessary.
+
+ When enabling this flag, it is important that any custom sign-in resolvers directly return the result of the sign-in method. For example, the following would not work:
+
+ ```ts
+ const { token } = await ctx.issueToken({
+ claims: { sub: entityRef, ent: [entityRef] },
+ });
+ return { token }; // WARNING: This will not work with the flag enabled
+ ```
+
+ Instead, the sign-in resolver should directly return the result:
+
+ ```ts
+ return ctx.issueToken({
+ claims: { sub: entityRef, ent: [entityRef] },
+ });
+ ```
+
+- 72d019d: Removed various typos
+- b128ed9: The `static` key store now issues tokens with the same structure as other key stores. Tokens now include the `typ` field in the header and the `uip` (user identity proof) in the payload.
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## 0.25.0-next.0
+
+### Minor Changes
+
+- 57221d9: **BREAKING**: Removed support for the old backend system, and removed all deprecated exports.
+
+ If you were using one of the deprecated imports from this package, you will have to follow the instructions in their respective deprecation notices before upgrading. Most of the general utilities are available from `@backstage/plugin-auth-node`, and the specific auth providers are available from dedicated packages such as for example `@backstage/plugin-auth-backend-module-github-provider`. See [the auth docs](https://backstage.io/docs/auth/) for specific instructions.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 0.24.5
### Patch Changes
diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts
index bf91780c34..4e099e75be 100644
--- a/plugins/auth-backend/config.d.ts
+++ b/plugins/auth-backend/config.d.ts
@@ -43,6 +43,15 @@ export interface Config {
*/
identityTokenAlgorithm?: string;
+ /**
+ * Whether to omit the entity ownership references (`ent`) claim from the
+ * identity token. If this is enabled the `ent` claim will only be available
+ * via the user info endpoint and the `UserInfoService`.
+ *
+ * Defaults to `false`.
+ */
+ omitIdentityTokenOwnershipClaim?: boolean;
+
/** To control how to store JWK data in auth-backend */
keyStore?: {
provider?: 'database' | 'memory' | 'firestore' | 'static';
diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json
index e6da6d2eb6..3fce660aa5 100644
--- a/plugins/auth-backend/package.json
+++ b/plugins/auth-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend",
- "version": "0.24.5",
+ "version": "0.25.0-next.1",
"description": "A Backstage backend plugin that handles authentication",
"backstage": {
"role": "backend-plugin",
@@ -69,6 +69,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-auth-backend-module-google-provider": "workspace:^",
+ "@backstage/plugin-auth-backend-module-guest-provider": "workspace:^",
"@types/cookie-parser": "^1.4.2",
"@types/express": "^4.17.6",
"@types/express-session": "^1.17.2",
diff --git a/plugins/auth-backend/src/authPlugin.test.ts b/plugins/auth-backend/src/authPlugin.test.ts
index 008df01295..cd2b558c80 100644
--- a/plugins/auth-backend/src/authPlugin.test.ts
+++ b/plugins/auth-backend/src/authPlugin.test.ts
@@ -17,6 +17,8 @@
import { mockServices, startTestBackend } from '@backstage/backend-test-utils';
import request from 'supertest';
import { authPlugin } from './authPlugin';
+import authModuleGuestProvider from '@backstage/plugin-auth-backend-module-guest-provider';
+import { authServiceFactory } from '@backstage/backend-defaults/auth';
describe('authPlugin', () => {
it('should provide an OpenID configuration', async () => {
@@ -42,4 +44,125 @@ describe('authPlugin', () => {
issuer: `http://localhost:${server.port()}/api/auth`,
});
});
+
+ describe('mock provider', () => {
+ const mockProvidersConfig = {
+ environment: 'test',
+ providers: {
+ guest: {
+ dangerouslyAllowOutsideDevelopment: true,
+ userEntityRef: 'user:default/tester',
+ ownershipEntityRefs: [
+ 'group:default/testers',
+ 'group:default/testers2',
+ ],
+ },
+ },
+ };
+
+ const expectedIdentity = {
+ type: 'user',
+ userEntityRef: 'user:default/tester',
+ ownershipEntityRefs: ['group:default/testers', 'group:default/testers2'],
+ };
+
+ it('should return tokens with all identity claims by default', async () => {
+ const { server } = await startTestBackend({
+ features: [
+ authPlugin,
+ authModuleGuestProvider,
+ authServiceFactory,
+ mockServices.rootConfig.factory({
+ data: {
+ app: {
+ baseUrl: 'http://localhost',
+ },
+ auth: {
+ ...mockProvidersConfig,
+ },
+ },
+ }),
+ ],
+ });
+
+ const refreshRes = await request(server).post('/api/auth/guest/refresh');
+ expect(refreshRes.status).toBe(200);
+ expect(refreshRes.body).toMatchObject({
+ backstageIdentity: {
+ expiresInSeconds: expect.any(Number),
+ identity: expectedIdentity,
+ token: expect.any(String),
+ },
+ profile: {},
+ });
+
+ const token = refreshRes.body.backstageIdentity.token;
+ const decoded = JSON.parse(atob(token.split('.')[1]));
+ expect(decoded.sub).toEqual(expectedIdentity.userEntityRef);
+ expect(decoded.ent).toEqual(expectedIdentity.ownershipEntityRefs);
+
+ const userInfoRes = await request(server)
+ .get('/api/auth/v1/userinfo')
+ .set('Authorization', `Bearer ${token}`);
+
+ expect(userInfoRes.status).toBe(200);
+ expect(userInfoRes.body).toMatchObject({
+ claims: {
+ sub: expectedIdentity.userEntityRef,
+ ent: expectedIdentity.ownershipEntityRefs,
+ exp: expect.any(Number),
+ },
+ });
+ });
+
+ it('should omit ownership claims from the token when the config is set', async () => {
+ const { server } = await startTestBackend({
+ features: [
+ authPlugin,
+ authModuleGuestProvider,
+ authServiceFactory,
+ mockServices.rootConfig.factory({
+ data: {
+ app: {
+ baseUrl: 'http://localhost',
+ },
+ auth: {
+ omitIdentityTokenOwnershipClaim: true,
+ ...mockProvidersConfig,
+ },
+ },
+ }),
+ ],
+ });
+
+ const refreshRes = await request(server).post('/api/auth/guest/refresh');
+ expect(refreshRes.status).toBe(200);
+ expect(refreshRes.body).toMatchObject({
+ backstageIdentity: {
+ expiresInSeconds: expect.any(Number),
+ identity: expectedIdentity,
+ token: expect.any(String),
+ },
+ profile: {},
+ });
+
+ const token = refreshRes.body.backstageIdentity.token;
+ const decoded = JSON.parse(atob(token.split('.')[1]));
+ expect(decoded.sub).toEqual(expectedIdentity.userEntityRef);
+ expect(decoded.ent).toBeUndefined();
+
+ const userInfoRes = await request(server)
+ .get('/api/auth/v1/userinfo')
+ .set('Authorization', `Bearer ${token}`);
+
+ expect(userInfoRes.status).toBe(200);
+ expect(userInfoRes.body).toMatchObject({
+ claims: {
+ sub: expectedIdentity.userEntityRef,
+ ent: expectedIdentity.ownershipEntityRefs,
+ exp: expect.any(Number),
+ },
+ });
+ });
+ });
});
diff --git a/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts b/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts
index 92b5888113..ef6f7bd148 100644
--- a/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts
+++ b/plugins/auth-backend/src/identity/StaticTokenIssuer.test.ts
@@ -18,6 +18,8 @@ import { createLocalJWKSet, jwtVerify } from 'jose';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { StaticKeyStore } from './StaticKeyStore';
import { mockServices } from '@backstage/backend-test-utils';
+import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler';
+import { omit } from 'lodash';
const logger = mockServices.logger.mock();
const entityRef = stringifyEntityRef({
@@ -27,63 +29,67 @@ const entityRef = stringifyEntityRef({
});
describe('StaticTokenIssuer', () => {
- it('should issue valid tokens signed by the first listed key', async () => {
- const staticKeyStore = {
- listKeys: () => {
- return Promise.resolve({
- items: [
- {
- createdAt: new Date(),
- key: {
- kty: 'EC',
- x: 'vrJVK0vSV4LRwdUXA55pb3rruB92ZoUEY72HTvjIP9w',
- y: 'UmyzoVoTERJPOE9Kvdf5t797FquRMxyFsNc0R0IrCmc',
- crv: 'P-256',
- kid: '1',
- alg: 'ES256',
- use: 'sig',
- },
- },
- {
- createdAt: new Date(),
- key: {
- kty: 'EC',
- x: 'vrJVK0vSV4LRwdUXA55pb3rruB92ZoUEY72HTvjIP9w',
- y: 'UmyzoVoTERJPOE9Kvdf5t797FquRMxyFsNc0R0IrCmc',
- crv: 'P-256',
- kid: '2',
- alg: 'ES256',
- use: 'sig',
- },
- },
- ],
- });
- },
- getPrivateKey: (kid: string) => {
- expect(kid).toEqual('1');
- return {
- kty: 'EC',
- x: 'vrJVK0vSV4LRwdUXA55pb3rruB92ZoUEY72HTvjIP9w',
- y: 'UmyzoVoTERJPOE9Kvdf5t797FquRMxyFsNc0R0IrCmc',
- crv: 'P-256',
- d: 'R8Ja2ppMEgOm1KeYKpje00U1luybndt6yC263vcgeKo',
- kid: '1',
- alg: 'ES256',
- };
- },
- };
+ const mockUserInfoDatabaseHandler = {
+ addUserInfo: jest.fn().mockResolvedValue(undefined),
+ } as unknown as UserInfoDatabaseHandler;
+ const staticKeyStore = {
+ listKeys: () => {
+ return Promise.resolve({
+ items: [
+ {
+ createdAt: new Date(),
+ key: {
+ kty: 'EC',
+ x: 'vrJVK0vSV4LRwdUXA55pb3rruB92ZoUEY72HTvjIP9w',
+ y: 'UmyzoVoTERJPOE9Kvdf5t797FquRMxyFsNc0R0IrCmc',
+ crv: 'P-256',
+ kid: '1',
+ alg: 'ES256',
+ use: 'sig',
+ },
+ },
+ {
+ createdAt: new Date(),
+ key: {
+ kty: 'EC',
+ x: 'vrJVK0vSV4LRwdUXA55pb3rruB92ZoUEY72HTvjIP9w',
+ y: 'UmyzoVoTERJPOE9Kvdf5t797FquRMxyFsNc0R0IrCmc',
+ crv: 'P-256',
+ kid: '2',
+ alg: 'ES256',
+ use: 'sig',
+ },
+ },
+ ],
+ });
+ },
+ getPrivateKey: (kid: string) => {
+ expect(kid).toEqual('1');
+ return {
+ kty: 'EC',
+ x: 'vrJVK0vSV4LRwdUXA55pb3rruB92ZoUEY72HTvjIP9w',
+ y: 'UmyzoVoTERJPOE9Kvdf5t797FquRMxyFsNc0R0IrCmc',
+ crv: 'P-256',
+ d: 'R8Ja2ppMEgOm1KeYKpje00U1luybndt6yC263vcgeKo',
+ kid: '1',
+ alg: 'ES256',
+ };
+ },
+ };
+
+ it('should issue valid tokens signed by the first listed key', async () => {
const keyDurationSeconds = 86400;
- const options = {
- logger,
- issuer: 'my-issuer',
- sessionExpirationSeconds: keyDurationSeconds,
- };
const issuer = new StaticTokenIssuer(
- options,
+ {
+ logger,
+ issuer: 'my-issuer',
+ sessionExpirationSeconds: keyDurationSeconds,
+ userInfoDatabaseHandler: mockUserInfoDatabaseHandler,
+ },
staticKeyStore as unknown as StaticKeyStore,
);
- const token = await issuer.issueToken({
+ const { token } = await issuer.issueToken({
claims: {
sub: entityRef,
ent: [entityRef],
@@ -98,6 +104,7 @@ describe('StaticTokenIssuer', () => {
expect(verifyResult.protectedHeader).toEqual({
kid: '1',
alg: 'ES256',
+ typ: 'vnd.backstage.user',
});
expect(verifyResult.payload).toEqual({
iss: 'my-issuer',
@@ -107,9 +114,62 @@ describe('StaticTokenIssuer', () => {
'x-fancy-claim': 'my special claim',
iat: expect.any(Number),
exp: expect.any(Number),
+ uip: expect.any(String),
});
expect(verifyResult.payload.exp).toBe(
verifyResult.payload.iat! + keyDurationSeconds,
);
+ expect(mockUserInfoDatabaseHandler.addUserInfo).toHaveBeenCalledWith({
+ claims: omit(verifyResult.payload, ['aud', 'iat', 'iss', 'uip']),
+ });
+ });
+
+ it('should issue valid tokens with omitted claims', async () => {
+ const keyDurationSeconds = 86400;
+ const issuer = new StaticTokenIssuer(
+ {
+ logger,
+ issuer: 'my-issuer',
+ sessionExpirationSeconds: keyDurationSeconds,
+ userInfoDatabaseHandler: mockUserInfoDatabaseHandler,
+ omitClaimsFromToken: ['ent'],
+ },
+ staticKeyStore as unknown as StaticKeyStore,
+ );
+ const { token } = await issuer.issueToken({
+ claims: {
+ sub: entityRef,
+ ent: [entityRef],
+ 'x-fancy-claim': 'my special claim',
+ aud: 'this value will be overridden',
+ },
+ });
+
+ const { keys } = await issuer.listPublicKeys();
+ const keyStore = createLocalJWKSet({ keys: keys });
+ const verifyResult = await jwtVerify(token, keyStore);
+ expect(verifyResult.protectedHeader).toEqual({
+ kid: '1',
+ alg: 'ES256',
+ typ: 'vnd.backstage.user',
+ });
+ expect(verifyResult.payload).toEqual({
+ iss: 'my-issuer',
+ aud: 'backstage',
+ sub: entityRef,
+ 'x-fancy-claim': 'my special claim',
+ iat: expect.any(Number),
+ exp: expect.any(Number),
+ uip: expect.any(String),
+ });
+ expect(verifyResult.payload.exp).toBe(
+ verifyResult.payload.iat! + keyDurationSeconds,
+ );
+ expect(mockUserInfoDatabaseHandler.addUserInfo).toHaveBeenCalledWith({
+ claims: {
+ ...omit(verifyResult.payload, ['aud', 'iat', 'iss', 'uip']),
+ ent: [entityRef],
+ },
+ });
});
});
diff --git a/plugins/auth-backend/src/identity/StaticTokenIssuer.ts b/plugins/auth-backend/src/identity/StaticTokenIssuer.ts
index 17fbe1b180..6875e47457 100644
--- a/plugins/auth-backend/src/identity/StaticTokenIssuer.ts
+++ b/plugins/auth-backend/src/identity/StaticTokenIssuer.ts
@@ -15,14 +15,15 @@
*/
import { AnyJWK, TokenIssuer } from './types';
-import { SignJWT, importJWK, JWK } from 'jose';
-import { parseEntityRef } from '@backstage/catalog-model';
-import { AuthenticationError } from '@backstage/errors';
+import { JWK } from 'jose';
import { LoggerService } from '@backstage/backend-plugin-api';
import { StaticKeyStore } from './StaticKeyStore';
-import { TokenParams } from '@backstage/plugin-auth-node';
-
-const MS_IN_S = 1000;
+import {
+ BackstageSignInResult,
+ TokenParams,
+} from '@backstage/plugin-auth-node';
+import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler';
+import { issueUserToken } from './issueUserToken';
export type Config = {
publicKeyFile: string;
@@ -37,6 +38,11 @@ export type Options = {
issuer: string;
/** Expiration time of the JWT in seconds */
sessionExpirationSeconds: number;
+ /**
+ * A list of claims to omit from issued tokens and only store in the user info database
+ */
+ omitClaimsFromToken?: string[];
+ userInfoDatabaseHandler: UserInfoDatabaseHandler;
};
/**
@@ -48,47 +54,30 @@ export class StaticTokenIssuer implements TokenIssuer {
private readonly logger: LoggerService;
private readonly keyStore: StaticKeyStore;
private readonly sessionExpirationSeconds: number;
+ private readonly omitClaimsFromToken?: string[];
+ private readonly userInfoDatabaseHandler: UserInfoDatabaseHandler;
public constructor(options: Options, keyStore: StaticKeyStore) {
this.issuer = options.issuer;
this.logger = options.logger;
this.sessionExpirationSeconds = options.sessionExpirationSeconds;
this.keyStore = keyStore;
+ this.omitClaimsFromToken = options.omitClaimsFromToken;
+ this.userInfoDatabaseHandler = options.userInfoDatabaseHandler;
}
- public async issueToken(params: TokenParams): Promise {
+ public async issueToken(params: TokenParams): Promise {
const key = await this.getSigningKey();
- // TODO: code shared with TokenFactory.ts
- const iss = this.issuer;
- const { sub, ent, ...additionalClaims } = params.claims;
- const aud = 'backstage';
- const iat = Math.floor(Date.now() / MS_IN_S);
- const exp = iat + this.sessionExpirationSeconds;
-
- // Validate that the subject claim is a valid EntityRef
- try {
- parseEntityRef(sub);
- } catch (error) {
- throw new Error(
- '"sub" claim provided by the auth resolver is not a valid EntityRef.',
- );
- }
-
- this.logger.info(`Issuing token for ${sub}, with entities ${ent ?? []}`);
-
- if (!key.alg) {
- throw new AuthenticationError('No algorithm was provided in the key');
- }
-
- return new SignJWT({ ...additionalClaims, iss, sub, ent, aud, iat, exp })
- .setProtectedHeader({ alg: key.alg, kid: key.kid })
- .setIssuer(iss)
- .setAudience(aud)
- .setSubject(sub)
- .setIssuedAt(iat)
- .setExpirationTime(exp)
- .sign(await importJWK(key));
+ return issueUserToken({
+ issuer: this.issuer,
+ key,
+ keyDurationSeconds: this.sessionExpirationSeconds,
+ logger: this.logger,
+ omitClaimsFromToken: this.omitClaimsFromToken,
+ params,
+ userInfoDatabaseHandler: this.userInfoDatabaseHandler,
+ });
}
private async getSigningKey(): Promise {
diff --git a/plugins/auth-backend/src/identity/TokenFactory.test.ts b/plugins/auth-backend/src/identity/TokenFactory.test.ts
index 53cec685d1..764b26eda4 100644
--- a/plugins/auth-backend/src/identity/TokenFactory.test.ts
+++ b/plugins/auth-backend/src/identity/TokenFactory.test.ts
@@ -60,7 +60,7 @@ describe('TokenFactory', () => {
});
await expect(factory.listPublicKeys()).resolves.toEqual({ keys: [] });
- const token = await factory.issueToken({
+ const { token, identity } = await factory.issueToken({
claims: {
sub: entityRef,
ent: [entityRef],
@@ -68,6 +68,11 @@ describe('TokenFactory', () => {
aud: 'this value will be overridden',
},
});
+ expect(identity).toEqual({
+ type: 'user',
+ userEntityRef: entityRef,
+ ownershipEntityRefs: [entityRef],
+ });
const { keys } = await factory.listPublicKeys();
const keyStore = createLocalJWKSet({ keys: keys });
@@ -137,10 +142,10 @@ describe('TokenFactory', () => {
userInfoDatabaseHandler: mockUserInfoDatabaseHandler,
});
- const token1 = await factory.issueToken({
+ const { token: token1 } = await factory.issueToken({
claims: { sub: entityRef },
});
- const token2 = await factory.issueToken({
+ const { token: token2 } = await factory.issueToken({
claims: { sub: entityRef },
});
expect(jwtKid(token1)).toBe(jwtKid(token2));
@@ -159,7 +164,7 @@ describe('TokenFactory', () => {
keys: [],
});
- const token3 = await factory.issueToken({
+ const { token: token3 } = await factory.issueToken({
claims: { sub: entityRef },
});
expect(jwtKid(token3)).not.toBe(jwtKid(token2));
@@ -236,7 +241,7 @@ describe('TokenFactory', () => {
userInfoDatabaseHandler: mockUserInfoDatabaseHandler,
});
- const token = await factory.issueToken({
+ const { token } = await factory.issueToken({
claims: { sub: entityRef, ent: [entityRef] },
});
diff --git a/plugins/auth-backend/src/identity/TokenFactory.ts b/plugins/auth-backend/src/identity/TokenFactory.ts
index a1643cdbe3..5a8a789d17 100644
--- a/plugins/auth-backend/src/identity/TokenFactory.ts
+++ b/plugins/auth-backend/src/identity/TokenFactory.ts
@@ -14,28 +14,19 @@
* limitations under the License.
*/
-import { parseEntityRef } from '@backstage/catalog-model';
-import { AuthenticationError } from '@backstage/errors';
-import {
- exportJWK,
- generateKeyPair,
- importJWK,
- JWK,
- SignJWT,
- GeneralSign,
- KeyLike,
-} from 'jose';
-import { omit } from 'lodash';
+import { exportJWK, generateKeyPair, JWK } from 'jose';
import { DateTime } from 'luxon';
import { v4 as uuid } from 'uuid';
import { LoggerService } from '@backstage/backend-plugin-api';
-import { TokenParams, tokenTypes } from '@backstage/plugin-auth-node';
+import {
+ BackstageSignInResult,
+ TokenParams,
+ tokenTypes,
+} from '@backstage/plugin-auth-node';
import { AnyJWK, KeyStore, TokenIssuer } from './types';
import { JsonValue } from '@backstage/types';
import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler';
-
-const MS_IN_S = 1000;
-const MAX_TOKEN_LENGTH = 32768; // At 64 bytes per entity ref this still leaves room for about 500 entities
+import { issueUserToken } from './issueUserToken';
/**
* The payload contents of a valid Backstage JWT token
@@ -52,7 +43,7 @@ export interface BackstageTokenPayload {
sub: string;
/**
- * The entity refs that the user claims ownership througg
+ * The entity refs that the user claims ownership through
*/
ent: string[];
@@ -82,28 +73,6 @@ export interface BackstageTokenPayload {
[claim: string]: JsonValue;
}
-/**
- * The payload contents of a valid Backstage user identity claim token
- *
- * @internal
- */
-interface BackstageUserIdentityProofPayload {
- /**
- * The entity ref of the user
- */
- sub: string;
-
- /**
- * Standard expiry in epoch seconds
- */
- exp: number;
-
- /**
- * Standard issue time in epoch seconds
- */
- iat: number;
-}
-
type Options = {
logger: LoggerService;
/** Value of the issuer claim in issued tokens */
@@ -119,6 +88,10 @@ type Options = {
* If not, add a knex migration file in the migrations folder.
* More info on supported algorithms: https://github.com/panva/jose */
algorithm?: string;
+ /**
+ * A list of claims to omit from issued tokens and only store in the user info database
+ */
+ omitClaimsFromToken?: string[];
userInfoDatabaseHandler: UserInfoDatabaseHandler;
};
@@ -142,6 +115,7 @@ export class TokenFactory implements TokenIssuer {
private readonly keyStore: KeyStore;
private readonly keyDurationSeconds: number;
private readonly algorithm: string;
+ private readonly omitClaimsFromToken?: string[];
private readonly userInfoDatabaseHandler: UserInfoDatabaseHandler;
private keyExpiry?: Date;
@@ -153,79 +127,22 @@ export class TokenFactory implements TokenIssuer {
this.keyStore = options.keyStore;
this.keyDurationSeconds = options.keyDurationSeconds;
this.algorithm = options.algorithm ?? 'ES256';
+ this.omitClaimsFromToken = options.omitClaimsFromToken;
this.userInfoDatabaseHandler = options.userInfoDatabaseHandler;
}
- async issueToken(params: TokenParams): Promise {
+ async issueToken(params: TokenParams): Promise {
const key = await this.getKey();
- const iss = this.issuer;
- const { sub, ent = [sub], ...additionalClaims } = params.claims;
- const aud = tokenTypes.user.audClaim;
- const iat = Math.floor(Date.now() / MS_IN_S);
- const exp = iat + this.keyDurationSeconds;
-
- try {
- // The subject must be a valid entity ref
- parseEntityRef(sub);
- } catch (error) {
- throw new Error(
- '"sub" claim provided by the auth resolver is not a valid EntityRef.',
- );
- }
-
- if (!key.alg) {
- throw new AuthenticationError('No algorithm was provided in the key');
- }
-
- this.logger.info(`Issuing token for ${sub}, with entities ${ent}`);
-
- const signingKey = await importJWK(key);
-
- const uip = await this.createUserIdentityClaim({
- header: {
- typ: tokenTypes.limitedUser.typParam,
- alg: key.alg,
- kid: key.kid,
- },
- payload: { sub, iat, exp },
- key: signingKey,
+ return issueUserToken({
+ issuer: this.issuer,
+ key,
+ keyDurationSeconds: this.keyDurationSeconds,
+ logger: this.logger,
+ omitClaimsFromToken: this.omitClaimsFromToken,
+ params,
+ userInfoDatabaseHandler: this.userInfoDatabaseHandler,
});
-
- const claims: BackstageTokenPayload = {
- ...additionalClaims,
- iss,
- sub,
- ent,
- aud,
- iat,
- exp,
- uip,
- };
-
- const token = await new SignJWT(claims)
- .setProtectedHeader({
- typ: tokenTypes.user.typParam,
- alg: key.alg,
- kid: key.kid,
- })
- .sign(signingKey);
-
- if (token.length > MAX_TOKEN_LENGTH) {
- throw new Error(
- `Failed to issue a new user token. The resulting token is excessively large, with either too many ownership claims or too large custom claims. You likely have a bug either in the sign-in resolver or catalog data. The following claims were requested: '${JSON.stringify(
- claims,
- )}'`,
- );
- }
-
- // Store the user info in the database upon successful token
- // issuance so that it can be retrieved later by limited user tokens
- await this.userInfoDatabaseHandler.addUserInfo({
- claims: omit(claims, ['aud', 'iat', 'iss', 'uip']),
- });
-
- return token;
}
// This will be called by other services that want to verify ID tokens.
@@ -318,46 +235,4 @@ export class TokenFactory implements TokenIssuer {
return promise;
}
-
- // Creates a string claim that can be used as part of reconstructing a limited
- // user token. The output of this function is only the signature part of a
- // JWS.
- private async createUserIdentityClaim(options: {
- header: {
- typ: string;
- alg: string;
- kid?: string;
- };
- payload: BackstageUserIdentityProofPayload;
- key: KeyLike | Uint8Array;
- }): Promise {
- // NOTE: We reconstruct the header and payload structures carefully to
- // perfectly guarantee ordering. The reason for this is that we store only
- // the signature part of these to reduce duplication within the Backstage
- // token. Anyone who wants to make an actual JWT based on all this must be
- // able to do the EXACT reconstruction of the header and payload parts, to
- // then append the signature.
-
- const header = {
- typ: options.header.typ,
- alg: options.header.alg,
- ...(options.header.kid ? { kid: options.header.kid } : {}),
- };
-
- const payload = {
- sub: options.payload.sub,
- iat: options.payload.iat,
- exp: options.payload.exp,
- };
-
- const jws = await new GeneralSign(
- new TextEncoder().encode(JSON.stringify(payload)),
- )
- .addSignature(options.key)
- .setProtectedHeader(header)
- .done()
- .sign();
-
- return jws.signatures[0].signature;
- }
}
diff --git a/plugins/auth-backend/src/identity/issueUserToken.ts b/plugins/auth-backend/src/identity/issueUserToken.ts
new file mode 100644
index 0000000000..d411439e6e
--- /dev/null
+++ b/plugins/auth-backend/src/identity/issueUserToken.ts
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2025 The 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 { parseEntityRef } from '@backstage/catalog-model';
+import { AuthenticationError } from '@backstage/errors';
+import {
+ BackstageSignInResult,
+ TokenParams,
+ tokenTypes,
+} from '@backstage/plugin-auth-node';
+import { omit } from 'lodash';
+import { UserInfoDatabaseHandler } from './UserInfoDatabaseHandler';
+import { LoggerService } from '@backstage/backend-plugin-api';
+import { GeneralSign, importJWK, JWK, KeyLike, SignJWT } from 'jose';
+import { BackstageTokenPayload } from './TokenFactory';
+
+const MS_IN_S = 1000;
+const MAX_TOKEN_LENGTH = 32768; // At 64 bytes per entity ref this still leaves room for about 500 entities
+
+export async function issueUserToken({
+ issuer,
+ key,
+ keyDurationSeconds,
+ logger,
+ omitClaimsFromToken,
+ params,
+ userInfoDatabaseHandler,
+}: {
+ issuer: string;
+ key: JWK;
+ keyDurationSeconds: number;
+ logger: LoggerService;
+ omitClaimsFromToken?: string[];
+ params: TokenParams;
+ userInfoDatabaseHandler: UserInfoDatabaseHandler;
+}): Promise {
+ const { sub, ent = [sub], ...additionalClaims } = params.claims;
+ const aud = tokenTypes.user.audClaim;
+ const iat = Math.floor(Date.now() / MS_IN_S);
+ const exp = iat + keyDurationSeconds;
+
+ try {
+ // The subject must be a valid entity ref
+ parseEntityRef(sub);
+ } catch (error) {
+ throw new Error(
+ '"sub" claim provided by the auth resolver is not a valid EntityRef.',
+ );
+ }
+
+ if (!key.alg) {
+ throw new AuthenticationError('No algorithm was provided in the key');
+ }
+
+ logger.info(`Issuing token for ${sub}, with entities ${ent}`);
+
+ const signingKey = await importJWK(key);
+
+ const uip = await createUserIdentityClaim({
+ header: {
+ typ: tokenTypes.limitedUser.typParam,
+ alg: key.alg,
+ kid: key.kid,
+ },
+ payload: { sub, iat, exp },
+ key: signingKey,
+ });
+
+ const claims: BackstageTokenPayload = {
+ ...additionalClaims,
+ iss: issuer,
+ sub,
+ ent,
+ aud,
+ iat,
+ exp,
+ uip,
+ };
+
+ const tokenClaims = omitClaimsFromToken
+ ? omit(claims, omitClaimsFromToken)
+ : claims;
+ const token = await new SignJWT(tokenClaims)
+ .setProtectedHeader({
+ typ: tokenTypes.user.typParam,
+ alg: key.alg,
+ kid: key.kid,
+ })
+ .sign(signingKey);
+
+ if (token.length > MAX_TOKEN_LENGTH) {
+ throw new Error(
+ `Failed to issue a new user token. The resulting token is excessively large, with either too many ownership claims or too large custom claims. You likely have a bug either in the sign-in resolver or catalog data. The following claims were requested: '${JSON.stringify(
+ tokenClaims,
+ )}'`,
+ );
+ }
+
+ // Store the user info in the database upon successful token
+ // issuance so that it can be retrieved later by limited user tokens
+ await userInfoDatabaseHandler.addUserInfo({
+ claims: omit(claims, ['aud', 'iat', 'iss', 'uip']),
+ });
+
+ return {
+ token,
+ identity: {
+ type: 'user',
+ userEntityRef: sub,
+ ownershipEntityRefs: ent,
+ },
+ };
+}
+
+/**
+ * The payload contents of a valid Backstage user identity claim token
+ *
+ * @internal
+ */
+interface BackstageUserIdentityProofPayload {
+ /**
+ * The entity ref of the user
+ */
+ sub: string;
+
+ /**
+ * Standard expiry in epoch seconds
+ */
+ exp: number;
+
+ /**
+ * Standard issue time in epoch seconds
+ */
+ iat: number;
+}
+
+/**
+ * Creates a string claim that can be used as part of reconstructing a limited
+ * user token. The output of this function is only the signature part of a JWS.
+ */
+async function createUserIdentityClaim(options: {
+ header: {
+ typ: string;
+ alg: string;
+ kid?: string;
+ };
+ payload: BackstageUserIdentityProofPayload;
+ key: KeyLike | Uint8Array;
+}): Promise {
+ // NOTE: We reconstruct the header and payload structures carefully to
+ // perfectly guarantee ordering. The reason for this is that we store only
+ // the signature part of these to reduce duplication within the Backstage
+ // token. Anyone who wants to make an actual JWT based on all this must be
+ // able to do the EXACT reconstruction of the header and payload parts, to
+ // then append the signature.
+
+ const header = {
+ typ: options.header.typ,
+ alg: options.header.alg,
+ ...(options.header.kid ? { kid: options.header.kid } : {}),
+ };
+
+ const payload = {
+ sub: options.payload.sub,
+ iat: options.payload.iat,
+ exp: options.payload.exp,
+ };
+
+ const jws = await new GeneralSign(
+ new TextEncoder().encode(JSON.stringify(payload)),
+ )
+ .addSignature(options.key)
+ .setProtectedHeader(header)
+ .done()
+ .sign();
+
+ return jws.signatures[0].signature;
+}
diff --git a/plugins/auth-backend/src/identity/types.ts b/plugins/auth-backend/src/identity/types.ts
index 0693a2aba0..786e5d6481 100644
--- a/plugins/auth-backend/src/identity/types.ts
+++ b/plugins/auth-backend/src/identity/types.ts
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-import { TokenParams } from '@backstage/plugin-auth-node';
+import {
+ BackstageUserIdentity,
+ TokenParams,
+} from '@backstage/plugin-auth-node';
/** Represents any form of serializable JWK */
export interface AnyJWK extends Record {
@@ -31,7 +34,9 @@ export type TokenIssuer = {
/**
* Issues a new ID Token
*/
- issueToken(params: TokenParams): Promise;
+ issueToken(
+ params: TokenParams,
+ ): Promise<{ token: string; identity?: BackstageUserIdentity }>;
/**
* List all public keys that are currently being used to sign tokens, or have been used
diff --git a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts
index bbf42c70eb..5268833d14 100644
--- a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts
+++ b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts
@@ -77,8 +77,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext {
) {}
async issueToken(params: TokenParams) {
- const token = await this.tokenIssuer.issueToken(params);
- return { token };
+ return await this.tokenIssuer.issueToken(params);
}
async findCatalogUser(query: AuthResolverCatalogUserQuery) {
@@ -147,13 +146,12 @@ export class CatalogAuthResolverContext implements AuthResolverContext {
entity,
);
- const token = await this.tokenIssuer.issueToken({
+ return await this.tokenIssuer.issueToken({
claims: {
sub: stringifyEntityRef(entity),
ent: ownershipEntityRefs,
},
});
- return { token };
}
async resolveOwnershipEntityRefs(
diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts
index 1952e9d648..fd9373d5a3 100644
--- a/plugins/auth-backend/src/service/router.ts
+++ b/plugins/auth-backend/src/service/router.ts
@@ -81,6 +81,12 @@ export async function createRouter(
await authDb.get(),
);
+ const omitClaimsFromToken = config.getOptionalBoolean(
+ 'auth.omitIdentityTokenOwnershipClaim',
+ )
+ ? ['ent']
+ : [];
+
let tokenIssuer: TokenIssuer;
if (keyStore instanceof StaticKeyStore) {
tokenIssuer = new StaticTokenIssuer(
@@ -88,6 +94,8 @@ export async function createRouter(
logger: logger.child({ component: 'token-factory' }),
issuer: authUrl,
sessionExpirationSeconds: backstageTokenExpiration,
+ userInfoDatabaseHandler,
+ omitClaimsFromToken,
},
keyStore as StaticKeyStore,
);
@@ -101,6 +109,7 @@ export async function createRouter(
tokenFactoryAlgorithm ??
config.getOptionalString('auth.identityTokenAlgorithm'),
userInfoDatabaseHandler,
+ omitClaimsFromToken,
});
}
diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md
index a386cee68b..7fd817c2aa 100644
--- a/plugins/auth-node/CHANGELOG.md
+++ b/plugins/auth-node/CHANGELOG.md
@@ -1,5 +1,33 @@
# @backstage/plugin-auth-node
+## 0.6.3-next.1
+
+### Patch Changes
+
+- 332e934: Added the `identity` property to `BackstageSignInResult`.
+
+ The `prepareBackstageIdentityResponse` function will now also forward the `identity` to the response if present in the provided sign-in result.
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## 0.6.3-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 0.6.2
### Patch Changes
diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json
index d45292050a..744bff38ef 100644
--- a/plugins/auth-node/package.json
+++ b/plugins/auth-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-node",
- "version": "0.6.2",
+ "version": "0.6.3-next.1",
"backstage": {
"role": "node-library",
"pluginId": "auth",
diff --git a/plugins/auth-node/report.api.md b/plugins/auth-node/report.api.md
index f693683920..ad0d85030c 100644
--- a/plugins/auth-node/report.api.md
+++ b/plugins/auth-node/report.api.md
@@ -104,9 +104,7 @@ export type AuthResolverCatalogUserQuery =
// @public
export type AuthResolverContext = {
- issueToken(params: TokenParams): Promise<{
- token: string;
- }>;
+ issueToken(params: TokenParams): Promise;
findCatalogUser(query: AuthResolverCatalogUserQuery): Promise<{
entity: Entity;
}>;
@@ -126,6 +124,7 @@ export interface BackstageIdentityResponse extends BackstageSignInResult {
// @public
export interface BackstageSignInResult {
+ identity?: BackstageUserIdentity;
token: string;
}
diff --git a/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.test.ts b/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.test.ts
index d37224a11d..5d6527eaa1 100644
--- a/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.test.ts
+++ b/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.test.ts
@@ -44,6 +44,30 @@ describe('prepareBackstageIdentityResponse', () => {
});
});
+ it('uses the identity in the result if present', () => {
+ jest.spyOn(Date, 'now').mockReturnValue(5000);
+
+ const token = mkToken({ sub: 'k:ns/n', ent: ['k:ns/o'], exp: 1005 });
+ expect(
+ prepareBackstageIdentityResponse({
+ token,
+ identity: {
+ type: 'user',
+ userEntityRef: 'k:ns/other',
+ ownershipEntityRefs: ['k:ns/group1', 'k:ns/group2'],
+ },
+ }),
+ ).toEqual({
+ token,
+ expiresInSeconds: 1000,
+ identity: {
+ type: 'user',
+ userEntityRef: 'k:ns/other',
+ ownershipEntityRefs: ['k:ns/group1', 'k:ns/group2'],
+ },
+ });
+ });
+
it('should reject tokens without subject', () => {
const token = mkToken({});
expect(() =>
diff --git a/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts b/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts
index 33e9bafd0d..a1de4e365c 100644
--- a/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts
+++ b/plugins/auth-node/src/identity/prepareBackstageIdentityResponse.ts
@@ -57,7 +57,7 @@ export function prepareBackstageIdentityResponse(
return {
...result,
expiresInSeconds: exp,
- identity: {
+ identity: result.identity ?? {
type: 'user',
userEntityRef: sub,
ownershipEntityRefs: ent,
diff --git a/plugins/auth-node/src/types.ts b/plugins/auth-node/src/types.ts
index abcc6a2185..a7202d7748 100644
--- a/plugins/auth-node/src/types.ts
+++ b/plugins/auth-node/src/types.ts
@@ -34,6 +34,12 @@ export interface BackstageSignInResult {
* The token used to authenticate the user within Backstage.
*/
token: string;
+
+ /**
+ * Identity information to pass to the client rather than using the
+ * information that's embeeded in the token.
+ */
+ identity?: BackstageUserIdentity;
}
/**
@@ -141,7 +147,7 @@ export type AuthResolverContext = {
/**
* Issues a Backstage token using the provided parameters.
*/
- issueToken(params: TokenParams): Promise<{ token: string }>;
+ issueToken(params: TokenParams): Promise;
/**
* Finds a single user in the catalog using the provided query.
diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md
index ae1880e9e6..238b119291 100644
--- a/plugins/auth-react/CHANGELOG.md
+++ b/plugins/auth-react/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-auth-react
+## 0.1.15-next.0
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+
## 0.1.14
### Patch Changes
diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json
index 27b446a46d..214276dbc6 100644
--- a/plugins/auth-react/package.json
+++ b/plugins/auth-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-react",
- "version": "0.1.14",
+ "version": "0.1.15-next.0",
"description": "Web library for the auth plugin",
"backstage": {
"role": "web-library",
diff --git a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx
index a6860ad185..59c32c1f6c 100644
--- a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx
+++ b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx
@@ -57,7 +57,7 @@ export function useCookieAuthRefresh(options: {
if (!response.ok) {
// If we get a 404 from the cookie endpoint we assume that it does not
// exist and cookie auth is not needed. For all active tabs we don't
- // schedule another refresh for the forseeable future, but new tabs will
+ // schedule another refresh for the foreseeable future, but new tabs will
// still check if cookie auth has been added to the deployment.
// TODO(Rugvip): Once the legacy backend system is no longer supported we should remove this check
if (response.status === 404) {
diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md
index 3cc7761a69..68006127be 100644
--- a/plugins/bitbucket-cloud-common/CHANGELOG.md
+++ b/plugins/bitbucket-cloud-common/CHANGELOG.md
@@ -1,5 +1,36 @@
# @backstage/plugin-bitbucket-cloud-common
+## 0.3.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.1
+
+## 0.3.0-next.0
+
+### Minor Changes
+
+- 5850717: Update Bitbucket Cloud schema and models.
+
+ The latest schema was fetched from Bitbucket Cloud and stored locally.
+ Based on the updated schema, the models got regenerated.
+
+ **BREAKING:**
+
+ Due to the schema changes, the model update includes one breaking change:
+
+ - `Account.username` was removed.
+
+ Additionally, there were a couple of compatible changes including the addition of
+ `BaseCommit.committer` and others.
+
+### Patch Changes
+
+- 57ad208: Add support for `repo:updated` events as `Events.RepoUpdatedEvent`.
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+
## 0.2.29
### Patch Changes
diff --git a/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json b/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json
index ead80230c6..83645a6d22 100644
--- a/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json
+++ b/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json
@@ -634,68 +634,6 @@
}
]
},
- "/pullrequests/{selected_user}": {
- "get": {
- "tags": ["Pullrequests"],
- "description": "Returns all pull requests authored by the specified user.\n\nBy default only open pull requests are returned. This can be controlled\nusing the `state` query parameter. To retrieve pull requests that are\nin one of multiple states, repeat the `state` parameter for each\nindividual state.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details.",
- "summary": "List pull requests for a user",
- "responses": {
- "200": {
- "description": "All pull requests authored by the specified user.",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/paginated_pullrequests"
- }
- }
- }
- },
- "404": {
- "description": "If the specified user does not exist.",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/error"
- }
- }
- }
- }
- },
- "parameters": [
- {
- "name": "state",
- "in": "query",
- "description": "Only return pull requests that are in this state. This parameter can be repeated.",
- "schema": {
- "type": "string",
- "enum": ["OPEN", "MERGED", "DECLINED", "SUPERSEDED"]
- }
- }
- ],
- "security": [
- {
- "oauth2": ["pullrequest"]
- },
- {
- "basic": []
- },
- {
- "api_key": []
- }
- ]
- },
- "parameters": [
- {
- "name": "selected_user",
- "in": "path",
- "description": "This can either be the username of the pull request author, the author's UUID\nsurrounded by curly-braces, for example: `{account UUID}`, or the author's Atlassian ID.\n",
- "required": true,
- "schema": {
- "type": "string"
- }
- }
- ]
- },
"/repositories": {
"get": {
"tags": ["Repositories"],
@@ -2846,13 +2784,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pullrequest:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pullrequest"]
@@ -2863,6 +2794,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pullrequest:bitbucket"]
+ }
]
}
},
@@ -2923,6 +2861,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:repository:bitbucket"]
+ }
]
}
},
@@ -3013,6 +2958,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:repository:bitbucket"]
+ }
]
},
"get": {
@@ -3090,6 +3042,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:repository:bitbucket"]
+ }
]
},
"delete": {
@@ -3150,6 +3109,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:repository:bitbucket"]
+ }
]
}
},
@@ -3219,6 +3185,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:repository:bitbucket"]
+ }
]
},
"post": {
@@ -3305,6 +3278,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:repository:bitbucket"]
+ }
]
}
},
@@ -3393,6 +3373,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:repository:bitbucket"]
+ }
]
},
"put": {
@@ -3490,6 +3477,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:repository:bitbucket"]
+ }
]
},
"delete": {
@@ -3559,6 +3553,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:repository:bitbucket"]
+ }
]
}
},
@@ -3593,6 +3594,15 @@
}
},
"parameters": [
+ {
+ "name": "refname",
+ "in": "query",
+ "description": "If specified, only return commit status objects that were either\ncreated without a refname, or were created with the specified refname\n",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ },
{
"name": "q",
"in": "query",
@@ -3664,7 +3674,7 @@
"/repositories/{workspace}/{repo_slug}/commit/{commit}/statuses/build": {
"post": {
"tags": ["Commit statuses"],
- "description": "Creates a new build status against the specified commit.\n\nIf the specified key already exists, the existing status object will\nbe overwritten.\n\nExample:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/my-workspace/my-repo/commit/e10dae226959c2194f2b07b077c07762d93821cf/statuses/build/ -X POST -u jdoe -H 'Content-Type: application/json' -d '{\n \"key\": \"MY-BUILD\",\n \"state\": \"SUCCESSFUL\",\n \"description\": \"42 tests passed\",\n \"url\": \"https://www.example.org/my-build-result\"\n }'\n```\n\nWhen creating a new commit status, you can use a URI template for the URL.\nTemplates are URLs that contain variable names that Bitbucket will\nevaluate at runtime whenever the URL is displayed anywhere similar to\nparameter substitution in\n[Bitbucket Connect](https://developer.atlassian.com/bitbucket/concepts/context-parameters.html).\nFor example, one could use `https://foo.com/builds/{repository.full_name}`\nwhich Bitbucket will turn into `https://foo.com/builds/foo/bar` at render time.\nThe context variables available are `repository` and `commit`.",
+ "description": "Creates a new build status against the specified commit.\n\nIf the specified key already exists, the existing status object will\nbe overwritten.\n\nExample:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/my-workspace/my-repo/commit/e10dae226959c2194f2b07b077c07762d93821cf/statuses/build/ -X POST -u jdoe -H 'Content-Type: application/json' -d '{\n \"key\": \"MY-BUILD\",\n \"state\": \"SUCCESSFUL\",\n \"description\": \"42 tests passed\",\n \"url\": \"https://www.example.org/my-build-result\"\n }'\n```\n\nWhen creating a new commit status, you can use a URI template for the URL.\nTemplates are URLs that contain variable names that Bitbucket will\nevaluate at runtime whenever the URL is displayed anywhere similar to\nparameter substitution in\n[Bitbucket Connect](https://developer.atlassian.com/bitbucket/concepts/context-parameters.html).\nFor example, one could use `https://foo.com/builds/{repository.full_name}`\nwhich Bitbucket will turn into `https://foo.com/builds/foo/bar` at render time.\nThe context variables available are `repository` and `commit`.\n\nTo associate a commit status to a pull request, the refname field must be set to the source branch\nof the pull request.\n\nExample:\n```\ncurl https://api.bitbucket.org/2.0/repositories/my-workspace/my-repo/commit/e10dae226959c2194f2b07b077c07762d93821cf/statuses/build/ -X POST -u jdoe -H 'Content-Type: application/json' -d '{\n \"key\": \"MY-BUILD\",\n \"state\": \"SUCCESSFUL\",\n \"description\": \"42 tests passed\",\n \"url\": \"https://www.example.org/my-build-result\",\n \"refname\": \"my-pr-branch\"\n }'\n```",
"summary": "Create a build status for a commit",
"responses": {
"201": {
@@ -4929,13 +4939,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -4946,6 +4949,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -5006,13 +5016,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -5023,6 +5026,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -5083,6 +5093,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"post": {
@@ -5180,6 +5197,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -5270,6 +5294,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
},
"delete": {
@@ -5340,6 +5371,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -5723,6 +5761,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["write:repository:bitbucket"]
+ }
]
},
"parameters": [
@@ -5834,6 +5879,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:repository:bitbucket"]
+ }
]
},
"parameters": [
@@ -6085,13 +6137,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -6102,6 +6147,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"post": {
@@ -6190,6 +6242,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -6250,13 +6309,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -6267,6 +6319,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"delete": {
@@ -6328,6 +6387,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -6391,6 +6457,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -7479,15 +7552,8 @@
"description": "Deletes the specified issue. This requires write access to the\nrepository.",
"summary": "Delete an issue",
"responses": {
- "200": {
- "description": "The issue object.",
- "content": {
- "application/json": {
- "schema": {
- "$ref": "#/components/schemas/issue"
- }
- }
- }
+ "204": {
+ "description": "Indicates the issue was deleted successfully."
},
"403": {
"description": "When the authenticated user isn't authorized to delete the issue.",
@@ -9314,7 +9380,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:repository:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:repository:bitbucket"]
}
]
},
@@ -9342,7 +9408,7 @@
"/repositories/{workspace}/{repo_slug}/permissions-config/groups/{group_slug}": {
"delete": {
"tags": ["Repositories"],
- "description": "Deletes the repository group permission between the requested repository and group, if one exists.\n\nOnly users with admin permission for the repository may access this resource.",
+ "description": "Deletes the repository group permission between the requested repository and group, if one exists.\n\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method supported for this endpoint is via app passwords.",
"summary": "Delete an explicit group permission for a repository",
"responses": {
"204": {
@@ -9471,7 +9537,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:repository:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:repository:bitbucket"]
}
]
},
@@ -9708,7 +9774,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:repository:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:repository:bitbucket"]
}
]
},
@@ -9866,7 +9932,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:repository:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:repository:bitbucket"]
}
]
},
@@ -10005,7 +10071,7 @@
"get": {
"tags": ["Pipelines"],
"summary": "List pipelines",
- "description": "Find pipelines",
+ "description": "Find pipelines in a repository.\n\nNote that unlike other endpoints in the Bitbucket API, this endpoint utilizes query parameters to allow filtering\nand sorting of returned results. See [query parameters](#api-repositories-workspace-repo-slug-pipelines-get-request-Query%20parameters)\nfor specific details.\n",
"operationId": "getPipelinesForRepository",
"parameters": [
{
@@ -10025,6 +10091,148 @@
"schema": {
"type": "string"
}
+ },
+ {
+ "name": "creator.uuid",
+ "description": "The UUID of the creator of the pipeline to filter by.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "format": "uuid"
+ }
+ },
+ {
+ "name": "target.ref_type",
+ "description": "The type of the reference to filter by.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "enum": ["BRANCH", "TAG", "ANNOTATED_TAG"]
+ }
+ },
+ {
+ "name": "target.ref_name",
+ "description": "The reference name to filter by.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "target.branch",
+ "description": "The name of the branch to filter by.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "target.commit.hash",
+ "description": "The revision to filter by.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "target.selector.pattern",
+ "description": "The pipeline pattern to filter by.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "target.selector.type",
+ "description": "The type of pipeline to filter by.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "enum": ["BRANCH", "TAG", "CUSTOM", "PULLREQUESTS", "DEFAULT"]
+ }
+ },
+ {
+ "name": "created_on",
+ "description": "The creation date to filter by.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "format": "date-time"
+ }
+ },
+ {
+ "name": "trigger_type",
+ "description": "The trigger type to filter by.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "enum": ["PUSH", "MANUAL", "SCHEDULED", "PARENT_STEP"]
+ }
+ },
+ {
+ "name": "status",
+ "description": "The pipeline status to filter by.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "enum": [
+ "PARSING",
+ "PENDING",
+ "PAUSED",
+ "HALTED",
+ "BUILDING",
+ "ERROR",
+ "PASSED",
+ "FAILED",
+ "STOPPED",
+ "UNKNOWN"
+ ]
+ }
+ },
+ {
+ "name": "sort",
+ "description": "The attribute name to sort on.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "string",
+ "enum": ["creator.uuid", "created_on", "run_creation_date"]
+ }
+ },
+ {
+ "name": "page",
+ "description": "The page number of elements to retrieve.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "minimum": 1,
+ "default": 1
+ }
+ },
+ {
+ "name": "pagelen",
+ "description": "The maximum number of results to return.",
+ "required": false,
+ "in": "query",
+ "schema": {
+ "type": "integer",
+ "format": "int32",
+ "minimum": 1,
+ "maximum": 100,
+ "default": 10
+ }
}
],
"responses": {
@@ -10039,13 +10247,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -10056,6 +10257,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"post": {
@@ -10134,13 +10342,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket", "write:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -10151,6 +10352,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket", "write:pipeline:bitbucket"]
+ }
]
}
},
@@ -10202,13 +10410,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -10219,6 +10420,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"delete": {
@@ -10270,13 +10478,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["write:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline:write"]
@@ -10287,6 +10488,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["write:pipeline:bitbucket"]
+ }
]
}
},
@@ -10340,13 +10548,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["write:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline:write"]
@@ -10357,6 +10558,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["write:pipeline:bitbucket"]
+ }
]
}
},
@@ -10417,13 +10625,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -10434,6 +10635,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -10494,13 +10702,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -10511,6 +10712,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -10561,13 +10769,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -10578,6 +10779,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -10647,13 +10855,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -10664,6 +10865,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -10725,6 +10933,9 @@
}
}
},
+ "307": {
+ "description": "After the step is completed, the log is moved to long term storage and a redirection to the log file is returned."
+ },
"404": {
"description": "A pipeline with the given UUID does not exist, a step with the given UUID does not exist in the pipeline or a log file does not exist for the given step.",
"content": {
@@ -10746,13 +10957,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -10763,6 +10967,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -10823,6 +11034,9 @@
"200": {
"description": "The raw log file for the build container or service container."
},
+ "307": {
+ "description": "After the step is completed, the log is moved to long term storage and a redirection to the log file is returned."
+ },
"404": {
"description": "No account, repository, pipeline, step or log exist for the provided path.",
"content": {
@@ -10834,13 +11048,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -10851,6 +11058,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -10912,13 +11126,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -10929,6 +11136,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -10990,13 +11204,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -11007,6 +11214,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -11077,13 +11291,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -11094,6 +11301,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -11157,13 +11371,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["write:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline:write"]
@@ -11174,6 +11381,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["write:pipeline:bitbucket"]
+ }
]
}
},
@@ -11225,6 +11439,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:repository:bitbucket"]
+ }
]
},
"put": {
@@ -11285,6 +11506,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:repository:bitbucket"]
+ }
]
}
},
@@ -11367,6 +11595,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -11410,6 +11645,14 @@
"responses": {
"201": {
"description": "The created schedule.",
+ "headers": {
+ "Location": {
+ "description": "The URL of the newly created schedule.",
+ "schema": {
+ "type": "string"
+ }
+ }
+ },
"content": {
"application/json": {
"schema": {
@@ -11449,13 +11692,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket", "write:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline:write"]
@@ -11466,6 +11702,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket", "write:pipeline:bitbucket"]
+ }
]
},
"get": {
@@ -11515,13 +11758,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -11532,6 +11768,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -11592,13 +11835,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -11609,6 +11845,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"put": {
@@ -11678,13 +11921,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket", "write:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline:write"]
@@ -11695,6 +11931,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket", "write:pipeline:bitbucket"]
+ }
]
},
"delete": {
@@ -11746,13 +11989,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["write:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline:write"]
@@ -11763,6 +11999,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["write:pipeline:bitbucket"]
+ }
]
}
},
@@ -11823,13 +12066,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -11840,6 +12076,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
}
},
@@ -11891,13 +12134,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -11908,6 +12144,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"put": {
@@ -11978,6 +12221,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
},
"delete": {
@@ -12020,13 +12270,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["admin:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline:variable"]
@@ -12037,6 +12280,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -12078,13 +12328,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -12095,6 +12338,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"post": {
@@ -12183,6 +12433,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -12243,13 +12500,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -12260,6 +12510,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"put": {
@@ -12339,6 +12596,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
},
"delete": {
@@ -12390,13 +12654,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["admin:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline:variable"]
@@ -12407,6 +12664,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -12448,13 +12712,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -12465,6 +12722,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"post": {
@@ -12553,6 +12817,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -12613,13 +12884,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -12630,6 +12894,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"put": {
@@ -12709,6 +12980,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
},
"delete": {
@@ -12760,13 +13038,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["admin:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline:variable"]
@@ -12777,6 +13048,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -13034,7 +13312,7 @@
},
"post": {
"tags": ["Pullrequests"],
- "description": "Creates a new pull request where the destination repository is\nthis repository and the author is the authenticated user.\n\nThe minimum required fields to create a pull request are `title` and\n`source`, specified by a branch name.\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/my-workspace/my-repository/pullrequests \\\n -u my-username:my-password \\\n --request POST \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"staging\"\n }\n }\n }'\n```\n\nIf the pull request's `destination` is not specified, it will default\nto the `repository.mainbranch`. To open a pull request to a\ndifferent branch, say from a feature branch to a staging branch,\nspecify a `destination` (same format as the `source`):\n\n```\n{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"my-feature-branch\"\n }\n },\n \"destination\": {\n \"branch\": {\n \"name\": \"staging\"\n }\n }\n}\n```\n\nReviewers can be specified by adding an array of user objects as the\n`reviewers` property.\n\n```\n{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"my-feature-branch\"\n }\n },\n \"reviewers\": [\n {\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n }\n ]\n}\n```\n\nOther fields:\n\n* `description` - a string\n* `close_source_branch` - boolean that specifies if the source branch should be closed upon merging",
+ "description": "Creates a new pull request where the destination repository is\nthis repository and the author is the authenticated user.\n\nThe minimum required fields to create a pull request are `title` and\n`source`, specified by a branch name.\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/my-workspace/my-repository/pullrequests \\\n -u my-username:my-password \\\n --request POST \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"staging\"\n }\n }\n }'\n```\n\nIf the pull request's `destination` is not specified, it will default\nto the `repository.mainbranch`. To open a pull request to a\ndifferent branch, say from a feature branch to a staging branch,\nspecify a `destination` (same format as the `source`):\n\n```\n{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"my-feature-branch\"\n }\n },\n \"destination\": {\n \"branch\": {\n \"name\": \"staging\"\n }\n }\n}\n```\n\nReviewers can be specified by adding an array of user objects as the\n`reviewers` property.\n\n```\n{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"my-feature-branch\"\n }\n },\n \"reviewers\": [\n {\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n }\n ]\n}\n```\n\nOther fields:\n\n* `description` - a string\n* `close_source_branch` - boolean that specifies if the source branch should be closed upon merging\n* `draft` - boolean that specifies whether the pull request is a draft",
"summary": "Create a pull request",
"responses": {
"201": {
@@ -14385,6 +14663,9 @@
"202": {
"description": "In the Location header, the URL to poll for the pull request merge status"
},
+ "409": {
+ "description": "Unable to merge because one of the refs involved changed while attempting to merge"
+ },
"555": {
"description": "If the merge took too long and timed out.\nIn this case the caller should retry the request later",
"content": {
@@ -14482,6 +14763,9 @@
},
"403": {
"description": "The user making the request does not have permission to the repo and is different from the user who queued the task"
+ },
+ "409": {
+ "description": "Unable to merge because one of the refs involved changed while attempting to merge"
}
},
"security": [
@@ -14970,7 +15254,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/A_pullrequest_comment_task"
+ "$ref": "#/components/schemas/pullrequest_comment_task"
}
}
}
@@ -15010,7 +15294,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/A_pullrequest_task_create"
+ "$ref": "#/components/schemas/pullrequest_task_create"
}
}
},
@@ -15125,7 +15409,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/A_pullrequest_comment_task"
+ "$ref": "#/components/schemas/pullrequest_comment_task"
}
}
}
@@ -15180,7 +15464,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/A_pullrequest_comment_task"
+ "$ref": "#/components/schemas/pullrequest_comment_task"
}
}
}
@@ -15220,7 +15504,7 @@
"content": {
"application/json": {
"schema": {
- "$ref": "#/components/schemas/A_pullrequest_task_update"
+ "$ref": "#/components/schemas/pullrequest_task_update"
}
}
},
@@ -15635,7 +15919,10 @@
"merge_strategies": [
"merge_commit",
"squash",
- "fast_forward"
+ "fast_forward",
+ "squash_fast_forward",
+ "rebase_fast_forward",
+ "rebase_merge"
],
"type": "branch",
"target": {
@@ -15947,7 +16234,10 @@
"merge_strategies": [
"merge_commit",
"squash",
- "fast_forward"
+ "fast_forward",
+ "squash_fast_forward",
+ "rebase_fast_forward",
+ "rebase_merge"
],
"type": "branch",
"target": {
@@ -19502,6 +19792,349 @@
}
]
},
+ "/users/{selected_user}/gpg-keys": {
+ "get": {
+ "tags": ["GPG"],
+ "description": "Returns a paginated list of the user's GPG public keys.\nThe `key` and `subkeys` fields can also be requested from the endpoint.\nSee [Partial Responses](/cloud/bitbucket/rest/intro/#partial-response) for more details.",
+ "summary": "List GPG keys",
+ "responses": {
+ "200": {
+ "description": "A list of the GPG keys associated with the account.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/paginated_gpg_user_keys"
+ },
+ "examples": {
+ "response": {
+ "value": {
+ "page": 1,
+ "pagelen": 10,
+ "size": 1,
+ "values": [
+ {
+ "added_on": "2024-09-24T12:09:33.154081+00:00",
+ "created_on": "2018-03-20T13:09:04.207005+00:00",
+ "expires_on": null,
+ "fingerprint": "h4m26ppxrol7blr7spbc7lhyymokdbnkxd06kzrt",
+ "key_id": "ymokdbnkxd06kzrt",
+ "last_used": "2024-09-25T15:18:05.196003+00:00",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}/gpg-keys/H4M26PPXROL7BLR7SPBC7LHYYMOKDBNKXD06KZRT"
+ }
+ },
+ "name": "Alice's OpenPGP key",
+ "owner": {
+ "display_name": "Mark Adams",
+ "links": {
+ "avatar": {
+ "href": "https://bitbucket.org/account/markadams-atl/avatar/32/"
+ },
+ "html": {
+ "href": "https://bitbucket.org/markadams-atl/"
+ },
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}"
+ }
+ },
+ "type": "user",
+ "username": "markadams-atl",
+ "nickname": "markadams-atl",
+ "uuid": "{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}"
+ },
+ "parent_fingerprint": null,
+ "type": "gpg_key"
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "If the specified user's keys are not accessible to the current user"
+ },
+ "404": {
+ "description": "If the specified user does not exist",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "oauth2": ["account"]
+ },
+ {
+ "basic": []
+ },
+ {
+ "api_key": []
+ }
+ ]
+ },
+ "post": {
+ "tags": ["GPG"],
+ "description": "Adds a new GPG public key to the specified user account and returns the resulting key.\n\nExample:\n\n```\n$ curl -X POST -H \"Content-Type: application/json\" -d\n'{\"key\": \"\"}'\nhttps://api.bitbucket.org/2.0/users/{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}/gpg-keys\n```",
+ "summary": "Add a new GPG key",
+ "responses": {
+ "201": {
+ "description": "The newly created GPG key.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GPG_account_key"
+ },
+ "examples": {
+ "response": {
+ "value": {
+ "added_on": "2024-09-24T12:09:33.154081+00:00",
+ "created_on": "2018-03-20T13:09:04.207005+00:00",
+ "expires_on": null,
+ "fingerprint": "h4m26ppxrol7blr7spbc7lhyymokdbnkxd06kzrt",
+ "key_id": "ymokdbnkxd06kzrt",
+ "last_used": "2024-09-25T15:18:05.196003+00:00",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}/gpg-keys/H4M26PPXROL7BLR7SPBC7LHYYMOKDBNKXD06KZRT"
+ }
+ },
+ "name": "Alice's OpenPGP key",
+ "owner": {
+ "display_name": "Mark Adams",
+ "links": {
+ "avatar": {
+ "href": "https://bitbucket.org/account/markadams-atl/avatar/32/"
+ },
+ "html": {
+ "href": "https://bitbucket.org/markadams-atl/"
+ },
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}"
+ }
+ },
+ "type": "user",
+ "username": "markadams-atl",
+ "nickname": "markadams-atl",
+ "uuid": "{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}"
+ },
+ "parent_fingerprint": null,
+ "type": "gpg_key"
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "If the submitted key or related value is invalid",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "If the current user does not have permission to add a key for the specified user"
+ },
+ "404": {
+ "description": "If the specified user does not exist",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ }
+ },
+ "requestBody": {
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GPG_account_key"
+ }
+ }
+ },
+ "description": "The new GPG key object."
+ },
+ "security": [
+ {
+ "oauth2": ["account:write"]
+ },
+ {
+ "basic": []
+ },
+ {
+ "api_key": []
+ }
+ ]
+ },
+ "parameters": [
+ {
+ "name": "selected_user",
+ "in": "path",
+ "description": "This can either be an Atlassian Account ID OR the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`.\n",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ]
+ },
+ "/users/{selected_user}/gpg-keys/{fingerprint}": {
+ "delete": {
+ "tags": ["GPG"],
+ "description": "Deletes a specific GPG public key from a user's account.",
+ "summary": "Delete a GPG key",
+ "responses": {
+ "204": {
+ "description": "The key has been deleted"
+ },
+ "400": {
+ "description": "If the submitted key or related value is invalid",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "If the current user does not have permission to delete a key for the specified user, or the submitted key is a subkey"
+ },
+ "404": {
+ "description": "If the specified key does not exist",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "oauth2": ["account:write"]
+ },
+ {
+ "basic": []
+ },
+ {
+ "api_key": []
+ }
+ ]
+ },
+ "get": {
+ "tags": ["GPG"],
+ "description": "Returns a specific GPG public key belonging to a user.\nThe `key` and `subkeys` fields can also be requested from the endpoint.\nSee [Partial Responses](/cloud/bitbucket/rest/intro/#partial-response) for more details.",
+ "summary": "Get a GPG key",
+ "responses": {
+ "200": {
+ "description": "The specific GPG key matching the user and fingerprint.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/GPG_account_key"
+ },
+ "examples": {
+ "response": {
+ "value": {
+ "added_on": "2024-09-24T12:09:33.154081+00:00",
+ "created_on": "2018-03-20T13:09:04.207005+00:00",
+ "expires_on": null,
+ "fingerprint": "h4m26ppxrol7blr7spbc7lhyymokdbnkxd06kzrt",
+ "key_id": "ymokdbnkxd06kzrt",
+ "last_used": "2024-09-25T15:18:05.196003+00:00",
+ "links": {
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}/gpg-keys/H4M26PPXROL7BLR7SPBC7LHYYMOKDBNKXD06KZRT"
+ }
+ },
+ "name": "Alice's OpenPGP key",
+ "owner": {
+ "display_name": "Mark Adams",
+ "links": {
+ "avatar": {
+ "href": "https://bitbucket.org/account/markadams-atl/avatar/32/"
+ },
+ "html": {
+ "href": "https://bitbucket.org/markadams-atl/"
+ },
+ "self": {
+ "href": "https://api.bitbucket.org/2.0/users/{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}"
+ }
+ },
+ "type": "user",
+ "username": "markadams-atl",
+ "nickname": "markadams-atl",
+ "uuid": "{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}"
+ },
+ "parent_fingerprint": null,
+ "type": "gpg_key"
+ }
+ }
+ }
+ }
+ }
+ },
+ "403": {
+ "description": "If the specified user's keys are not accessible to the current user"
+ },
+ "404": {
+ "description": "If the specified user does not exist",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ }
+ },
+ "security": [
+ {
+ "oauth2": ["account"]
+ },
+ {
+ "basic": []
+ },
+ {
+ "api_key": []
+ }
+ ]
+ },
+ "parameters": [
+ {
+ "name": "fingerprint",
+ "in": "path",
+ "description": "A GPG key fingerprint.\n",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "selected_user",
+ "in": "path",
+ "description": "This can either be an Atlassian Account ID OR the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`.\n",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ]
+ },
"/users/{selected_user}/pipelines_config/variables": {
"get": {
"tags": ["Pipelines"],
@@ -20139,7 +20772,7 @@
},
"/users/{selected_user}/ssh-keys": {
"get": {
- "tags": ["Ssh"],
+ "tags": ["SSH"],
"description": "Returns a paginated list of the user's SSH public keys.",
"summary": "List SSH keys",
"responses": {
@@ -20160,6 +20793,8 @@
{
"comment": "user@myhost",
"created_on": "2018-03-14T13:17:05.196003+00:00",
+ "expires_on": null,
+ "fingerprint": "w9tZSiFoZ8oXwS4ZSWrJopGxwvnPIewA6Mo4owAHPrI",
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY",
"label": "",
"last_used": "2018-03-20T13:18:05.196003+00:00",
@@ -20223,7 +20858,7 @@
]
},
"post": {
- "tags": ["Ssh"],
+ "tags": ["SSH"],
"description": "Adds a new SSH public key to the specified user account and returns the resulting key.\n\nExample:\n\n```\n$ curl -X POST -H \"Content-Type: application/json\" -d '{\"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY user@myhost\"}' https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys\n```",
"summary": "Add a new SSH key",
"responses": {
@@ -20239,6 +20874,8 @@
"value": {
"comment": "user@myhost",
"created_on": "2018-03-14T13:17:05.196003+00:00",
+ "expires_on": null,
+ "fingerprint": "w9tZSiFoZ8oXwS4ZSWrJopGxwvnPIewA6Mo4owAHPrI",
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY",
"label": "",
"last_used": "2018-03-20T13:18:05.196003+00:00",
@@ -20297,6 +20934,17 @@
}
}
},
+ "parameters": [
+ {
+ "name": "expires_on",
+ "in": "query",
+ "description": "The date or date-time of when the key will expire,\nin [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601) format.\nExample: `YYYY-MM-DDTHH:mm:ss.sssZ`",
+ "required": false,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ],
"requestBody": {
"content": {
"application/json": {
@@ -20333,7 +20981,7 @@
},
"/users/{selected_user}/ssh-keys/{key_id}": {
"delete": {
- "tags": ["Ssh"],
+ "tags": ["SSH"],
"description": "Deletes a specific SSH public key from a user's account.",
"summary": "Delete a SSH key",
"responses": {
@@ -20377,7 +21025,7 @@
]
},
"get": {
- "tags": ["Ssh"],
+ "tags": ["SSH"],
"description": "Returns a specific SSH public key belonging to a user.",
"summary": "Get a SSH key",
"responses": {
@@ -20393,6 +21041,8 @@
"value": {
"comment": "user@myhost",
"created_on": "2018-03-14T13:17:05.196003+00:00",
+ "expires_on": null,
+ "fingerprint": "w9tZSiFoZ8oXwS4ZSWrJopGxwvnPIewA6Mo4owAHPrI",
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY",
"label": "",
"last_used": "2018-03-20T13:18:05.196003+00:00",
@@ -20454,7 +21104,7 @@
]
},
"put": {
- "tags": ["Ssh"],
+ "tags": ["SSH"],
"description": "Updates a specific SSH public key on a user's account\n\nNote: Only the 'comment' field can be updated using this API. To modify the key or comment values, you must delete and add the key again.\n\nExample:\n\n```\n$ curl -X PUT -H \"Content-Type: application/json\" -d '{\"label\": \"Work key\"}' https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/{b15b6026-9c02-4626-b4ad-b905f99f763a}\n```",
"summary": "Update a SSH key",
"responses": {
@@ -20470,6 +21120,8 @@
"value": {
"comment": "",
"created_on": "2018-03-14T13:17:05.196003+00:00",
+ "expires_on": null,
+ "fingerprint": "w9tZSiFoZ8oXwS4ZSWrJopGxwvnPIewA6Mo4owAHPrI",
"key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY",
"label": "Work key",
"last_used": "2018-03-20T13:18:05.196003+00:00",
@@ -21007,7 +21659,7 @@
"/workspaces/{workspace}/members": {
"get": {
"tags": ["Workspaces"],
- "description": "Returns all members of the requested workspace.",
+ "description": "Returns all members of the requested workspace.\n\nThis endpoint additionally supports [filtering](/cloud/bitbucket/rest/intro/#filtering) by\nemail address, if called by a workspace administrator, integration or workspace access\ntoken. This is done by adding the following query string parameter:\n\n* `q=user.email IN (\"user1@org.com\",\"user2@org.com\")`\n\nWhen filtering by email, you can query up to 90 addresses at a time.\nNote that the query parameter values need to be URL escaped, so the final query string\nshould be:\n\n* `q=user.email%20IN%20(%22user1@org.com%22,%22user2@org.com%22)`\n\nEmail addresses that you filter by (and only these email addresses) can be included in the\nresponse using the `fields` query parameter:\n\n* `&fields=+values.user.email` - add the `email` field to the default `user` response object\n* `&fields=values.user.email,values.user.account_id` - only return user email addresses and\naccount IDs\n\nOnce again, all query parameter values must be URL escaped.",
"summary": "List users in a workspace",
"responses": {
"200": {
@@ -21020,6 +21672,16 @@
}
}
},
+ "400": {
+ "description": "When more than 90 emails were provided when querying by email.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ },
"401": {
"description": "The request wasn't authenticated.",
"content": {
@@ -21046,7 +21708,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:workspace:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:workspace:bitbucket"]
}
]
},
@@ -21114,7 +21776,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:workspace:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:workspace:bitbucket"]
}
]
},
@@ -21234,7 +21896,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:workspace:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:workspace:bitbucket"]
}
]
},
@@ -21368,7 +22030,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:repository:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:repository:bitbucket"]
}
]
},
@@ -21487,7 +22149,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:repository:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:repository:bitbucket"]
}
]
},
@@ -21631,13 +22293,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -21648,6 +22303,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"post": {
@@ -21719,6 +22381,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -21770,13 +22439,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline"]
@@ -21787,6 +22449,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:pipeline:bitbucket"]
+ }
]
},
"put": {
@@ -21849,6 +22518,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
},
"delete": {
@@ -21891,13 +22567,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["admin:pipeline:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["pipeline:variable"]
@@ -21908,6 +22577,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["admin:pipeline:bitbucket"]
+ }
]
}
},
@@ -21981,7 +22657,7 @@
}
},
"403": {
- "description": "The requesting user isn't authorized to create the project.",
+ "description": "The user requesting to create a project does not have the necessary permissions.",
"content": {
"application/json": {
"schema": {
@@ -23420,7 +24096,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:project:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:project:bitbucket"]
}
]
},
@@ -23576,7 +24252,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:project:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:project:bitbucket"]
}
]
},
@@ -23812,7 +24488,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:project:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:project:bitbucket"]
}
]
},
@@ -23969,7 +24645,7 @@
{
"state": "Current",
"scheme": "oauth2",
- "scopes": ["read:project:bitbucket", "read:user:bitbucket"]
+ "scopes": ["read:project:bitbucket"]
}
]
},
@@ -24103,6 +24779,77 @@
}
]
},
+ "/workspaces/{workspace}/pullrequests/{selected_user}": {
+ "get": {
+ "tags": ["Workspaces", "Pullrequests"],
+ "description": "Returns all workspace pull requests authored by the specified user.\n\nBy default only open pull requests are returned. This can be controlled\nusing the `state` query parameter. To retrieve pull requests that are\nin one of multiple states, repeat the `state` parameter for each\nindividual state.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details.",
+ "summary": "List workspace pull requests for a user",
+ "responses": {
+ "200": {
+ "description": "All pull requests authored by the specified user.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/paginated_pullrequests"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "If the specified user does not exist.",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/error"
+ }
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "state",
+ "in": "query",
+ "description": "Only return pull requests that are in this state. This parameter can be repeated.",
+ "schema": {
+ "type": "string",
+ "enum": ["OPEN", "MERGED", "DECLINED", "SUPERSEDED"]
+ }
+ }
+ ],
+ "security": [
+ {
+ "oauth2": ["pullrequest"]
+ },
+ {
+ "basic": []
+ },
+ {
+ "api_key": []
+ }
+ ]
+ },
+ "parameters": [
+ {
+ "name": "selected_user",
+ "in": "path",
+ "description": "This can either be the username of the pull request author, the author's UUID\nsurrounded by curly-braces, for example: `{account UUID}`, or the author's Atlassian ID.\n",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ },
+ {
+ "name": "workspace",
+ "in": "path",
+ "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n",
+ "required": true,
+ "schema": {
+ "type": "string"
+ }
+ }
+ ]
+ },
"/workspaces/{workspace}/search/code": {
"get": {
"tags": ["Search"],
@@ -24267,13 +25014,6 @@
}
}
},
- "x-atlassian-oauth2-scopes": [
- {
- "state": "Current",
- "scheme": "oauth2",
- "scopes": ["read:repository:bitbucket"]
- }
- ],
"security": [
{
"oauth2": ["repository"]
@@ -24284,6 +25024,13 @@
{
"api_key": []
}
+ ],
+ "x-atlassian-oauth2-scopes": [
+ {
+ "state": "Current",
+ "scheme": "oauth2",
+ "scopes": ["read:repository:bitbucket"]
+ }
]
}
}
@@ -24317,6 +25064,10 @@
"name": "Downloads",
"description": "Access the list of download links associated with the repository."
},
+ {
+ "name": "GPG",
+ "description": "The GPG resource allows you to manage GPG keys.\n"
+ },
{
"name": "Issue tracker",
"description": "The issue resources provide functionality for getting information on\nissues in an issue tracker, creating new issues, updating them and deleting\nthem.\n\nYou can access public issues without authentication, but you can't gain access\nto private repositories' issues. By authenticating, you will get the ability\nto create issues, as well as access to updating data or deleting issues you\nhave access to. Issue Tracker features are not supported for repositories in workspaces administered through admin.atlassian.com.\n"
@@ -24345,6 +25096,10 @@
"name": "Repositories",
"description": "A Git repository is a virtual storage of your project. It\nallows you to save versions of your code, which you can access\nwhen needed. The repo resource allows you to access public repos,\nor repos that belong to a specific workspace.\n"
},
+ {
+ "name": "SSH",
+ "description": "The SSH resource allows you to manage SSH keys.\n"
+ },
{
"name": "Snippets",
"description": "Snippets allow you share code segments or files with yourself, members of\nyour workspace, or the world.\n\nLike pull requests, repositories and workspaces, the full set of snippets\nis defined by what the current user has access to. This includes all\nsnippets owned by any of the workspaces the user is a member of, or\nsnippets by other users that the current user is either watching or has\n collaborated on (for instance by commenting on it).\n"
@@ -24353,10 +25108,6 @@
"name": "Source",
"description": "Browse the source code in the repository and\n create new commits by uploading."
},
- {
- "name": "Ssh",
- "description": "The SSH resource allows you to manage SSH keys.\n"
- },
{
"name": "Teams",
"description": "The teams resource has been deprecated, and the workspaces\nendpoint should be used instead.\n\nThe teams resource returns all the teams that the authenticated\nuser is associated with.\n"
@@ -24378,64 +25129,64 @@
"description": "A workspace is where you create repositories, collaborate on\nyour code, and organize different streams of work in your Bitbucket\nCloud account. Workspaces replace the use of teams and users in API\ncalls.\n"
}
],
- "x-revision": "d8aa33b24a80",
+ "x-revision": "877077a77316",
"x-atlassian-narrative": {
"documents": [
{
"anchor": "authentication",
"title": "Authentication methods",
"description": "How to authenticate API actions",
- "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTcuNjQ3MyAxODYuODEzOCI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5jbHMtMSB7CiAgICAgICAgaXNvbGF0aW9uOiBpc29sYXRlOwogICAgICB9CgogICAgICAuY2xzLTIgewogICAgICAgIGZpbGw6ICNkZTM1MGI7CiAgICAgIH0KCiAgICAgIC5jbHMtMyB7CiAgICAgICAgZmlsbDogI2ZmNTYzMDsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZGZlMWU1OwogICAgICAgIG1peC1ibGVuZC1tb2RlOiBtdWx0aXBseTsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjZmFmYmZjOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIGZpbGw6ICNlYmVjZjA7CiAgICAgIH0KCiAgICAgIC5jbHMtNyB7CiAgICAgICAgZmlsbDogbm9uZTsKICAgICAgICBzdHJva2U6ICMwMDY1ZmY7CiAgICAgICAgc3Ryb2tlLW1pdGVybGltaXQ6IDEwOwogICAgICAgIHN0cm9rZS13aWR0aDogMnB4OwogICAgICB9CgogICAgICAuY2xzLTggewogICAgICAgIGZpbGw6ICM1ZTZjODQ7CiAgICAgIH0KCiAgICAgIC5jbHMtOSB7CiAgICAgICAgZmlsbDogIzI1Mzg1ODsKICAgICAgfQoKICAgICAgLmNscy0xMCB7CiAgICAgICAgZmlsbDogIzI2ODRmZjsKICAgICAgfQoKICAgICAgLmNscy0xMSB7CiAgICAgICAgZmlsbDogIzAwNjVmZjsKICAgICAgfQogICAgPC9zdHlsZT4KICA8L2RlZnM+CiAgPHRpdGxlPlNlY3VyaXR5IHdpdGggS2V5PC90aXRsZT4KICA8ZyBjbGFzcz0iY2xzLTEiPgogICAgPGcgaWQ9IkxheWVyXzIiIGRhdGEtbmFtZT0iTGF5ZXIgMiI+CiAgICAgIDxnIGlkPSJPYmplY3RzIj4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik00Mi4wNjcyLDBoLjYxMTRhOCw4LDAsMCwxLDgsOFYyMy4yMzM4YTAsMCwwLDAsMSwwLDBIMzQuMDY3MmEwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDQyLjA2NzIsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xMDguMjIsMGguNjExNGE4LDgsMCwwLDEsOCw4VjIzLjIzMzhhMCwwLDAsMCwxLDAsMEgxMDAuMjJhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSwxMDguMjIsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xNzQuMzcyMiwwaC42MTE0YTgsOCwwLDAsMSw4LDhWMjMuMjMzOGEwLDAsMCwwLDEsMCwwSDE2Ni4zNzIyYTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMTc0LjM3MjIsMFoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjM0LjA2NzIiIHk9IjIzLjIzMzgiIHdpZHRoPSIxNjMuNTgiIGhlaWdodD0iMTYzLjU4Ii8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNDIuMDY3MiwwSDU5LjI5YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDM0LjA2NzJhMCwwLDAsMCwxLDAsMFY4YTgsOCwwLDAsMSw4LThaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTA3LjI0NTgsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDk5LjI0NThhMCwwLDAsMCwxLDAsMFY4YTgsOCwwLDAsMSw4LThaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTcyLjQyNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDE2NC40MjQ0YTAsMCwwLDAsMSwwLDBWOGE4LDgsMCwwLDEsOC04WiIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMyIgeD0iMTcuNDU1OCIgeT0iMjMuMjMzOCIgd2lkdGg9IjE2My41OCIgaGVpZ2h0PSIxNjMuNTgiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0yNS40NTU4LDBINDIuNjc4NmE4LDgsMCwwLDEsOCw4VjIzLjIyMjhhMCwwLDAsMCwxLDAsMEgxNy40NTU4YTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMjUuNDU1OCwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTkwLjYzNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDgyLjYzNDRhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSw5MC42MzQ0LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTU1LjgxMywwaDE3LjIyMjhhOCw4LDAsMCwxLDgsOFYyMy4yMjI4YTAsMCwwLDAsMSwwLDBIMTQ3LjgxM2EwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDE1NS44MTMsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0yNS40NTU4LDBINDIuNjc4NmE4LDgsMCwwLDEsOCw4VjIzLjIyMjhhMCwwLDAsMCwxLDAsMEgxNy40NTU4YTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMjUuNDU1OCwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTkwLjYzNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDgyLjYzNDRhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSw5MC42MzQ0LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTU1LjgxMywwaDE3LjIyMjhhOCw4LDAsMCwxLDgsOFYyMy4yMjI4YTAsMCwwLDAsMSwwLDBIMTQ3LjgxM2EwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDE1NS44MTMsMFoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjM1Ljc1OTYiIHk9IjU2LjgwNjUiIHdpZHRoPSIzMy4yMjI4IiBoZWlnaHQ9IjE1LjYwMzgiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjEzMS4yMDE2IiB5PSIxMzYuOTYxNSIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTU3LjM3MDksNzEuNjAzNmg3MC43NWE5LDksMCwwLDEsOSw5djM1LjM3NDlhNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLTQ0LjM3NDgsNDQuMzc0OGgwYTQ0LjM3NDgsNDQuMzc0OCwwLDAsMS00NC4zNzQ4LTQ0LjM3NDhWODAuNjAzNkE5LDksMCwwLDEsNTcuMzcwOSw3MS42MDM2WiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNSIgZD0iTTY2LjM3MSw2Ni42NjE3aDcwLjc1YTksOSwwLDAsMSw5LDl2MzUuMzc0OWE0NC4zNzQ4LDQ0LjM3NDgsMCwwLDEtNDQuMzc0OCw0NC4zNzQ4aDBBNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLDU3LjM3MSwxMTEuMDM2NlY3NS42NjE3YTksOSwwLDAsMSw5LTlaIi8+CiAgICAgICAgPHBhdGggaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIGQ9Ik02MS4zNzEsNjYuNjYxN2g3MC43NWE5LDksMCwwLDEsOSw5djM1LjM3NDlhNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLTQ0LjM3NDgsNDQuMzc0OGgwQTQ0LjM3NDgsNDQuMzc0OCwwLDAsMSw1Mi4zNzEsMTExLjAzNjZWNzUuNjYxN0E5LDksMCwwLDEsNjEuMzcxLDY2LjY2MTdaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy03IiBkPSJNOTYuNzQ1OSwxNDcuNzQ0MWEzNi43NDg3LDM2Ljc0ODcsMCwwLDEtMzYuNzA3NC0zNi43MDc0Vjc4LjA1ODRhMy43MzMzLDMuNzMzMywwLDAsMSwzLjcyOS0zLjcyOWg2NS45NTYzYTMuNzMzMywzLjczMzMsMCwwLDEsMy43MjksMy43Mjl2MzIuOTc4NEEzNi43NDg2LDM2Ljc0ODYsMCwwLDEsOTYuNzQ1OSwxNDcuNzQ0MVoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMDAuNjg5MywxNjMuMzE2N1YxMTEuMDk3M2EzLjk0NDMsMy45NDQzLDAsMCwwLTcuODg4NywwdjUyLjIyYTIyLjUyNTIsMjIuNTI1MiwwLDAsMC0xOC41NDc5LDIyLjE0YzAsLjQ1Ni4wMTc4LjkwNzguMDQ0NywxLjM1NzFIODIuMjFjLS4wNDE0LS40NDc0LS4wNjg4LS44OTktLjA2ODgtMS4zNTcxYTE0LjYyLDE0LjYyLDAsMCwxLDE0LjU5NzQtMTQuNjA0MWwuMDA2MS4wMDA2LjAwNjgtLjAwMDdBMTQuNjIxMSwxNC42MjExLDAsMCwxLDExMS4zNSwxODUuNDU2NmMwLC40NTgxLS4wMjczLjkxLS4wNjg4LDEuMzU3MWg3LjkxMjhjLjAyNjktLjQ0OTMuMDQ0Ny0uOTAxMS4wNDQ3LTEuMzU3MUEyMi41MjU5LDIyLjUyNTksMCwwLDAsMTAwLjY4OTMsMTYzLjMxNjdaIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxNy40NTU4IiB5PSIzNi40NzAyIiB3aWR0aD0iMzMuMjIyOCIgaGVpZ2h0PSIxNS42MDM4Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxNy40NTU4IiB5PSIxNTguMTIxNyIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMTQ3LjgxMyIgeT0iMzYuNDcwMiIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMTUwLjA2NDMiIHk9IjE1Ny41NTEzIiB3aWR0aD0iMzMuMjIyOCIgaGVpZ2h0PSIxNS42MDM4Ii8+CiAgICAgICAgPHBhdGggaWQ9Il9QYXRoXyIgZGF0YS1uYW1lPSImbHQ7UGF0aCZndDsiIGNsYXNzPSJjbHMtOCIgZD0iTTEwNy41MjU0LDEwMS4wMDI3YTExLjc3OTQsMTEuNzc5NCwwLDEsMC0xOS44Niw4LjU1NDhBNC4wNDE3LDQuMDQxNywwLDAsMSw4OC44NSwxMTMuNjJsLTIuMTA0LDcuMjY4MWEzLDMsMCwwLDAsMi44ODE3LDMuODM0MmgxMi4yMzcxYTMsMywwLDAsMCwyLjg4MTctMy44MzQybC0yLjA5NTktNy4yNGE0LjA3NDMsNC4wNzQzLDAsMCwxLDEuMTgwOC00LjA5NDVBMTEuNzE3MiwxMS43MTcyLDAsMCwwLDEwNy41MjU0LDEwMS4wMDI3WiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTEwNC43NDYxLDEyMC44ODc3bC0yLjA5NTktNy4yNGE0LjA3NDQsNC4wNzQ0LDAsMCwxLDEuMTgwOC00LjA5NDUsMTEuNzYyOSwxMS43NjI5LDAsMCwwLTUuMDYtMTkuOTMxMywxMS45MSwxMS45MSwwLDAsMC04Ljc5OCwxMC45OTQ5LDExLjcxODUsMTEuNzE4NSwwLDAsMCwzLjY5MjksOC45NDFBNC4wNDE2LDQuMDQxNiwwLDAsMSw5NC44NSwxMTMuNjJsLTMuMjE0LDExLjEwMjNoMTAuMjI4OEEzLDMsMCwwLDAsMTA0Ljc0NjEsMTIwLjg4NzdaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTgxLjc5NzUsMTAwLjMxYTMuOTQzOSwzLjk0MzksMCwwLDAtMy45NDQzLTMuOTQ0M0g0MS4wNDE3YTMuOTQ0MywzLjk0NDMsMCwwLDAsMCw3Ljg4ODdINzcuODUzMkEzLjk0MzksMy45NDM5LDAsMCwwLDgxLjc5NzUsMTAwLjMxWiIvPgogICAgICAgIDxwYXRoIGlkPSJfUGF0aF8yIiBkYXRhLW5hbWU9IiZsdDtQYXRoJmd0OyIgY2xhc3M9ImNscy0xMSIgZD0iTTQxLjA0MTYsMTA0LjI1MzlIOTYuODUzMmEzLjk0NDMsMy45NDQzLDAsMCwwLDAtNy44ODg3SDQxLjA0MTZhMy45NDQzLDMuOTQ0MywwLDAsMCwwLDcuODg4N1oiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTEwIiBkPSJNODEuNzk3NSwxMDAuMzFhMy45NDM5LDMuOTQzOSwwLDAsMC0zLjk0NDMtMy45NDQzSDQxLjA0MTdhMy45NDQzLDMuOTQ0MywwLDAsMCwwLDcuODg4N0g3Ny44NTMyQTMuOTQzOSwzLjk0MzksMCwwLDAsODEuNzk3NSwxMDAuMzFaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTIyLjQ5MzIsMTIyLjgwMjlBMjIuNDkyOSwyMi40OTI5LDAsMSwxLDQ0Ljk4NTgsMTAwLjMxLDIyLjUxODUsMjIuNTE4NSwwLDAsMSwyMi40OTMyLDEyMi44MDI5Wm0wLTM3LjA5NzJBMTQuNjA0MiwxNC42MDQyLDAsMSwwLDM3LjA5NzIsMTAwLjMxLDE0LjYyMDcsMTQuNjIwNywwLDAsMCwyMi40OTMyLDg1LjcwNTdaIi8+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo='",
- "body": "\nThe purpose of this section is to describe how to authenticate when making API calls using the Bitbucket REST API.\n\n-----\n\n* [Basic auth](#basic-auth)\n* [Access Tokens](#access-tokens)\n * [Repository Access Tokens](#repository-access-tokens)\n * [Project Access Tokens](#project-access-tokens)\n * [Workspace Access Tokens](#workspace-access-tokens)\n* [App passwords](#app-passwords)\n* [OAuth 2.0](#oauth-2-0)\n * [Making requests](#making-requests)\n * [Repository cloning](#repository-cloning)\n * [Refresh tokens](#refresh-tokens)\n* [Bitbucket OAuth 2.0 Scopes](#bitbucket-oauth-2-0-scopes)\n* [Forge App Scopes](#forge-app-scopes)\n\n---\n\n### Basic auth\n\nBasic HTTP Authentication as per [RFC-2617](https://tools.ietf.org/html/rfc2617) (Digest not supported).\nNote that Basic Auth is available only with username and [app password](https://bitbucket.org/account/settings/app-passwords/) as credentials.\n\n### Access Tokens\n\nAccess Tokens are passwords (or tokens) that provide access to a _single_ repository, project or workspace.\nThese tokens can authenticate with Bitbucket APIs for scripting, CI/CD tools, Bitbucket Cloud-connected apps,\nand Bitbucket Cloud integrations.\n\nAccess Tokens are linked to a repository, project, or workspace, not a user account.\nThe level of access provided by the token is set when a repository, or workspace admin creates it,\nby setting permission scopes.\n\nThere are three types of Access Token:\n\n* **Repository Access Tokens** can connect to a single repository, preventing them from accessing any other repositories or workspaces.\n* **Project Access Tokens** can connect to a single project, providing access to any repositories within the project.\n* **Workspace Access Tokens** can connect to a single workspace and have access to any projects and repositories within that workspace.\n\nWhen using Bitbucket APIs with an Access Token, the token will be treated as the \"user\" in the\nBitbucket UI and Bitbucket logs. This includes when using the Access Token to leave a comment on a pull request,\npush a commit, or merge a pull request. The Bitbucket UI and API responses will show the\nRepository/Project/Workspace Access Token as a user. The username shown in the Bitbucket UI is the Access\nToken _name_, and a custom icon is used to differentiate it from a regular user in the UI.\n\n#### Considerations for using Access Tokens\n\n* After creation, an Access Token can't be viewed or modified. The token's name, created date,\nlast accessed date, and scopes are visible on the repository, project, or workspace **Access Tokens** page.\n* Access Tokens can access a limited set of Bitbucket's permission scopes.\n* Provided you set the correct permission scopes, you can use an Access Token to clone (`repository`)\nand push (`repository:write`) code to the token's repository or the repositories the token can access.\n* You can't use an Access Token to log into the Bitbucket website.\n* Access Tokens don't require two-step verification.\n* You can set permission scopes (specific access rights) for each Access Token.\n* You can't use an Access Token to manipulate or query repository, project, or workspace permissions.\n* Access Tokens are not listed in any repository or workspace permission API response.\n* Access Tokens are deactivated when deleting the resource tied to it (a repository, project, or workspace).\nRepository Access Tokens are also revoked when transferring the repository to another workspace.\n* Any content created by the Access Token will persist after the Access Token has been revoked.\n* Access Tokens can interact with branch restriction APIs, but the token can't be configured as a user with merge access when using branch restrictions.\n\nThere are some APIs which are inaccessible for Access Tokens, these are:\n\n* [Add a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-post)\n* [Update a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-key-id-put)\n* [Delete a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-key-id-delete)\n\n#### Repository Access Tokens\n\nFor details on creating, managing, and using Repository Access Tokens, visit\n[Repository Access Tokens](https://support.atlassian.com/bitbucket-cloud/docs/repository-access-tokens/).\n\nThe available scopes for Repository Access Tokens are:\n\n- [`repository`](#repository)\n- [`repository:write`](#repository-write)\n- [`repository:admin`](#repository-admin)\n- [`repository:delete`](#repository-delete)\n- [`pullrequest`](#pullrequest)\n- [`pullrequest:write`](#pullrequest-write)\n- [`webhook`](#webhook)\n- [`pipeline`](#pipeline)\n- [`pipeline:write`](#pipeline-write)\n- [`pipeline:variable`](#pipeline-variable)\n- [`runner`](#runner)\n- [`runner:write`](#runner-write)\n\n#### Project Access Tokens\n\nFor details on creating, managing, and using Project Access Tokens, visit\n[Project Access Tokens](https://support.atlassian.com/bitbucket-cloud/docs/project-access-tokens/).\n\nThe available scopes for Project Access Tokens are:\n\n- [`project`](#project)\n- [`repository`](#repository)\n- [`repository:write`](#repository-write)\n- [`repository:admin`](#repository-admin)\n- [`repository:delete`](#repository-delete)\n- [`pullrequest`](#pullrequest)\n- [`pullrequest:write`](#pullrequest-write)\n- [`webhook`](#webhook)\n- [`pipeline`](#pipeline)\n- [`pipeline:write`](#pipeline-write)\n- [`pipeline:variable`](#pipeline-variable)\n- [`runner`](#runner)\n- [`runner:write`](#runner-write)\n\n#### Workspace Access Tokens\n\nFor details on creating, managing, and using Workspace Access Tokens, visit\n[Workspace Access Tokens](https://support.atlassian.com/bitbucket-cloud/docs/workspace-access-tokens/).\n\nThe available scopes for Workspace Access Tokens are:\n\n- [`project`](#project)\n- [`project:admin`](#project-admin)\n- [`repository`](#repository)\n- [`repository:write`](#repository-write)\n- [`repository:admin`](#repository-admin)\n- [`repository:delete`](#repository-delete)\n- [`pullrequest`](#pullrequest)\n- [`pullrequest:write`](#pullrequest-write)\n- [`webhook`](#webhook)\n- [`account`](#account)\n- [`pipeline`](#pipeline)\n- [`pipeline:write`](#pipeline-write)\n- [`pipeline:variable`](#pipeline-variable)\n- [`runner`](#runner)\n- [`runner:write`](#runner-write)\n\n### App passwords\n\nApp passwords allow users to make API calls to their Bitbucket account through apps such as Sourcetree.\n\nSome important points about app passwords:\n\n* You cannot view an app password or adjust permissions after you create the app password. Because app passwords are encrypted on our database and cannot be viewed by anyone. They are essentially designed to be disposable. If you need to change the scopes or lost the password just create a new one.\n* You cannot use them to log into your Bitbucket account.\n* You cannot use app passwords to manage team actions.\n\n App passwords are tied to an individual account's credentials and should not be shared. If you're sharing your app password you're essentially giving direct, authenticated, access to everything that password has been scoped to do with the Bitbucket API's.\n\n* You can use them for API call authentication, even if you don't have two-step verification enabled.\n* You can set permission scopes (specific access rights) for each app password.\n\nFor details on creating, managing, and using App passwords, visit\n[App passwords](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/).\n\n### OAuth 2.0\n\nOur OAuth 2 implementation is merged in with our existing OAuth 1 in\nsuch a way that existing OAuth 1 consumers automatically become\nvalid OAuth 2 clients. The only thing you need to do is edit your\nexisting consumer and configure a callback URL.\n\nOnce that is in place, you'll have the following 2 URLs:\n\n https://bitbucket.org/site/oauth2/authorize\n https://bitbucket.org/site/oauth2/access_token\n\nFor obtaining access/bearer tokens, we support three of RFC-6749's grant\nflows, plus a custom Bitbucket flow for exchanging JWT tokens for access tokens.\nNote that Resource Owner Password Credentials Grant (4.3) is no longer supported.\n\n\n#### 1. Authorization Code Grant (4.1)\n\nThe full-blown 3-LO flow. Request authorization from the end user by\nsending their browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=code\n\nThe callback includes the `?code={}` query parameter that you can swap\nfor an access token:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=authorization_code -d code={code}\n\n\n#### 2. Implicit Grant (4.2)\n\nThis flow is useful for browser-based add-ons that operate without server-side backends.\n\nRequest the end user for authorization by directing the browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=token\n\nThat will redirect to your preconfigured callback URL with a fragment\ncontaining the access token\n(`#access_token={token}&token_type=bearer`) where your page's js can\npull it out of the URL.\n\n\n#### 3. Client Credentials Grant (4.4)\n\nSomewhat like our existing \"2-LO\" flow for OAuth 1. Obtain an access\ntoken that represents not an end user, but the owner of the\nclient/consumer:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=client_credentials\n\n\n#### 4. Bitbucket Cloud JWT Grant (urn:bitbucket:oauth2:jwt)\n\nIf your Atlassian Connect add-on uses JWT authentication, you can swap a\nJWT for an OAuth access token. The resulting access token represents the\naccount for which the add-on is installed.\n\nMake sure you send the JWT token in the Authorization request header\nusing the \"JWT\" scheme (case sensitive). Note that this custom scheme\nmakes this different from HTTP Basic Auth (and so you cannot use \"curl\n-u\").\n\n $ curl -X POST -H \"Authorization: JWT {jwt_token}\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=urn:bitbucket:oauth2:jwt\n\n\n#### Making Requests\n\nOnce you have an access token, as per RFC-6750, you can use it in a request in any of\nthe following ways (in decreasing order of desirability):\n\n1. Send it in a request header: `Authorization: Bearer {access_token}`\n2. Include it in a (application/x-www-form-urlencoded) POST body as `access_token={access_token}`\n3. Put it in the query string of a non-POST: `?access_token={access_token}`\n\n\n#### Repository Cloning\n\nSince add-ons will not be able to upload their own SSH keys to clone\nwith, access tokens can be used as Basic HTTP Auth credentials to\nclone securely over HTTPS. This is much like GitHub, yet slightly\ndifferent:\n\n $ git clone https://x-token-auth:{access_token}@bitbucket.org/user/repo.git\n\nThe literal string `x-token-auth` as a substitute for username is\nrequired (note the difference with GitHub where the actual token is in\nthe username field).\n\n\n#### Refresh Tokens\n\nOur access tokens expire in one hour. When this happens you'll get 401\nresponses.\n\nMost access tokens grant responses (Implicit and JWT excluded). Therefore, you should include a\nrefresh token that can then be used to generate a new access token,\nwithout the need for end user participation:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=refresh_token -d refresh_token={refresh_token}\n\n\n### Bitbucket OAuth 2.0 scopes\n\nBitbucket's API applies a number of privilege scopes to endpoints. In order to access an endpoint, a request will need to have the necessary scopes.\n\nOAuth 2.0 Scopes are applicable for OAuth 2, Access Tokens, and App passwords auth mechanisms as well as Bitbucket Connect apps.\n\nScopes are declared in the descriptor as a list of strings, with each string being the name of a unique scope.\n\nA descriptor lacking the `scopes` element is implicitly assumed to require all scopes and as a result, Bitbucket will require end users authorizing/installing the add-on\nto explicitly accept all scopes.\n\nOur best practice suggests you add only the scopes your add-on needs, but no more than it needs.\n\nInvalid scope strings will cause the descriptor to be rejected and the installation to fail.\n\nThe available scopes are:\n\n- [project](#project)\n- [project:write](#project-write)\n- [project:admin](#project-admin)\n- [repository](#repository)\n- [repository:write](#repository-write)\n- [repository:admin](#repository-admin)\n- [repository:delete](#repository-delete)\n- [pullrequest](#pullrequest)\n- [pullrequest:write](#pullrequest-write)\n- [issue](#issue)\n- [issue:write](#issue-write)\n- [wiki](#wiki)\n- [webhook](#webhook)\n- [snippet](#snippet)\n- [snippet:write](#snippet-write)\n- [email](#email)\n- [account](#account)\n- [account:write](#account-write)\n- [pipeline](#pipeline)\n- [pipeline:write](#pipeline-write)\n- [pipeline:variable](#pipeline-variable)\n- [runner](#runner)\n- [runner:write](#runner-write)\n\n#### project\n\nProvides access to view the project or projects.\nThis scope implies the [`repository`](#repository) scope, giving read access to all the repositories in a project or projects.\n\n#### project:write\n\nThis scope is deprecated, and has been made obsolete by `project:admin`. Please see the deprecation notice [here](/cloud/bitbucket/deprecation-notice-project-write-scope).\n\n#### project:admin\n\nProvides admin access to a project or projects. No distinction is made between public and private projects. This scope doesn't implicitly grant the [`project`](#project) scope or the [`repository:write`](#repository-write) scope on any repositories under the project. It gives access to the admin features of a project only, not direct access to its repositories' contents.\n\n* ability to create the project\n* ability to update the project\n* ability to delete the project\n\n#### repository\n\nProvides read access to a repository or repositories.\nNote that this scope does not give access to a repository's pull requests.\n\n* access to the repo's source code\n* clone over HTTPS\n* access the file browsing API\n* download zip archives of the repo's contents\n* the ability to view and use the issue tracker on any repo (created issues, comment, vote, etc)\n* the ability to view and use the wiki on any repo (create/edit pages)\n\n#### repository:write\n\nProvides write (not admin) access to a repository or repositories. No distinction is made between public and private repositories. This scope implicitly grants the [`repository`](#repository) scope, which does not need to be requested separately.\nThis scope alone does not give access to the pull requests API.\n\n* push access over HTTPS\n* fork repos\n\n#### repository:admin\n\nProvides admin access to a repository or repositories. No distinction is made between public and private repositories. This scope doesn't implicitly grant the [`repository`](#repository) or the [`repository:write`](#repository-write) scopes. It gives access to the admin features of a repo only, not direct access to its contents. This scope can be used or misused to grant read access to other users, who can then clone the repo, but users that need to read and write source code would also request explicit read or write.\nThis scope comes with access to the following functionality:\n\n* View and manipulate committer mappings\n* List and edit deploy keys\n* Ability to delete the repo\n* View and edit repo permissions\n* View and edit branch permissions\n* Import and export the issue tracker\n* Enable and disable the issue tracker\n* List and edit issue tracker version, milestones and components\n* Enable and disable the wiki\n* List and edit default reviewers\n* List and edit repo links (Jira/Bamboo/Custom)\n* List and edit the repository webhooks\n* Initiate a repo ownership transfer\n\n#### repository:delete\n\nProvides access to delete a repository or repositories.\n\n#### pullrequest\n\nProvides read access to pull requests.\nThis scope implies the [`repository`](#repository) scope, giving read access to the pull request's destination repository.\n\n* see and list pull requests\n* create and resolve tasks\n* comment on pull requests\n\n#### pullrequest:write\n\nImplicitly grants the [`pullrequest`](#pullrequest) scope and adds the ability to create, merge and decline pull requests.\nThis scope also implicitly grants the [`repository:write`](#repository-write) scope, giving write access to the pull request's destination repository. This is necessary to allow merging.\n\n* merge pull requests\n* decline pull requests\n* create pull requests\n* approve pull requests\n\n#### issue\n\nAbility to interact with issue trackers the way non-repo members can.\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* view, list and search issues\n* create new issues\n* comment on issues\n* watch issues\n* vote for issues\n\n#### issue:write\n\nThis scope implicitly grants the [`issue`](#issue) scope and adds the ability to transition and delete issues.\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* transition issues\n* delete issues\n\n#### wiki\n\nProvides access to wikis. This scope provides both read and write access (wikis are always editable by anyone with access to them).\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* view wikis\n* create pages\n* edit pages\n* push to wikis\n* clone wikis\n\n#### webhook\n\nGives access to webhooks. This scope is required for any webhook-related operation.\n\nThis scope gives read access to existing webhook subscriptions on all\nresources the authorization mechanism can access, without needing further scopes.\nFor example:\n\n- A client can list all existing webhook subscriptions on a repository. The [`repository`](#repository) scope is not required.\n- Existing webhook subscriptions for the issue tracker on a repo can be retrieved without the [`issue`](#issue) scope. All that is required is the `webhook` scope.\n\nTo create webhooks, the client will need read access to the resource. Such as: for [`issue:created`](#issue-created), the client will need to\nhave both the `webhook` and the [`issue`](#issue) scope.\n\n* list webhook subscriptions on any accessible repository, user, team, or snippet\n* create/update/delete webhook subscriptions.\n\n#### snippet\n\nProvides read access to snippets.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\n\n* view any snippet\n* create snippet comments\n\n#### snippet:write\n\nProvides write access to snippets.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\nThis scope implicitly grants the [`snippet`](#snippet) scope which does not need to be requested separately.\n\n* create snippets\n* edit snippets\n* delete snippets\n\n#### email\n\nAbility to see the user's primary email address. This should make it easier to use Bitbucket Cloud as a login provider for apps or external applications.\n\n#### account\n\nWhen used for:\n* **user-related APIs** — Gives read-only access to the user's account information.\nNote that this doesn't include any ability to change any of the data. This scope allows you to view the user's:\n * email addresses\n * language\n * location\n * website\n * full name\n * SSH keys\n * user groups\n* **workspace-related APIs** — Grants access to view the workspace's:\n * users\n * user permissions\n * projects\n\n#### account:write\n\nAbility to change properties on the user's account.\n\n* delete the authorizing user's account\n* manage the user's groups\n* change a user's email addresses\n* change username, display name and avatar\n\n#### pipeline\n\nGives read-only access to pipelines, steps, deployment environments and variables.\n\n#### pipeline:write\n\nGives write access to pipelines. This scope allows a user to:\n* Stop pipelines\n* Rerun failed pipelines\n* Resume halted pipelines\n* Trigger manual pipelines.\n\nThis scope is not needed to trigger a build using a push. Performing a `git push` (or equivalent actions) will trigger the build. The token doing the push only needs the [`repository:write`](#repository-write) scope.\n\nThis doesn't give write access to create variables.\n\n#### pipeline:variable\n\nGives write access to create variables in pipelines at the various levels:\n* Workspace\n* Repository\n* Deployment\n\n#### runner\n\nGives read-only access to pipelines runners setup against a workspace or repository.\n\n#### runner:write\n\nGives write access to create/edit/disable/delete pipelines runners setup against a workspace or repository.\n### Forge app scopes\n\nIn order for a Forge app integration to access Bitbucket API endpoints, it needs to include certain privilege scopes in the app manifest. These are different from Bitbucket OAuth 2.0 scopes.\n\nUnlike OAuth 2.0 scopes, Forge app scopes do not implicitly grant other scopes, for example, `write:repository:bitbucket` does not implicitly grant `read:repository:bitbucket`.\n\nOnly a subset of Bitbucket API endpoints are currently available for Forge app integrations. These will be labeled with Forge app scopes.\n\nOur best practice suggests you only add the scopes your app needs, but no more than it needs.\n\nThe available scopes are:\n\n- [`read:repository:bitbucket`](#read-repository-bitbucket)\n- [`write:repository:bitbucket`](#write-repository-bitbucket)\n- [`admin:repository:bitbucket`](#admin-repository-bitbucket)\n- [`delete:repository:bitbucket`](#delete-repository-bitbucket)\n- [`read:pullrequest:bitbucket`](#read-pullrequest-bitbucket)\n- [`write:pullrequest:bitbucket`](#write-pullrequest-bitbucket)\n- [`read:project:bitbucket`](#read-project-bitbucket)\n- [`admin:project:bitbucket`](#admin-project-bitbucket)\n- [`read:workspace:bitbucket`](#read-workspace-bitbucket)\n- [`read:user:bitbucket`](#read-user-bitbucket)\n- [`read:pipeline:bitbucket`](#read-pipeline-bitbucket)\n- [`write:pipeline:bitbucket`](#write-pipeline-bitbucket)\n- [`admin:pipeline:bitbucket`](#admin-pipeline-bitbucket)\n- [`read:runner:bitbucket`](#read-runner-bitbucket)\n- [`write:runner:bitbucket`](#write-runner-bitbucket)\n\n#### read:repository:bitbucket\n\nAllows viewing of repository data. Note that this scope does not give access to a repository's pull requests.\n* access to the repository's source code\n* access the file browsing API\n* access to certain repository configurations such as branching model, default reviewers, etc.\n\n#### write:repository:bitbucket\n\nAllows modification of repository data. No distinction is made between public and private repositories. This scope does not imply the `read:repository:bitbucket` scope, so you need to request that separately if required. This scope alone does not give access to the pull request API.\n* update/delete source, branches, tags, etc.\n* fork repositories\n\n#### admin:repository:bitbucket\n\nAllows admin activities on repositories. No distinction is made between public and private repositories. This scope does not implicitly grant the `read:repository:bitbucket` or the `write:repository:bitbucket` scopes. It gives access to the admin features of a repository only, not direct access to its contents. This scope does not allow modification of repository permissions. This scope comes with access to the following functionality:\n* create repository\n* view repository permissions\n* view and edit branch restrictions\n* edit branching model settings\n* edit default reviewers\n* view and edit inheritance state for repository settings\n\n#### delete:repository:bitbucket\nAllows deletion of repositories.\n\n#### read:pullrequest:bitbucket\nAllows viewing of pull requests, plus the ability to comment on pull requests.\n\nThis scope does not imply the `read:repository:bitbucket` scope. With this scope, you could retrieve some data specific to the source/destination repositories of a pull request using pull request endpoints, but it does not give access to repository API endpoints.\n\n#### write:pullrequest:bitbucket\nAllows the ability to create, update, approve, decline, and merge pull requests.\n\nThis scope does not imply the `write:repository:bitbucket` scope.\n\n#### read:project:bitbucket\nAllows viewing of project and project permission data.\n\n#### admin:project:bitbucket\nAllows the ability to create, update, and delete project. No distinction is made between public and private projects.\n\nThis scope does not implicitly grant the `read:project:bitbucket` scope or any repository scopes. It gives access to the admin features of a project only, not direct access to its repositories' contents.\n\n#### read:workspace:bitbucket\nAllows viewing of workspace and workspace permission data.\n\n#### read:user:bitbucket\nAllows viewing of user data. This scope is typically required for permission related endpoints.\n\n#### read:pipeline:bitbucket\nAllows read access to all pipeline information (pipelines, steps, caches, artifacts, logs, tests, code-insights).\n\n#### write:pipeline:bitbucket\nAllows running pipelines (i.e., start/stop/create pipeline) and uploading tests/code-insights.\n\nThis scope does not imply the `read:pipeline:bitbucket` scope.\n\n#### admin:pipeline:bitbucket\nAllows admin activities, such as creating pipeline variables.\n\nThis scope does not implicitly grant the `read:pipeline:bitbucket` or the `write:pipeline:bitbucket` scopes.\n\n#### read:runner:bitbucket\nAllows viewing of runners information.\n\n#### write:runner:bitbucket\nAllows runners management.\n\nThis scope does not imply the `read:runners:bitbucket` scope.\n"
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTcuNjQ3MyAxODYuODEzOCI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5jbHMtMSB7CiAgICAgICAgaXNvbGF0aW9uOiBpc29sYXRlOwogICAgICB9CgogICAgICAuY2xzLTIgewogICAgICAgIGZpbGw6ICNkZTM1MGI7CiAgICAgIH0KCiAgICAgIC5jbHMtMyB7CiAgICAgICAgZmlsbDogI2ZmNTYzMDsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZGZlMWU1OwogICAgICAgIG1peC1ibGVuZC1tb2RlOiBtdWx0aXBseTsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjZmFmYmZjOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIGZpbGw6ICNlYmVjZjA7CiAgICAgIH0KCiAgICAgIC5jbHMtNyB7CiAgICAgICAgZmlsbDogbm9uZTsKICAgICAgICBzdHJva2U6ICMwMDY1ZmY7CiAgICAgICAgc3Ryb2tlLW1pdGVybGltaXQ6IDEwOwogICAgICAgIHN0cm9rZS13aWR0aDogMnB4OwogICAgICB9CgogICAgICAuY2xzLTggewogICAgICAgIGZpbGw6ICM1ZTZjODQ7CiAgICAgIH0KCiAgICAgIC5jbHMtOSB7CiAgICAgICAgZmlsbDogIzI1Mzg1ODsKICAgICAgfQoKICAgICAgLmNscy0xMCB7CiAgICAgICAgZmlsbDogIzI2ODRmZjsKICAgICAgfQoKICAgICAgLmNscy0xMSB7CiAgICAgICAgZmlsbDogIzAwNjVmZjsKICAgICAgfQogICAgPC9zdHlsZT4KICA8L2RlZnM+CiAgPHRpdGxlPlNlY3VyaXR5IHdpdGggS2V5PC90aXRsZT4KICA8ZyBjbGFzcz0iY2xzLTEiPgogICAgPGcgaWQ9IkxheWVyXzIiIGRhdGEtbmFtZT0iTGF5ZXIgMiI+CiAgICAgIDxnIGlkPSJPYmplY3RzIj4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik00Mi4wNjcyLDBoLjYxMTRhOCw4LDAsMCwxLDgsOFYyMy4yMzM4YTAsMCwwLDAsMSwwLDBIMzQuMDY3MmEwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDQyLjA2NzIsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xMDguMjIsMGguNjExNGE4LDgsMCwwLDEsOCw4VjIzLjIzMzhhMCwwLDAsMCwxLDAsMEgxMDAuMjJhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSwxMDguMjIsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xNzQuMzcyMiwwaC42MTE0YTgsOCwwLDAsMSw4LDhWMjMuMjMzOGEwLDAsMCwwLDEsMCwwSDE2Ni4zNzIyYTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMTc0LjM3MjIsMFoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjM0LjA2NzIiIHk9IjIzLjIzMzgiIHdpZHRoPSIxNjMuNTgiIGhlaWdodD0iMTYzLjU4Ii8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNDIuMDY3MiwwSDU5LjI5YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDM0LjA2NzJhMCwwLDAsMCwxLDAsMFY4YTgsOCwwLDAsMSw4LThaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTA3LjI0NTgsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDk5LjI0NThhMCwwLDAsMCwxLDAsMFY4YTgsOCwwLDAsMSw4LThaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTcyLjQyNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDE2NC40MjQ0YTAsMCwwLDAsMSwwLDBWOGE4LDgsMCwwLDEsOC04WiIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMyIgeD0iMTcuNDU1OCIgeT0iMjMuMjMzOCIgd2lkdGg9IjE2My41OCIgaGVpZ2h0PSIxNjMuNTgiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0yNS40NTU4LDBINDIuNjc4NmE4LDgsMCwwLDEsOCw4VjIzLjIyMjhhMCwwLDAsMCwxLDAsMEgxNy40NTU4YTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMjUuNDU1OCwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTkwLjYzNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDgyLjYzNDRhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSw5MC42MzQ0LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTU1LjgxMywwaDE3LjIyMjhhOCw4LDAsMCwxLDgsOFYyMy4yMjI4YTAsMCwwLDAsMSwwLDBIMTQ3LjgxM2EwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDE1NS44MTMsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0yNS40NTU4LDBINDIuNjc4NmE4LDgsMCwwLDEsOCw4VjIzLjIyMjhhMCwwLDAsMCwxLDAsMEgxNy40NTU4YTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMjUuNDU1OCwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTkwLjYzNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDgyLjYzNDRhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSw5MC42MzQ0LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTU1LjgxMywwaDE3LjIyMjhhOCw4LDAsMCwxLDgsOFYyMy4yMjI4YTAsMCwwLDAsMSwwLDBIMTQ3LjgxM2EwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDE1NS44MTMsMFoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjM1Ljc1OTYiIHk9IjU2LjgwNjUiIHdpZHRoPSIzMy4yMjI4IiBoZWlnaHQ9IjE1LjYwMzgiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjEzMS4yMDE2IiB5PSIxMzYuOTYxNSIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTU3LjM3MDksNzEuNjAzNmg3MC43NWE5LDksMCwwLDEsOSw5djM1LjM3NDlhNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLTQ0LjM3NDgsNDQuMzc0OGgwYTQ0LjM3NDgsNDQuMzc0OCwwLDAsMS00NC4zNzQ4LTQ0LjM3NDhWODAuNjAzNkE5LDksMCwwLDEsNTcuMzcwOSw3MS42MDM2WiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNSIgZD0iTTY2LjM3MSw2Ni42NjE3aDcwLjc1YTksOSwwLDAsMSw5LDl2MzUuMzc0OWE0NC4zNzQ4LDQ0LjM3NDgsMCwwLDEtNDQuMzc0OCw0NC4zNzQ4aDBBNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLDU3LjM3MSwxMTEuMDM2NlY3NS42NjE3YTksOSwwLDAsMSw5LTlaIi8+CiAgICAgICAgPHBhdGggaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIGQ9Ik02MS4zNzEsNjYuNjYxN2g3MC43NWE5LDksMCwwLDEsOSw5djM1LjM3NDlhNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLTQ0LjM3NDgsNDQuMzc0OGgwQTQ0LjM3NDgsNDQuMzc0OCwwLDAsMSw1Mi4zNzEsMTExLjAzNjZWNzUuNjYxN0E5LDksMCwwLDEsNjEuMzcxLDY2LjY2MTdaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy03IiBkPSJNOTYuNzQ1OSwxNDcuNzQ0MWEzNi43NDg3LDM2Ljc0ODcsMCwwLDEtMzYuNzA3NC0zNi43MDc0Vjc4LjA1ODRhMy43MzMzLDMuNzMzMywwLDAsMSwzLjcyOS0zLjcyOWg2NS45NTYzYTMuNzMzMywzLjczMzMsMCwwLDEsMy43MjksMy43Mjl2MzIuOTc4NEEzNi43NDg2LDM2Ljc0ODYsMCwwLDEsOTYuNzQ1OSwxNDcuNzQ0MVoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMDAuNjg5MywxNjMuMzE2N1YxMTEuMDk3M2EzLjk0NDMsMy45NDQzLDAsMCwwLTcuODg4NywwdjUyLjIyYTIyLjUyNTIsMjIuNTI1MiwwLDAsMC0xOC41NDc5LDIyLjE0YzAsLjQ1Ni4wMTc4LjkwNzguMDQ0NywxLjM1NzFIODIuMjFjLS4wNDE0LS40NDc0LS4wNjg4LS44OTktLjA2ODgtMS4zNTcxYTE0LjYyLDE0LjYyLDAsMCwxLDE0LjU5NzQtMTQuNjA0MWwuMDA2MS4wMDA2LjAwNjgtLjAwMDdBMTQuNjIxMSwxNC42MjExLDAsMCwxLDExMS4zNSwxODUuNDU2NmMwLC40NTgxLS4wMjczLjkxLS4wNjg4LDEuMzU3MWg3LjkxMjhjLjAyNjktLjQ0OTMuMDQ0Ny0uOTAxMS4wNDQ3LTEuMzU3MUEyMi41MjU5LDIyLjUyNTksMCwwLDAsMTAwLjY4OTMsMTYzLjMxNjdaIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxNy40NTU4IiB5PSIzNi40NzAyIiB3aWR0aD0iMzMuMjIyOCIgaGVpZ2h0PSIxNS42MDM4Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxNy40NTU4IiB5PSIxNTguMTIxNyIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMTQ3LjgxMyIgeT0iMzYuNDcwMiIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMTUwLjA2NDMiIHk9IjE1Ny41NTEzIiB3aWR0aD0iMzMuMjIyOCIgaGVpZ2h0PSIxNS42MDM4Ii8+CiAgICAgICAgPHBhdGggaWQ9Il9QYXRoXyIgZGF0YS1uYW1lPSImbHQ7UGF0aCZndDsiIGNsYXNzPSJjbHMtOCIgZD0iTTEwNy41MjU0LDEwMS4wMDI3YTExLjc3OTQsMTEuNzc5NCwwLDEsMC0xOS44Niw4LjU1NDhBNC4wNDE3LDQuMDQxNywwLDAsMSw4OC44NSwxMTMuNjJsLTIuMTA0LDcuMjY4MWEzLDMsMCwwLDAsMi44ODE3LDMuODM0MmgxMi4yMzcxYTMsMywwLDAsMCwyLjg4MTctMy44MzQybC0yLjA5NTktNy4yNGE0LjA3NDMsNC4wNzQzLDAsMCwxLDEuMTgwOC00LjA5NDVBMTEuNzE3MiwxMS43MTcyLDAsMCwwLDEwNy41MjU0LDEwMS4wMDI3WiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTEwNC43NDYxLDEyMC44ODc3bC0yLjA5NTktNy4yNGE0LjA3NDQsNC4wNzQ0LDAsMCwxLDEuMTgwOC00LjA5NDUsMTEuNzYyOSwxMS43NjI5LDAsMCwwLTUuMDYtMTkuOTMxMywxMS45MSwxMS45MSwwLDAsMC04Ljc5OCwxMC45OTQ5LDExLjcxODUsMTEuNzE4NSwwLDAsMCwzLjY5MjksOC45NDFBNC4wNDE2LDQuMDQxNiwwLDAsMSw5NC44NSwxMTMuNjJsLTMuMjE0LDExLjEwMjNoMTAuMjI4OEEzLDMsMCwwLDAsMTA0Ljc0NjEsMTIwLjg4NzdaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTgxLjc5NzUsMTAwLjMxYTMuOTQzOSwzLjk0MzksMCwwLDAtMy45NDQzLTMuOTQ0M0g0MS4wNDE3YTMuOTQ0MywzLjk0NDMsMCwwLDAsMCw3Ljg4ODdINzcuODUzMkEzLjk0MzksMy45NDM5LDAsMCwwLDgxLjc5NzUsMTAwLjMxWiIvPgogICAgICAgIDxwYXRoIGlkPSJfUGF0aF8yIiBkYXRhLW5hbWU9IiZsdDtQYXRoJmd0OyIgY2xhc3M9ImNscy0xMSIgZD0iTTQxLjA0MTYsMTA0LjI1MzlIOTYuODUzMmEzLjk0NDMsMy45NDQzLDAsMCwwLDAtNy44ODg3SDQxLjA0MTZhMy45NDQzLDMuOTQ0MywwLDAsMCwwLDcuODg4N1oiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTEwIiBkPSJNODEuNzk3NSwxMDAuMzFhMy45NDM5LDMuOTQzOSwwLDAsMC0zLjk0NDMtMy45NDQzSDQxLjA0MTdhMy45NDQzLDMuOTQ0MywwLDAsMCwwLDcuODg4N0g3Ny44NTMyQTMuOTQzOSwzLjk0MzksMCwwLDAsODEuNzk3NSwxMDAuMzFaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTIyLjQ5MzIsMTIyLjgwMjlBMjIuNDkyOSwyMi40OTI5LDAsMSwxLDQ0Ljk4NTgsMTAwLjMxLDIyLjUxODUsMjIuNTE4NSwwLDAsMSwyMi40OTMyLDEyMi44MDI5Wm0wLTM3LjA5NzJBMTQuNjA0MiwxNC42MDQyLDAsMSwwLDM3LjA5NzIsMTAwLjMxLDE0LjYyMDcsMTQuNjIwNywwLDAsMCwyMi40OTMyLDg1LjcwNTdaIi8+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo=",
+ "body": "\nThe purpose of this section is to describe how to authenticate when making API calls using the Bitbucket REST API.\n\n-----\n\n* [Basic auth](#basic-auth)\n* [Access Tokens](#access-tokens)\n * [Repository Access Tokens](#repository-access-tokens)\n * [Project Access Tokens](#project-access-tokens)\n * [Workspace Access Tokens](#workspace-access-tokens)\n* [App passwords](#app-passwords)\n* [OAuth 2.0](#oauth-2-0)\n * [Making requests](#making-requests)\n * [Repository cloning](#repository-cloning)\n * [Refresh tokens](#refresh-tokens)\n* [Bitbucket OAuth 2.0 Scopes](#bitbucket-oauth-2-0-scopes)\n* [Forge App Scopes](#forge-app-scopes)\n\n---\n\n### Basic auth\n\nBasic HTTP Authentication as per [RFC-2617](https://tools.ietf.org/html/rfc2617) (Digest not supported).\nNote that Basic Auth is available only with username and [app password](https://bitbucket.org/account/settings/app-passwords/) as credentials.\n\n### Access Tokens\n\nAccess Tokens are passwords (or tokens) that provide access to a _single_ repository, project or workspace.\nThese tokens can authenticate with Bitbucket APIs for scripting, CI/CD tools, Bitbucket Cloud-connected apps,\nand Bitbucket Cloud integrations.\n\nAccess Tokens are linked to a repository, project, or workspace, not a user account.\nThe level of access provided by the token is set when a repository, or workspace admin creates it,\nby setting permission scopes.\n\nThere are three types of Access Token:\n\n* **Repository Access Tokens** can connect to a single repository, preventing them from accessing any other repositories or workspaces.\n* **Project Access Tokens** can connect to a single project, providing access to any repositories within the project.\n* **Workspace Access Tokens** can connect to a single workspace and have access to any projects and repositories within that workspace.\n\nWhen using Bitbucket APIs with an Access Token, the token will be treated as the \"user\" in the\nBitbucket UI and Bitbucket logs. This includes when using the Access Token to leave a comment on a pull request,\npush a commit, or merge a pull request. The Bitbucket UI and API responses will show the\nRepository/Project/Workspace Access Token as a user. The username shown in the Bitbucket UI is the Access\nToken _name_, and a custom icon is used to differentiate it from a regular user in the UI.\n\n#### Considerations for using Access Tokens\n\n* After creation, an Access Token can't be viewed or modified. The token's name, created date,\nlast accessed date, and scopes are visible on the repository, project, or workspace **Access Tokens** page.\n* Access Tokens can access a limited set of Bitbucket's permission scopes.\n* Provided you set the correct permission scopes, you can use an Access Token to clone (`repository`)\nand push (`repository:write`) code to the token's repository or the repositories the token can access.\n* You can't use an Access Token to log into the Bitbucket website.\n* Access Tokens don't require two-step verification.\n* You can set permission scopes (specific access rights) for each Access Token.\n* You can't use an Access Token to manipulate or query repository, project, or workspace permissions.\n* Access Tokens are not listed in any repository or workspace permission API response.\n* Access Tokens are deactivated when deleting the resource tied to it (a repository, project, or workspace).\nRepository Access Tokens are also revoked when transferring the repository to another workspace.\n* Any content created by the Access Token will persist after the Access Token has been revoked.\n* Access Tokens can interact with branch restriction APIs, but the token can't be configured as a user with merge access when using branch restrictions.\n\nThere are some APIs which are inaccessible for Access Tokens, these are:\n\n* [Add a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-post)\n* [Update a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-key-id-put)\n* [Delete a repository deploy key](/cloud/bitbucket/rest/api-group-deployments/#api-repositories-workspace-repo-slug-deploy-keys-key-id-delete)\n\n#### Repository Access Tokens\n\nFor details on creating, managing, and using Repository Access Tokens, visit\n[Repository Access Tokens](https://support.atlassian.com/bitbucket-cloud/docs/repository-access-tokens/).\n\nThe available scopes for Repository Access Tokens are:\n\n- [`repository`](#repository)\n- [`repository:write`](#repository-write)\n- [`repository:admin`](#repository-admin)\n- [`repository:delete`](#repository-delete)\n- [`pullrequest`](#pullrequest)\n- [`pullrequest:write`](#pullrequest-write)\n- [`webhook`](#webhook)\n- [`pipeline`](#pipeline)\n- [`pipeline:write`](#pipeline-write)\n- [`pipeline:variable`](#pipeline-variable)\n- [`runner`](#runner)\n- [`runner:write`](#runner-write)\n\n#### Project Access Tokens\n\nFor details on creating, managing, and using Project Access Tokens, visit\n[Project Access Tokens](https://support.atlassian.com/bitbucket-cloud/docs/project-access-tokens/).\n\nThe available scopes for Project Access Tokens are:\n\n- [`project`](#project)\n- [`repository`](#repository)\n- [`repository:write`](#repository-write)\n- [`repository:admin`](#repository-admin)\n- [`repository:delete`](#repository-delete)\n- [`pullrequest`](#pullrequest)\n- [`pullrequest:write`](#pullrequest-write)\n- [`webhook`](#webhook)\n- [`pipeline`](#pipeline)\n- [`pipeline:write`](#pipeline-write)\n- [`pipeline:variable`](#pipeline-variable)\n- [`runner`](#runner)\n- [`runner:write`](#runner-write)\n\n#### Workspace Access Tokens\n\nFor details on creating, managing, and using Workspace Access Tokens, visit\n[Workspace Access Tokens](https://support.atlassian.com/bitbucket-cloud/docs/workspace-access-tokens/).\n\nThe available scopes for Workspace Access Tokens are:\n\n- [`project`](#project)\n- [`project:admin`](#project-admin)\n- [`repository`](#repository)\n- [`repository:write`](#repository-write)\n- [`repository:admin`](#repository-admin)\n- [`repository:delete`](#repository-delete)\n- [`pullrequest`](#pullrequest)\n- [`pullrequest:write`](#pullrequest-write)\n- [`webhook`](#webhook)\n- [`account`](#account)\n- [`pipeline`](#pipeline)\n- [`pipeline:write`](#pipeline-write)\n- [`pipeline:variable`](#pipeline-variable)\n- [`runner`](#runner)\n- [`runner:write`](#runner-write)\n\n### App passwords\n\nApp passwords allow users to make API calls to their Bitbucket account through apps such as Sourcetree.\n\nSome important points about app passwords:\n\n* You cannot view an app password or adjust permissions after you create the app password. Because app passwords are encrypted on our database and cannot be viewed by anyone. They are essentially designed to be disposable. If you need to change the scopes or lost the password just create a new one.\n* You cannot use them to log into your Bitbucket account.\n* You cannot use app passwords to manage team actions.\n\n App passwords are tied to an individual account's credentials and should not be shared. If you're sharing your app password you're essentially giving direct, authenticated, access to everything that password has been scoped to do with the Bitbucket API's.\n\n* You can use them for API call authentication, even if you don't have two-step verification enabled.\n* You can set permission scopes (specific access rights) for each app password.\n\nFor details on creating, managing, and using App passwords, visit\n[App passwords](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/).\n\n### OAuth 2.0\n\nOur OAuth 2 implementation is merged in with our existing OAuth 1 in\nsuch a way that existing OAuth 1 consumers automatically become\nvalid OAuth 2 clients. The only thing you need to do is edit your\nexisting consumer and configure a callback URL.\n\nOnce that is in place, you'll have the following 2 URLs:\n\n https://bitbucket.org/site/oauth2/authorize\n https://bitbucket.org/site/oauth2/access_token\n\nFor obtaining access/bearer tokens, we support three of RFC-6749's grant\nflows, plus a custom Bitbucket flow for exchanging JWT tokens for access tokens.\nNote that Resource Owner Password Credentials Grant (4.3) is no longer supported.\n\n\n#### 1. Authorization Code Grant (4.1)\n\nThe full-blown 3-LO flow. Request authorization from the end user by\nsending their browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=code\n\nThe callback includes the `?code={}` query parameter that you can swap\nfor an access token:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=authorization_code -d code={code}\n\n\n#### 2. Implicit Grant (4.2)\n\nThis flow is useful for browser-based add-ons that operate without server-side backends.\n\nRequest the end user for authorization by directing the browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=token\n\nThat will redirect to your preconfigured callback URL with a fragment\ncontaining the access token\n(`#access_token={token}&token_type=bearer`) where your page's js can\npull it out of the URL.\n\n\n#### 3. Client Credentials Grant (4.4)\n\nSomewhat like our existing \"2-LO\" flow for OAuth 1. Obtain an access\ntoken that represents not an end user, but the owner of the\nclient/consumer:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=client_credentials\n\n\n#### 4. Bitbucket Cloud JWT Grant (urn:bitbucket:oauth2:jwt)\n\nIf your Atlassian Connect add-on uses JWT authentication, you can swap a\nJWT for an OAuth access token. The resulting access token represents the\naccount for which the add-on is installed.\n\nMake sure you send the JWT token in the Authorization request header\nusing the \"JWT\" scheme (case sensitive). Note that this custom scheme\nmakes this different from HTTP Basic Auth (and so you cannot use \"curl\n-u\").\n\n $ curl -X POST -H \"Authorization: JWT {jwt_token}\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=urn:bitbucket:oauth2:jwt\n\n\n#### Making Requests\n\nOnce you have an access token, as per RFC-6750, you can use it in a request in any of\nthe following ways (in decreasing order of desirability):\n\n1. Send it in a request header: `Authorization: Bearer {access_token}`\n2. Include it in a (application/x-www-form-urlencoded) POST body as `access_token={access_token}`\n3. Put it in the query string of a non-POST: `?access_token={access_token}`\n\n\n#### Repository Cloning\n\nSince add-ons will not be able to upload their own SSH keys to clone\nwith, access tokens can be used as Basic HTTP Auth credentials to\nclone securely over HTTPS. This is much like GitHub, yet slightly\ndifferent:\n\n $ git clone https://x-token-auth:{access_token}@bitbucket.org/user/repo.git\n\nThe literal string `x-token-auth` as a substitute for username is\nrequired (note the difference with GitHub where the actual token is in\nthe username field).\n\n\n#### Refresh Tokens\n\nOur access tokens expire in one hour. When this happens you'll get 401\nresponses.\n\nMost access tokens grant responses (Implicit and JWT excluded). Therefore, you should include a\nrefresh token that can then be used to generate a new access token,\nwithout the need for end user participation:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=refresh_token -d refresh_token={refresh_token}\n\n\n### Bitbucket OAuth 2.0 scopes\n\nBitbucket's API applies a number of privilege scopes to endpoints. In order to access an endpoint, a request will need to have the necessary scopes.\n\nOAuth 2.0 Scopes are applicable for OAuth 2, Access Tokens, and App passwords auth mechanisms as well as Bitbucket Connect apps.\n\nScopes are declared in the descriptor as a list of strings, with each string being the name of a unique scope.\n\nA descriptor lacking the `scopes` element is implicitly assumed to require all scopes and as a result, Bitbucket will require end users authorizing/installing the add-on\nto explicitly accept all scopes.\n\nOur best practice suggests you add only the scopes your add-on needs, but no more than it needs.\n\nInvalid scope strings will cause the descriptor to be rejected and the installation to fail.\n\nThe available scopes are:\n\n- [project](#project)\n- [project:write](#project-write)\n- [project:admin](#project-admin)\n- [repository](#repository)\n- [repository:write](#repository-write)\n- [repository:admin](#repository-admin)\n- [repository:delete](#repository-delete)\n- [pullrequest](#pullrequest)\n- [pullrequest:write](#pullrequest-write)\n- [issue](#issue)\n- [issue:write](#issue-write)\n- [wiki](#wiki)\n- [webhook](#webhook)\n- [snippet](#snippet)\n- [snippet:write](#snippet-write)\n- [email](#email)\n- [account](#account)\n- [account:write](#account-write)\n- [pipeline](#pipeline)\n- [pipeline:write](#pipeline-write)\n- [pipeline:variable](#pipeline-variable)\n- [runner](#runner)\n- [runner:write](#runner-write)\n\n#### project\n\nProvides access to view the project or projects.\nThis scope implies the [`repository`](#repository) scope, giving read access to all the repositories in a project or projects.\n\n#### project:write\n\nThis scope is deprecated, and has been made obsolete by `project:admin`. Please see the deprecation notice [here](/cloud/bitbucket/deprecation-notice-project-write-scope).\n\n#### project:admin\n\nProvides admin access to a project or projects. No distinction is made between public and private projects. This scope doesn't implicitly grant the [`project`](#project) scope or the [`repository:write`](#repository-write) scope on any repositories under the project. It gives access to the admin features of a project only, not direct access to its repositories' contents.\n\n* ability to create the project\n* ability to update the project\n* ability to delete the project\n\n#### repository\n\nProvides read access to a repository or repositories.\nNote that this scope does not give access to a repository's pull requests.\n\n* access to the repo's source code\n* clone over HTTPS\n* access the file browsing API\n* download zip archives of the repo's contents\n* the ability to view and use the issue tracker on any repo (created issues, comment, vote, etc)\n* the ability to view and use the wiki on any repo (create/edit pages)\n\n#### repository:write\n\nProvides write (not admin) access to a repository or repositories. No distinction is made between public and private repositories. This scope implicitly grants the [`repository`](#repository) scope, which does not need to be requested separately.\nThis scope alone does not give access to the pull requests API.\n\n* push access over HTTPS\n* fork repos\n\n#### repository:admin\n\nProvides admin access to a repository or repositories. No distinction is made between public and private repositories. This scope doesn't implicitly grant the [`repository`](#repository) or the [`repository:write`](#repository-write) scopes. It gives access to the admin features of a repo only, not direct access to its contents. This scope can be used or misused to grant read access to other users, who can then clone the repo, but users that need to read and write source code would also request explicit read or write.\nThis scope comes with access to the following functionality:\n\n* View and manipulate committer mappings\n* List and edit deploy keys\n* Ability to delete the repo\n* View and edit repo permissions\n* View and edit branch permissions\n* Import and export the issue tracker\n* Enable and disable the issue tracker\n* List and edit issue tracker version, milestones and components\n* Enable and disable the wiki\n* List and edit default reviewers\n* List and edit repo links (Jira/Bamboo/Custom)\n* List and edit the repository webhooks\n* Initiate a repo ownership transfer\n\n#### repository:delete\n\nProvides access to delete a repository or repositories.\n\n#### pullrequest\n\nProvides read access to pull requests.\nThis scope implies the [`repository`](#repository) scope, giving read access to the pull request's destination repository.\n\n* see and list pull requests\n* create and resolve tasks\n* comment on pull requests\n\n#### pullrequest:write\n\nImplicitly grants the [`pullrequest`](#pullrequest) scope and adds the ability to create, merge and decline pull requests.\nThis scope also implicitly grants the [`repository:write`](#repository-write) scope, giving write access to the pull request's destination repository. This is necessary to allow merging.\n\n* merge pull requests\n* decline pull requests\n* create pull requests\n* approve pull requests\n\n#### issue\n\nAbility to interact with issue trackers the way non-repo members can.\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* view, list and search issues\n* create new issues\n* comment on issues\n* watch issues\n* vote for issues\n\n#### issue:write\n\nThis scope implicitly grants the [`issue`](#issue) scope and adds the ability to transition and delete issues.\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* transition issues\n* delete issues\n\n#### wiki\n\nProvides access to wikis. This scope provides both read and write access (wikis are always editable by anyone with access to them).\nThis scope doesn't implicitly grant any other scopes and doesn't give implicit access to the repository.\n\n* view wikis\n* create pages\n* edit pages\n* push to wikis\n* clone wikis\n\n#### webhook\n\nGives access to webhooks. This scope is required for any webhook-related operation.\n\nThis scope gives read access to existing webhook subscriptions on all\nresources the authorization mechanism can access, without needing further scopes.\nFor example:\n\n- A client can list all existing webhook subscriptions on a repository. The [`repository`](#repository) scope is not required.\n- Existing webhook subscriptions for the issue tracker on a repo can be retrieved without the [`issue`](#issue) scope. All that is required is the `webhook` scope.\n\nTo create webhooks, the client will need read access to the resource. Such as: for [`issue:created`](#issue-created), the client will need to\nhave both the `webhook` and the [`issue`](#issue) scope.\n\n* list webhook subscriptions on any accessible repository, user, team, or snippet\n* create/update/delete webhook subscriptions.\n\n#### snippet\n\nProvides read access to snippets.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\n\n* view any snippet\n* create snippet comments\n\n#### snippet:write\n\nProvides write access to snippets.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\nThis scope implicitly grants the [`snippet`](#snippet) scope which does not need to be requested separately.\n\n* create snippets\n* edit snippets\n* delete snippets\n\n#### email\n\nAbility to see the user's primary email address. This should make it easier to use Bitbucket Cloud as a login provider for apps or external applications.\n\n#### account\n\nWhen used for:\n* **user-related APIs** — Gives read-only access to the user's account information.\nNote that this doesn't include any ability to change any of the data. This scope allows you to view the user's:\n * email addresses\n * language\n * location\n * website\n * full name\n * SSH keys\n * user groups\n* **workspace-related APIs** — Grants access to view the workspace's:\n * users\n * user permissions\n * projects\n\n#### account:write\n\nAbility to change properties on the user's account.\n\n* delete the authorizing user's account\n* manage the user's groups\n* change a user's email addresses\n* change username, display name and avatar\n\n#### pipeline\n\nGives read-only access to pipelines, steps, deployment environments and variables.\n\n#### pipeline:write\n\nGives write access to pipelines. This scope allows a user to:\n* Stop pipelines\n* Rerun failed pipelines\n* Resume halted pipelines\n* Trigger manual pipelines.\n\nThis scope is not needed to trigger a build using a push. Performing a `git push` (or equivalent actions) will trigger the build. The token doing the push only needs the [`repository:write`](#repository-write) scope.\n\nThis doesn't give write access to create variables.\n\n#### pipeline:variable\n\nGives write access to create variables in pipelines at the various levels:\n* Workspace\n* Repository\n* Deployment\n\n#### runner\n\nGives read-only access to pipelines runners setup against a workspace or repository.\n\n#### runner:write\n\nGives write access to create/edit/disable/delete pipelines runners setup against a workspace or repository.\n### Forge app scopes\n\nIn order for a Forge app integration to access Bitbucket API endpoints, it needs to include certain privilege scopes in the app manifest. These are different from Bitbucket OAuth 2.0 scopes.\n\nUnlike OAuth 2.0 scopes, Forge app scopes do not implicitly grant other scopes, for example, `write:repository:bitbucket` does not implicitly grant `read:repository:bitbucket`.\n\nOnly a subset of Bitbucket API endpoints are currently available for Forge app integrations. These will be labeled with Forge app scopes.\n\nOur best practice suggests you only add the scopes your app needs, but no more than it needs.\n\nThe available scopes are:\n\n- [`read:repository:bitbucket`](#read-repository-bitbucket)\n- [`write:repository:bitbucket`](#write-repository-bitbucket)\n- [`admin:repository:bitbucket`](#admin-repository-bitbucket)\n- [`delete:repository:bitbucket`](#delete-repository-bitbucket)\n- [`read:pullrequest:bitbucket`](#read-pullrequest-bitbucket)\n- [`write:pullrequest:bitbucket`](#write-pullrequest-bitbucket)\n- [`read:project:bitbucket`](#read-project-bitbucket)\n- [`admin:project:bitbucket`](#admin-project-bitbucket)\n- [`read:workspace:bitbucket`](#read-workspace-bitbucket)\n- [`read:user:bitbucket`](#read-user-bitbucket)\n- [`read:pipeline:bitbucket`](#read-pipeline-bitbucket)\n- [`write:pipeline:bitbucket`](#write-pipeline-bitbucket)\n- [`admin:pipeline:bitbucket`](#admin-pipeline-bitbucket)\n- [`read:runner:bitbucket`](#read-runner-bitbucket)\n- [`write:runner:bitbucket`](#write-runner-bitbucket)\n\n#### read:repository:bitbucket\n\nAllows viewing of repository data. Note that this scope does not give access to a repository's pull requests.\n* access to the repository's source code\n* access the file browsing API\n* access to certain repository configurations such as branching model, default reviewers, etc.\n\n#### write:repository:bitbucket\n\nAllows modification of repository data. No distinction is made between public and private repositories. This scope does not imply the `read:repository:bitbucket` scope, so you need to request that separately if required. This scope alone does not give access to the pull request API.\n* update/delete source, branches, tags, etc.\n* fork repositories\n\n#### admin:repository:bitbucket\n\nAllows admin activities on repositories. No distinction is made between public and private repositories. This scope does not implicitly grant the `read:repository:bitbucket` or the `write:repository:bitbucket` scopes. It gives access to the admin features of a repository only, not direct access to its contents. This scope does not allow modification of repository permissions. This scope comes with access to the following functionality:\n* create repository\n* view repository permissions\n* view and edit branch restrictions\n* edit branching model settings\n* edit default reviewers\n* view and edit inheritance state for repository settings\n\n#### delete:repository:bitbucket\nAllows deletion of repositories.\n\n#### read:pullrequest:bitbucket\nAllows viewing of pull requests, plus the ability to comment on pull requests.\n\nThis scope does not imply the `read:repository:bitbucket` scope. With this scope, you could retrieve some data specific to the source/destination repositories of a pull request using pull request endpoints, but it does not give access to repository API endpoints.\n\n#### write:pullrequest:bitbucket\nAllows the ability to create, update, approve, decline, and merge pull requests.\n\nThis scope does not imply the `write:repository:bitbucket` scope.\n\n#### read:project:bitbucket\nAllows viewing of project and project permission data.\n\n#### admin:project:bitbucket\nAllows the ability to create, update, and delete project. No distinction is made between public and private projects.\n\nThis scope does not implicitly grant the `read:project:bitbucket` scope or any repository scopes. It gives access to the admin features of a project only, not direct access to its repositories' contents.\n\n#### read:workspace:bitbucket\nAllows viewing of workspace and workspace permission data.\n\n#### read:user:bitbucket\nAllows viewing of data related to the current user.\n\n#### read:pipeline:bitbucket\nAllows read access to all pipeline information (pipelines, steps, caches, artifacts, logs, tests, code-insights).\n\n#### write:pipeline:bitbucket\nAllows running pipelines (i.e., start/stop/create pipeline) and uploading tests/code-insights.\n\nThis scope does not imply the `read:pipeline:bitbucket` scope.\n\n#### admin:pipeline:bitbucket\nAllows admin activities, such as creating pipeline variables.\n\nThis scope does not implicitly grant the `read:pipeline:bitbucket` or the `write:pipeline:bitbucket` scopes.\n\n#### read:runner:bitbucket\nAllows viewing of runners information.\n\n#### write:runner:bitbucket\nAllows runners management.\n\nThis scope does not imply the `read:runners:bitbucket` scope.\n"
},
{
"anchor": "filtering",
"title": "Filter and sort API objects",
"description": "Query the 2.0 API for specific objects",
- "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTk0LjE5MTkgMTQ3LjYwOTIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjY2ZkNGRiOwogICAgICB9CgogICAgICAuY2xzLTMsIC5jbHMtNCB7CiAgICAgICAgZmlsbDogIzg3NzdkOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBtaXgtYmxlbmQtbW9kZTogbXVsdGlwbHk7CiAgICAgIH0KCiAgICAgIC5jbHMtNSB7CiAgICAgICAgZmlsbDogIzAwNjVmZjsKICAgICAgfQoKICAgICAgLmNscy02IHsKICAgICAgICBmaWxsOiAjY2NlMGZmOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIGZpbGw6IHVybCgjbGluZWFyLWdyYWRpZW50KTsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iNDE2LjMwODIiIHkxPSI3NS4wNDc5IiB4Mj0iNTg0Ljg1NTYiIHkyPSI3NS4wNDc5IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC00NDMuOTQ2NyAxMjMuMDY4Nikgcm90YXRlKC0xMy43OTc2KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmYiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIwLjY5MDgiIHN0b3AtY29sb3I9IiNmZmYiIHN0b3Atb3BhY2l0eT0iMC4xIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KICA8dGl0bGU+TWFnbmlmeWluZyBHbGFzczwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTMxLjExMjUsOTQuOTMwN2wtOS44ODc4LTUuOTg4OC04LjMyOTIsMTMuNzUxOSw5Ljg4NzgsNS45ODg4YTE1LjYwMywxNS42MDMsMCwwLDEsNS44OCw2LjM4MzVoMGExNS42MDMsMTUuNjAzLDAsMCwwLDUuODgsNi4zODM1bDQwLjExNDMsMjQuMjk2NGExMi44NjY0LDEyLjg2NjQsMCwwLDAsMTcuNjcwOS00LjM0aDBhMTIuODY2NCwxMi44NjY0LDAsMCwwLTQuMzQtMTcuNjcwOUwxNDcuODc0OSw5OS40MzkyYTE1LjYwMywxNS42MDMsMCwwLDAtOC4zODEyLTIuMjU0MmgwQTE1LjYwMywxNS42MDMsMCwwLDEsMTMxLjExMjUsOTQuOTMwN1oiLz4KICAgICAgICA8cGF0aCBpZD0iX1BhdGhfIiBkYXRhLW5hbWU9IiZsdDtQYXRoJmd0OyIgY2xhc3M9ImNscy0zIiBkPSJNMTMxLjExMjUsOTQuOTMwN2wtMy4wMTE4LTEuODI0MkE4LjAzODgsOC4wMzg4LDAsMCwwLDExNy4wNiw5NS44MTc4aDBhOC4wMzg4LDguMDM4OCwwLDAsMCwyLjcxMTMsMTEuMDQwNWwzLjAxMTgsMS44MjQyYTE1LjYwMywxNS42MDMsMCwwLDEsNS44OCw2LjM4MzVoMGExNS42MDMsMTUuNjAzLDAsMCwwLDUuODgsNi4zODM1bDQwLjExNDMsMjQuMjk2NGExMi44NjY0LDEyLjg2NjQsMCwwLDAsMTcuNjcwOS00LjM0aDBhMTIuODY2NCwxMi44NjY0LDAsMCwwLTQuMzQtMTcuNjcwOUwxNDcuODc0OSw5OS40MzkyYTE1LjYwMywxNS42MDMsMCwwLDAtOC4zODEyLTIuMjU0M2gwQTE1LjYwMywxNS42MDMsMCwwLDEsMTMxLjExMjUsOTQuOTMwN1oiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMzkuMTQzNyw5Ny4xNzkyYTE1LjU5NzMsMTUuNTk3MywwLDAsMS04LjAzMTItMi4yNDg1bC0zLjAxMTgtMS44MjQyQTguMDM4OCw4LjAzODgsMCwwLDAsMTE3LjA2LDk1LjgxNzhoMGE4LjAzODgsOC4wMzg4LDAsMCwwLDIuNzExMywxMS4wNDA1bDMuMDExOCwxLjgyNDJhMTUuNTk3LDE1LjU5NywwLDAsMSw1LjcwNjksNi4wNjQ4LDY3Ljg0ODEsNjcuODQ4MSwwLDAsMCwxMC42NTM2LTE3LjU2ODFaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy01IiBkPSJNODMuMjUzNywxMzIuNTU2QTY3LjIzNDgsNjcuMjM0OCwwLDAsMSw5LjcxLDMyLjQyOTUsNjYuNzk3NCw2Ni43OTc0LDAsMCwxLDUxLjE4MzcsMS45NjY2bC4wMDA3LDBBNjYuNzk2Miw2Ni43OTYyLDAsMCwxLDEwMi4wNTEsOS43NTI1aDBBNjcuMjM0Niw2Ny4yMzQ2LDAsMCwxLDgzLjI1MzcsMTMyLjU1NloiLz4KICAgICAgICA8cGF0aCBpZD0iX1BhdGhfMiIgZGF0YS1uYW1lPSImbHQ7UGF0aCZndDsiIGNsYXNzPSJjbHMtNiIgZD0iTTIzLjQzOSw0MC43NDgyQTUxLjE5MDgsNTEuMTkwOCwwLDAsMCwxMTEuMDEsOTMuNzg4OSw1MS4xOTA4LDUxLjE5MDgsMCwwLDAsMjMuNDM5LDQwLjc0ODJaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy03IiBkPSJNNzkuNDMzLDExNi45ODJBNTEuMjE2Miw1MS4yMTYyLDAsMCwwLDExOC40MjQxLDY3LjAzN2E0OS4xMzkxLDQ5LjEzOTEsMCwwLDEtNS4wODY3LDIuMjc4OWMtMTUuNzAyOSw1Ljk2MDktMjkuNjg5NSwyLjExLTM2LjQ5ODcuMTMwOC0yMC40MzA3LTUuOTM5LTI0Ljc5LTE3LjM3ODUtMzkuMDQxNC0yNC41ODIzYTQ4LjMwOTIsNDguMzA5MiwwLDAsMC0xNC4wOTM5LTQuNTNjLS4wODYyLjEzOTUtLjE3OTMuMjczLS4yNjQ0LjQxMzVBNTEuMTkwNyw1MS4xOTA3LDAsMCwwLDc5LjQzMywxMTYuOTgyWiIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K'",
- "body": "\nYou can query the 2.0 API for specific objects using a simple language which resembles SQL.\n\nNote that filtering and querying by username has been deprecated, due to privacy changes. \nSee the [announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/#changes-to-querying) \nfor details.\n\n---\n\n* [Supported endpoints](#supported-endpoints)\n* [Operators](#operators)\n* [Data types](#data-types)\n* [Querying](#querying)\n* [Sorting query results](#sorting-query-results)\n\n----\n\n### Supported endpoints\n\nMost 2.0 API resources that return paginated collections of objects support a single, shared, generic querying language that is used to filter down a result set.\n\nThis includes, but is in no way limited to:\n\n /2.0/repositories/{username}\n /2.0/repositories/{username}/{slug}/refs\n /2.0/repositories/{username}/{slug}/refs/branches\n /2.0/repositories/{username}/{slug}/refs/tags\n /2.0/repositories/{username}/{slug}/forks\n /2.0/repositories/{username}/{slug}/src\n /2.0/repositories/{username}/{slug}/issues\n /2.0/repositories/{username}/{slug}/pullrequests\n\nFiltering and sorting supports several distinct operators and data types as well as basic features, like logical operators (AND, OR).\nAs examples, the following queries could be used on the issue tracker endpoint (`/2.0/repositories/{workspace}/{slug}/issues/`):\n\n\t(state = \"open\" OR state = \"new\") AND assignee = null\n\treporter.nickname != \"evzijst\" AND priority >= \"major\"\n\t(title ~ \"unicode\" OR content.raw ~ \"unicode\") AND created_on > 2015-10-04T14:00:00-07:00\n\nFilter queries can be added to the URL using the q= query parameter. To sort the response, add sort=. Note that the entire query string is put in the q parameter and hence needs to be URL-encoded as shown in the following example:\n\n\t/2.0/repositories/foo/bar/issues?q=state=\"new\"&sort=-updated_on\n\n\n### Operators\n\nFiltering and sorting supports the following operators:\n\n| Operator | Definition | Example |\n|----------|--------------------------------|----------------------------|\n| \"=\" | test for equality | `nickname = \"evzijst\"` |\n| \"!=\" | not equal | `is_private != true` |\n| \"~\" | case-insensitive text contains | `description ~ \"beef\"` |\n| \"!~\" | case-insensitive not contains | `description !~ \"fubar\"` |\n| \">\" | greater than | `priority > \"major\"` |\n| \">=\" | greater than or equal | `priority <= \"trivial\"` |\n| \"<\" | less than | `id < 1234` |\n| \"<=\" | less than or equal | `updated_on <= 2015-03-04` |\n\n### Data types\n\nFiltering and sorting supports the following data types:\n\n| Type | Description | Example |\n|--------------|-----------------------------------------|---------------|\n| **String** | any text inside double quotes | `\"foo\"` |\n| **Number** | arbitrary precision integers and floats | `1, -10.302` |\n| **Null** | to test for the absence of a value | `null` |\n| **boolean** | the unquoted strings true or false | `true, false` |\n| **datetime** | an unquoted [ISO-8601][iso-8601] date time string with the timezone offset, milliseconds and entire time component being optional | `2015-03-04T14:08:59.123+02:00`, `2015-03-04T14:08:59` Date time strings are assumed to be in UTC, unless an explicit timezone offset is provided |\n\n[https://en.wikipedia.org/wiki/ISO_8601]: /iso-8601\n\n### Querying\n\nObjects can be filtered based on their properties. In principle, every element in an object's JSON document schema can be used as a filter criterion.\n\nNote that while the array of objects in a paginated response is wrapped in an\nenvelope with a `values` element, this prefix should not be included in the\nquery fields (so use `/2.0/repositories/foo/bar/issues?q=state=\"new\"`, not\n`/2.0/repositories/foo/bar/issues?q=values.state=\"new\"`).\n\n\n### Examples\n\nFields that contain embedded instances of other object types (e.g. owner is an embedded user object, while parent is an embedded repository) can be traversed recursively. For instance:\n\n\tparent.owner.nickname = \"bitbucket\"\n\nTo find pull requests which merge into master, come from a fork of the repo rather than a branch inside the repo, and on which I am a reviewer:\n\n```\nsource.repository.full_name != \"main/repo\" AND state = \"OPEN\" AND reviewers.nickname = \"evzijst\" AND destination.branch.name = \"master\"\n```\n```\n/2.0/repositories/main/repo/pullrequests?q=source.repository.full_name+%21%3D+%22main%2Frepo%22+AND+state+%3D+%22OPEN%22+AND+reviewers.nickname+%3D+%22evzijst%22+AND+destination.branch.name+%3D+%22master%22\n```\n\nTo find new or on-hold issues related to the UI, created or updated in the last day (SF local time), that have not yet been assigned to anyone:\n\n```\n(state = \"new\" OR state = \"on hold\") AND assignee = null AND component = \"UI\" and updated_on > 2015-11-11T00:00:00-07:00\n```\n```\n/2.0/repositories/main/repo/issues?q=%28state+%3D+%22new%22+OR+state+%3D+%22on+hold%22%29+AND+assignee+%3D+null+AND+component+%3D+%22UI%22+and+updated_on+%3E+2015-11-11T00%3A00%3A00-07%3A00\n```\n\nTo find all tags with the string \"2015\" in the name:\n\n```\nname ~ \"2015\"\n```\n```\n/2.0/repositories/{username}/{slug}/refs/tags?q=name+%7E+%222015%22\n```\nOr all my branches:\n\n```\nname ~ \"erik/\"\n```\n```\n/2.0/repositories/{username}/{slug}/refs/?q=name+%7E+%22erik%2F%22\n```\n### Sorting query results\n\nYou can sort result sets using the ?sort= query parameter, available on the same resources that support filtering:\n\n* In principle, every field that can be queried can also be used as a key for sorting.\n* By default the sort order is ascending. To reverse the order, prefix the field name with a hyphen (e.g. ?sort=-updated_on).\n* Only one field can be sorted on. Compound fields (e.g. sort on state first, followed by updated_on) are not supported.\n\n\n"
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTk0LjE5MTkgMTQ3LjYwOTIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjY2ZkNGRiOwogICAgICB9CgogICAgICAuY2xzLTMsIC5jbHMtNCB7CiAgICAgICAgZmlsbDogIzg3NzdkOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBtaXgtYmxlbmQtbW9kZTogbXVsdGlwbHk7CiAgICAgIH0KCiAgICAgIC5jbHMtNSB7CiAgICAgICAgZmlsbDogIzAwNjVmZjsKICAgICAgfQoKICAgICAgLmNscy02IHsKICAgICAgICBmaWxsOiAjY2NlMGZmOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIGZpbGw6IHVybCgjbGluZWFyLWdyYWRpZW50KTsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iNDE2LjMwODIiIHkxPSI3NS4wNDc5IiB4Mj0iNTg0Ljg1NTYiIHkyPSI3NS4wNDc5IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC00NDMuOTQ2NyAxMjMuMDY4Nikgcm90YXRlKC0xMy43OTc2KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmYiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIwLjY5MDgiIHN0b3AtY29sb3I9IiNmZmYiIHN0b3Atb3BhY2l0eT0iMC4xIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KICA8dGl0bGU+TWFnbmlmeWluZyBHbGFzczwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTMxLjExMjUsOTQuOTMwN2wtOS44ODc4LTUuOTg4OC04LjMyOTIsMTMuNzUxOSw5Ljg4NzgsNS45ODg4YTE1LjYwMywxNS42MDMsMCwwLDEsNS44OCw2LjM4MzVoMGExNS42MDMsMTUuNjAzLDAsMCwwLDUuODgsNi4zODM1bDQwLjExNDMsMjQuMjk2NGExMi44NjY0LDEyLjg2NjQsMCwwLDAsMTcuNjcwOS00LjM0aDBhMTIuODY2NCwxMi44NjY0LDAsMCwwLTQuMzQtMTcuNjcwOUwxNDcuODc0OSw5OS40MzkyYTE1LjYwMywxNS42MDMsMCwwLDAtOC4zODEyLTIuMjU0MmgwQTE1LjYwMywxNS42MDMsMCwwLDEsMTMxLjExMjUsOTQuOTMwN1oiLz4KICAgICAgICA8cGF0aCBpZD0iX1BhdGhfIiBkYXRhLW5hbWU9IiZsdDtQYXRoJmd0OyIgY2xhc3M9ImNscy0zIiBkPSJNMTMxLjExMjUsOTQuOTMwN2wtMy4wMTE4LTEuODI0MkE4LjAzODgsOC4wMzg4LDAsMCwwLDExNy4wNiw5NS44MTc4aDBhOC4wMzg4LDguMDM4OCwwLDAsMCwyLjcxMTMsMTEuMDQwNWwzLjAxMTgsMS44MjQyYTE1LjYwMywxNS42MDMsMCwwLDEsNS44OCw2LjM4MzVoMGExNS42MDMsMTUuNjAzLDAsMCwwLDUuODgsNi4zODM1bDQwLjExNDMsMjQuMjk2NGExMi44NjY0LDEyLjg2NjQsMCwwLDAsMTcuNjcwOS00LjM0aDBhMTIuODY2NCwxMi44NjY0LDAsMCwwLTQuMzQtMTcuNjcwOUwxNDcuODc0OSw5OS40MzkyYTE1LjYwMywxNS42MDMsMCwwLDAtOC4zODEyLTIuMjU0M2gwQTE1LjYwMywxNS42MDMsMCwwLDEsMTMxLjExMjUsOTQuOTMwN1oiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMzkuMTQzNyw5Ny4xNzkyYTE1LjU5NzMsMTUuNTk3MywwLDAsMS04LjAzMTItMi4yNDg1bC0zLjAxMTgtMS44MjQyQTguMDM4OCw4LjAzODgsMCwwLDAsMTE3LjA2LDk1LjgxNzhoMGE4LjAzODgsOC4wMzg4LDAsMCwwLDIuNzExMywxMS4wNDA1bDMuMDExOCwxLjgyNDJhMTUuNTk3LDE1LjU5NywwLDAsMSw1LjcwNjksNi4wNjQ4LDY3Ljg0ODEsNjcuODQ4MSwwLDAsMCwxMC42NTM2LTE3LjU2ODFaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy01IiBkPSJNODMuMjUzNywxMzIuNTU2QTY3LjIzNDgsNjcuMjM0OCwwLDAsMSw5LjcxLDMyLjQyOTUsNjYuNzk3NCw2Ni43OTc0LDAsMCwxLDUxLjE4MzcsMS45NjY2bC4wMDA3LDBBNjYuNzk2Miw2Ni43OTYyLDAsMCwxLDEwMi4wNTEsOS43NTI1aDBBNjcuMjM0Niw2Ny4yMzQ2LDAsMCwxLDgzLjI1MzcsMTMyLjU1NloiLz4KICAgICAgICA8cGF0aCBpZD0iX1BhdGhfMiIgZGF0YS1uYW1lPSImbHQ7UGF0aCZndDsiIGNsYXNzPSJjbHMtNiIgZD0iTTIzLjQzOSw0MC43NDgyQTUxLjE5MDgsNTEuMTkwOCwwLDAsMCwxMTEuMDEsOTMuNzg4OSw1MS4xOTA4LDUxLjE5MDgsMCwwLDAsMjMuNDM5LDQwLjc0ODJaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy03IiBkPSJNNzkuNDMzLDExNi45ODJBNTEuMjE2Miw1MS4yMTYyLDAsMCwwLDExOC40MjQxLDY3LjAzN2E0OS4xMzkxLDQ5LjEzOTEsMCwwLDEtNS4wODY3LDIuMjc4OWMtMTUuNzAyOSw1Ljk2MDktMjkuNjg5NSwyLjExLTM2LjQ5ODcuMTMwOC0yMC40MzA3LTUuOTM5LTI0Ljc5LTE3LjM3ODUtMzkuMDQxNC0yNC41ODIzYTQ4LjMwOTIsNDguMzA5MiwwLDAsMC0xNC4wOTM5LTQuNTNjLS4wODYyLjEzOTUtLjE3OTMuMjczLS4yNjQ0LjQxMzVBNTEuMTkwNyw1MS4xOTA3LDAsMCwwLDc5LjQzMywxMTYuOTgyWiIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K",
+ "body": "\nYou can query the 2.0 API for specific objects using a simple language which resembles SQL.\n\nNote that filtering and querying by username has been deprecated, due to privacy changes. \nSee the [announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/#changes-to-querying) \nfor details.\n\n---\n\n* [Supported endpoints](#supported-endpoints)\n* [Operators](#operators)\n* [Data types](#data-types)\n* [Querying](#querying)\n* [Sorting query results](#sorting-query-results)\n\n----\n\n### Supported endpoints\n\nMost 2.0 API resources that return paginated collections of objects support a single, shared, generic querying language that is used to filter down a result set.\n\nThis includes, but is in no way limited to:\n\n /2.0/repositories/{username}\n /2.0/repositories/{username}/{slug}/refs\n /2.0/repositories/{username}/{slug}/refs/branches\n /2.0/repositories/{username}/{slug}/refs/tags\n /2.0/repositories/{username}/{slug}/forks\n /2.0/repositories/{username}/{slug}/src\n /2.0/repositories/{username}/{slug}/issues\n /2.0/repositories/{username}/{slug}/pullrequests\n\nFiltering and sorting supports several distinct operators and data types as well as basic features, like logical operators (AND, OR).\nAs examples, the following queries could be used on the issue tracker endpoint (`/2.0/repositories/{workspace}/{slug}/issues/`):\n\n\t(state = \"open\" OR state = \"new\") AND assignee = null\n\treporter.nickname != \"evzijst\" AND priority >= \"major\"\n\t(title ~ \"unicode\" OR content.raw ~ \"unicode\") AND created_on > 2015-10-04T14:00:00-07:00\n\nFilter queries can be added to the URL using the q= query parameter. To sort the response, add sort=. Note that the entire query string is put in the q parameter and hence needs to be URL-encoded as shown in the following example:\n\n\t/2.0/repositories/foo/bar/issues?q=state=\"new\"&sort=-updated_on\n\n\n### Operators\n\nFiltering and sorting supports the following operators:\n\n| Operator | Definition | Example |\n|----------|--------------------------------|---------------------------------------|\n| \"=\" | test for equality | `nickname = \"evzijst\"` |\n| \"!=\" | not equal | `is_private != true` |\n| \"~\" | case-insensitive text contains | `description ~ \"beef\"` |\n| \"!~\" | case-insensitive not contains | `description !~ \"fubar\"` |\n| \">\" | greater than | `priority > \"major\"` |\n| \">=\" | greater than or equal | `priority <= \"trivial\"` |\n| \"<\" | less than | `id < 1234` |\n| \"<=\" | less than or equal | `updated_on <= 2015-03-04` |\n| \"IN\" | value present in list | `state IN (\"OPEN\", \"MERGED\")` |\n| \"NOT IN\" | value not present in list | `state NOT IN (\"DECLINED\", \"MERGED\")` |\n\n### Data types\n\nFiltering and sorting supports the following data types:\n\n| Type | Description | Example |\n|--------------|-----------------------------------------|-----------------|\n| **String** | any text inside double quotes | `\"foo\"` |\n| **Number** | arbitrary precision integers and floats | `1`, `-10.302` |\n| **Null** | to test for the absence of a value | `null` |\n| **boolean** | the unquoted strings true or false | `true`, `false` |\n| **datetime** | an unquoted [ISO-8601][iso-8601] date time string with the timezone offset, milliseconds and entire time component being optional | `2015-03-04T14:08:59.123+02:00`, `2015-03-04T14:08:59` Date time strings are assumed to be in UTC, unless an explicit timezone offset is provided |\n| **list** | a comma separated list of values enclosed in parentheses | `(\"a\", \"b\")`, `(1, 2)` |\n\n[iso-8601]: https://en.wikipedia.org/wiki/ISO_8601\n\n### Querying\n\nObjects can be filtered based on their properties. In principle, every element in an object's JSON document schema can be used as a filter criterion.\n\nNote that while the array of objects in a paginated response is wrapped in an\nenvelope with a `values` element, this prefix should not be included in the\nquery fields (so use `/2.0/repositories/foo/bar/issues?q=state=\"new\"`, not\n`/2.0/repositories/foo/bar/issues?q=values.state=\"new\"`).\n\n\n### Examples\n\nFields that contain embedded instances of other object types (e.g. owner is an embedded user object, while parent is an embedded repository) can be traversed recursively. For instance:\n\n\tparent.owner.nickname = \"bitbucket\"\n\nTo find pull requests which merge into master, come from a fork of the repo rather than a branch inside the repo, and on which I am a reviewer:\n\n```\nsource.repository.full_name != \"main/repo\" AND state = \"OPEN\" AND reviewers.nickname = \"evzijst\" AND destination.branch.name = \"master\"\n```\n```\n/2.0/repositories/main/repo/pullrequests?q=source.repository.full_name+%21%3D+%22main%2Frepo%22+AND+state+%3D+%22OPEN%22+AND+reviewers.nickname+%3D+%22evzijst%22+AND+destination.branch.name+%3D+%22master%22\n```\n\nTo find new or on-hold issues related to the UI, created or updated in the last day (SF local time), that have not yet been assigned to anyone:\n\n```\nstate IN (\"new\", \"on hold\") AND assignee = null AND component = \"UI\" and updated_on > 2015-11-11T00:00:00-07:00\n```\n```\n/2.0/repositories/main/repo/issues?q=state%20IN%20%28%22new%22%2C%20%22on%20hold%22%29%20AND%20assignee%20%3D%20null%20AND%20component%20%3D%20%22UI%22%20and%20updated%5Fon%20%3E%202015%2D11%2D11T00%3A00%3A00%2D07%3A00\n```\n\nTo find all tags with the string \"2015\" in the name:\n\n```\nname ~ \"2015\"\n```\n```\n/2.0/repositories/{username}/{slug}/refs/tags?q=name+%7E+%222015%22\n```\nOr all my branches:\n\n```\nname ~ \"erik/\"\n```\n```\n/2.0/repositories/{username}/{slug}/refs/?q=name+%7E+%22erik%2F%22\n```\n### Sorting query results\n\nYou can sort result sets using the ?sort= query parameter, available on the same resources that support filtering:\n\n* In principle, every field that can be queried can also be used as a key for sorting.\n* By default the sort order is ascending. To reverse the order, prefix the field name with a hyphen (e.g. ?sort=-updated_on).\n* Only one field can be sorted on. Compound fields (e.g. sort on state first, followed by updated_on) are not supported.\n\n\n"
},
{
"anchor": "pagination",
"title": "Pagination",
"description": "Learn more about pagination",
- "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjM4LjgyIDE1MS42Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2IyZDRmZjt9LmNscy0ye2ZpbGw6IzRjOWFmZjt9LmNscy0ze2ZpbGw6IzAwNTJjYzt9LmNscy00e29wYWNpdHk6MC42O30uY2xzLTV7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudCk7fS5jbHMtNntmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTIpO30uY2xzLTd7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudC0zKTt9LmNscy04e2ZpbGw6dXJsKCNsaW5lYXItZ3JhZGllbnQtNCk7fS5jbHMtOXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTUpO30uY2xzLTEwLC5jbHMtMTEsLmNscy0xMntmaWxsOm5vbmU7fS5jbHMtMTB7c3Ryb2tlOiMzMzg0ZmY7fS5jbHMtMTAsLmNscy0xMSwuY2xzLTEyLC5jbHMtMTN7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9LmNscy0xMXtzdHJva2U6I2ZmYWIwMDt9LmNscy0xMntzdHJva2U6I2ZhZmJmYzt9LmNscy0xM3tmaWxsOiNmZmFiMDA7c3Ryb2tlOiMyNjg0ZmY7fTwvc3R5bGU+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHkxPSI2NS4xNyIgeDI9Ijg2LjM4IiB5Mj0iNjUuMTciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM0YzlhZmYiLz48c3RvcCBvZmZzZXQ9IjAuMDgiIHN0b3AtY29sb3I9IiM0YzlhZmYiIHN0b3Atb3BhY2l0eT0iMC45NCIvPjxzdG9wIG9mZnNldD0iMC4yNCIgc3RvcC1jb2xvcj0iIzRjOWFmZiIgc3RvcC1vcGFjaXR5PSIwLjc4Ii8+PHN0b3Agb2Zmc2V0PSIwLjQ1IiBzdG9wLWNvbG9yPSIjNGM5YWZmIiBzdG9wLW9wYWNpdHk9IjAuNTMiLz48c3RvcCBvZmZzZXQ9IjAuNTUiIHN0b3AtY29sb3I9IiM0YzlhZmYiIHN0b3Atb3BhY2l0eT0iMC40Ii8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC0yIiB4MT0iMTUyLjQ0IiB5MT0iNjUuMTciIHgyPSIyMzguODIiIHkyPSI2NS4xNyIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTMiIHgxPSIxOC44NSIgeTE9IjExOC43OCIgeDI9IjEyNi4wOCIgeTI9IjExLjU2IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwLjA2IiBzdG9wLWNvbG9yPSIjMDA2NWZmIi8+PHN0b3Agb2Zmc2V0PSIwLjE5IiBzdG9wLWNvbG9yPSIjMDA2NWZmIiBzdG9wLW9wYWNpdHk9IjAuOTQiLz48c3RvcCBvZmZzZXQ9IjAuNDYiIHN0b3AtY29sb3I9IiMwMDY1ZmYiIHN0b3Atb3BhY2l0eT0iMC43OCIvPjxzdG9wIG9mZnNldD0iMC44MiIgc3RvcC1jb2xvcj0iIzAwNjVmZiIgc3RvcC1vcGFjaXR5PSIwLjUzIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDA2NWZmIiBzdG9wLW9wYWNpdHk9IjAuNCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNCIgeDE9IjExMi43NSIgeTE9IjExOC43OCIgeDI9IjIxOS45NyIgeTI9IjExLjU2IiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50LTMiLz48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC01IiB4MT0iNTAuOTciIHkxPSIxMzMuNjEiIHgyPSIxODcuODYiIHkyPSItMy4yOCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMC42NiIgc3RvcC1jb2xvcj0iIzI1Mzg1OCIvPjxzdG9wIG9mZnNldD0iMC44OCIgc3RvcC1jb2xvcj0iIzI1Mzg1OCIgc3RvcC1vcGFjaXR5PSIwLjgzIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMjUzODU4IiBzdG9wLW9wYWNpdHk9IjAuNyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjx0aXRsZT5WaWV3IFZlcnNpb25zPC90aXRsZT48ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj48ZyBpZD0iU29mdHdhcmUiPjxjaXJjbGUgY2xhc3M9ImNscy0xIiBjeD0iOTQuNTMiIGN5PSIxNDcuOTMiIHI9IjMuNjciLz48Y2lyY2xlIGNsYXNzPSJjbHMtMiIgY3g9IjEwNi45NCIgY3k9IjE0Ny45MyIgcj0iMy42NyIvPjxjaXJjbGUgY2xhc3M9ImNscy0zIiBjeD0iMTE5LjM0IiBjeT0iMTQ3LjkzIiByPSIzLjY3Ii8+PGNpcmNsZSBjbGFzcz0iY2xzLTIiIGN4PSIxMzEuNzUiIGN5PSIxNDcuOTMiIHI9IjMuNjciLz48Y2lyY2xlIGNsYXNzPSJjbHMtMSIgY3g9IjE0NC4xNiIgY3k9IjE0Ny45MyIgcj0iMy42NyIvPjxnIGNsYXNzPSJjbHMtNCI+PHJlY3QgaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTUiIHk9IjI1LjkyIiB3aWR0aD0iODYuMzgiIGhlaWdodD0iNzguNDkiLz48L2c+PGcgY2xhc3M9ImNscy00Ij48cmVjdCBpZD0iX1JlY3RhbmdsZV8yIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIHg9IjE1Mi40NCIgeT0iMjUuOTIiIHdpZHRoPSI4Ni4zOCIgaGVpZ2h0PSI3OC40OSIvPjwvZz48cmVjdCBpZD0iX1JlY3RhbmdsZV8zIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTciIHg9IjE2LjI4IiB5PSIxNC4xMiIgd2lkdGg9IjExMi4zNiIgaGVpZ2h0PSIxMDIuMDkiLz48cmVjdCBpZD0iX1JlY3RhbmdsZV80IiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTgiIHg9IjExMC4xOCIgeT0iMTQuMTIiIHdpZHRoPSIxMTIuMzYiIGhlaWdodD0iMTAyLjA5Ii8+PHJlY3QgaWQ9Il9SZWN0YW5nbGVfNSIgZGF0YS1uYW1lPSImbHQ7UmVjdGFuZ2xlJmd0OyIgY2xhc3M9ImNscy05IiB4PSI0Ny42OSIgd2lkdGg9IjE0My40NSIgaGVpZ2h0PSIxMzAuMzQiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNzkuMTYiIHkxPSIxNi4xOCIgeDI9IjExNy4yNCIgeTI9IjE2LjE4Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iMTYuMTgiIHgyPSI3Mi42IiB5Mj0iMTYuMTgiLz48bGluZSBjbGFzcz0iY2xzLTExIiB4MT0iNzkuMTYiIHkxPSIyNi45NSIgeDI9IjExNy4yNCIgeTI9IjI2Ljk1Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iMjYuOTUiIHgyPSI3Mi42IiB5Mj0iMjYuOTUiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNzkuMTYiIHkxPSIzNy43MiIgeDI9IjE1MC43IiB5Mj0iMzcuNzIiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSIzNy43MiIgeDI9IjcyLjYiIHkyPSIzNy43MiIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSIxNTAuNyIgeTE9IjQ4LjQ5IiB4Mj0iMTc1LjU5IiB5Mj0iNDguNDkiLz48bGluZSBjbGFzcz0iY2xzLTEyIiB4MT0iMTEwLjMyIiB5MT0iNDguNDkiIHgyPSIxNDMuMDUiIHkyPSI0OC40OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSI3OS4xNiIgeTE9IjQ4LjQ5IiB4Mj0iMTAxLjM3IiB5Mj0iNDguNDkiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSI0OC40OSIgeDI9IjcyLjYiIHkyPSI0OC40OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI3OS4xNiIgeTE9IjU5LjI2IiB4Mj0iMTUwLjciIHkyPSI1OS4yNiIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjU5LjI2IiB4Mj0iNzIuNiIgeTI9IjU5LjI2Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9Ijc5LjE2IiB5MT0iNzAuMDMiIHgyPSIxNzUuNTkiIHkyPSI3MC4wMyIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjcwLjAzIiB4Mj0iNzIuNiIgeTI9IjcwLjAzIi8+PGxpbmUgY2xhc3M9ImNscy0xMSIgeDE9Ijc5LjE2IiB5MT0iODAuNzkiIHgyPSIxMTcuMjQiIHkyPSI4MC43OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjgwLjc5IiB4Mj0iNzIuNiIgeTI9IjgwLjc5Ii8+PGxpbmUgY2xhc3M9ImNscy0xMyIgeDE9Ijc5LjE2IiB5MT0iOTEuNTYiIHgyPSIxNDkuMDYiIHkyPSI5MS41NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjkxLjU2IiB4Mj0iNzIuNiIgeTI9IjkxLjU2Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iODAuNzkiIHgyPSI3Mi42IiB5Mj0iODAuNzkiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSI5MS41NiIgeDI9IjcyLjYiIHkyPSI5MS41NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSI3OS4xNiIgeTE9IjEwMi4zMyIgeDI9IjExNy4yNCIgeTI9IjEwMi4zMyIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjEwMi4zMyIgeDI9IjcyLjYiIHkyPSIxMDIuMzMiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iMTI1Ljk4IiB5MT0iMTEzLjEiIHgyPSIxNDkuMDYiIHkyPSIxMTMuMSIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSI3OS4xNiIgeTE9IjExMy4xIiB4Mj0iMTE3LjI0IiB5Mj0iMTEzLjEiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSIxMTMuMSIgeDI9IjcyLjYiIHkyPSIxMTMuMSIvPjwvZz48L2c+PC9zdmc+'",
- "body": "\nEndpoints that return collections of objects should always apply pagination.\nPaginated collections are always wrapped in the following wrapper object:\n\n```json\n{\n \"size\": 5421,\n \"page\": 2,\n \"pagelen\": 10,\n \"next\": \"https://api.bitbucket.org/2.0/repositories/pypy/pypy/commits?page=3\",\n \"previous\": \"https://api.bitbucket.org/2.0/repositories/pypy/pypy/commits?page=1\",\n \"values\": [\n ...\n ]\n}\n```\n\nPagination is often page-bound, with a query parameter page indicating which\npage is to be returned.\n\nHowever, clients are not expected to construct URLs themselves by manipulating\nthe page number query parameter. Instead, the response contains a link to the\nnext page. This link should be treated as an opaque location that is not to be\nconstructed by clients or even assumed to be predictable. The only contract\naround the next link is that it will return the next chunk of results.\n\nLack of a next link in the response indicates the end of the collection.\n\nThe paginated response contains the following fields:\n\n| Field | Value |\n|------------|----------|\n| `size` | Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute. |\n| `page` | Page number of the current results. This is an optional element that is not provided in all responses. |\n| `pagelen` | Current number of objects on the existing page. Globally, the minimum length is 10 and the maximum is 100. Some APIs may specify a different default. |\n| `next` | Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs. |\n| `previous` | Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs. |\n| `values` | The list of objects. This contains at most `pagelen` objects. |\n\nThe link to the next page is included such that you don't have to hardcode or construct any links. Only values and next are guaranteed (except the last page, which lacks next). This is because the previous and size values can be expensive for some data sets.\n\nIt is important to realize that Bitbucket support both list-based pagination and iterator-based pagination. List-based pagination assumes that the collection is a discrete, immutable, consistently ordered, finite array of objects with a fixed size. Clients navigate a list-based collection by requesting offset-based chunks. In Bitbucket Cloud, list-based responses include the optional size, page, and previous element. The next and previous links typically resemble something like /foo/bar?page=4.\n\nHowever, not all result sets can be treated as immutable and finite – much like how programming languages tend to distinguish between lists and arrays on one hand and iterators or stream on the other. Where an list-based pagination offers random access into any point in a collection, iterator-based pagination can only navigate forward one element at a time. In Bitbucket such iterator-based pagination contains the next link and pagelen elements, but not necessarily anything else. In these cases, the next link's value often contains an unpredictable hash instead of an explicit page number. The commits resource uses iterator-based pagination.\n"
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjM4LjgyIDE1MS42Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2IyZDRmZjt9LmNscy0ye2ZpbGw6IzRjOWFmZjt9LmNscy0ze2ZpbGw6IzAwNTJjYzt9LmNscy00e29wYWNpdHk6MC42O30uY2xzLTV7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudCk7fS5jbHMtNntmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTIpO30uY2xzLTd7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudC0zKTt9LmNscy04e2ZpbGw6dXJsKCNsaW5lYXItZ3JhZGllbnQtNCk7fS5jbHMtOXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTUpO30uY2xzLTEwLC5jbHMtMTEsLmNscy0xMntmaWxsOm5vbmU7fS5jbHMtMTB7c3Ryb2tlOiMzMzg0ZmY7fS5jbHMtMTAsLmNscy0xMSwuY2xzLTEyLC5jbHMtMTN7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9LmNscy0xMXtzdHJva2U6I2ZmYWIwMDt9LmNscy0xMntzdHJva2U6I2ZhZmJmYzt9LmNscy0xM3tmaWxsOiNmZmFiMDA7c3Ryb2tlOiMyNjg0ZmY7fTwvc3R5bGU+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHkxPSI2NS4xNyIgeDI9Ijg2LjM4IiB5Mj0iNjUuMTciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM0YzlhZmYiLz48c3RvcCBvZmZzZXQ9IjAuMDgiIHN0b3AtY29sb3I9IiM0YzlhZmYiIHN0b3Atb3BhY2l0eT0iMC45NCIvPjxzdG9wIG9mZnNldD0iMC4yNCIgc3RvcC1jb2xvcj0iIzRjOWFmZiIgc3RvcC1vcGFjaXR5PSIwLjc4Ii8+PHN0b3Agb2Zmc2V0PSIwLjQ1IiBzdG9wLWNvbG9yPSIjNGM5YWZmIiBzdG9wLW9wYWNpdHk9IjAuNTMiLz48c3RvcCBvZmZzZXQ9IjAuNTUiIHN0b3AtY29sb3I9IiM0YzlhZmYiIHN0b3Atb3BhY2l0eT0iMC40Ii8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC0yIiB4MT0iMTUyLjQ0IiB5MT0iNjUuMTciIHgyPSIyMzguODIiIHkyPSI2NS4xNyIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTMiIHgxPSIxOC44NSIgeTE9IjExOC43OCIgeDI9IjEyNi4wOCIgeTI9IjExLjU2IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwLjA2IiBzdG9wLWNvbG9yPSIjMDA2NWZmIi8+PHN0b3Agb2Zmc2V0PSIwLjE5IiBzdG9wLWNvbG9yPSIjMDA2NWZmIiBzdG9wLW9wYWNpdHk9IjAuOTQiLz48c3RvcCBvZmZzZXQ9IjAuNDYiIHN0b3AtY29sb3I9IiMwMDY1ZmYiIHN0b3Atb3BhY2l0eT0iMC43OCIvPjxzdG9wIG9mZnNldD0iMC44MiIgc3RvcC1jb2xvcj0iIzAwNjVmZiIgc3RvcC1vcGFjaXR5PSIwLjUzIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDA2NWZmIiBzdG9wLW9wYWNpdHk9IjAuNCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNCIgeDE9IjExMi43NSIgeTE9IjExOC43OCIgeDI9IjIxOS45NyIgeTI9IjExLjU2IiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50LTMiLz48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC01IiB4MT0iNTAuOTciIHkxPSIxMzMuNjEiIHgyPSIxODcuODYiIHkyPSItMy4yOCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMC42NiIgc3RvcC1jb2xvcj0iIzI1Mzg1OCIvPjxzdG9wIG9mZnNldD0iMC44OCIgc3RvcC1jb2xvcj0iIzI1Mzg1OCIgc3RvcC1vcGFjaXR5PSIwLjgzIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMjUzODU4IiBzdG9wLW9wYWNpdHk9IjAuNyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjx0aXRsZT5WaWV3IFZlcnNpb25zPC90aXRsZT48ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj48ZyBpZD0iU29mdHdhcmUiPjxjaXJjbGUgY2xhc3M9ImNscy0xIiBjeD0iOTQuNTMiIGN5PSIxNDcuOTMiIHI9IjMuNjciLz48Y2lyY2xlIGNsYXNzPSJjbHMtMiIgY3g9IjEwNi45NCIgY3k9IjE0Ny45MyIgcj0iMy42NyIvPjxjaXJjbGUgY2xhc3M9ImNscy0zIiBjeD0iMTE5LjM0IiBjeT0iMTQ3LjkzIiByPSIzLjY3Ii8+PGNpcmNsZSBjbGFzcz0iY2xzLTIiIGN4PSIxMzEuNzUiIGN5PSIxNDcuOTMiIHI9IjMuNjciLz48Y2lyY2xlIGNsYXNzPSJjbHMtMSIgY3g9IjE0NC4xNiIgY3k9IjE0Ny45MyIgcj0iMy42NyIvPjxnIGNsYXNzPSJjbHMtNCI+PHJlY3QgaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTUiIHk9IjI1LjkyIiB3aWR0aD0iODYuMzgiIGhlaWdodD0iNzguNDkiLz48L2c+PGcgY2xhc3M9ImNscy00Ij48cmVjdCBpZD0iX1JlY3RhbmdsZV8yIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIHg9IjE1Mi40NCIgeT0iMjUuOTIiIHdpZHRoPSI4Ni4zOCIgaGVpZ2h0PSI3OC40OSIvPjwvZz48cmVjdCBpZD0iX1JlY3RhbmdsZV8zIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTciIHg9IjE2LjI4IiB5PSIxNC4xMiIgd2lkdGg9IjExMi4zNiIgaGVpZ2h0PSIxMDIuMDkiLz48cmVjdCBpZD0iX1JlY3RhbmdsZV80IiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTgiIHg9IjExMC4xOCIgeT0iMTQuMTIiIHdpZHRoPSIxMTIuMzYiIGhlaWdodD0iMTAyLjA5Ii8+PHJlY3QgaWQ9Il9SZWN0YW5nbGVfNSIgZGF0YS1uYW1lPSImbHQ7UmVjdGFuZ2xlJmd0OyIgY2xhc3M9ImNscy05IiB4PSI0Ny42OSIgd2lkdGg9IjE0My40NSIgaGVpZ2h0PSIxMzAuMzQiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNzkuMTYiIHkxPSIxNi4xOCIgeDI9IjExNy4yNCIgeTI9IjE2LjE4Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iMTYuMTgiIHgyPSI3Mi42IiB5Mj0iMTYuMTgiLz48bGluZSBjbGFzcz0iY2xzLTExIiB4MT0iNzkuMTYiIHkxPSIyNi45NSIgeDI9IjExNy4yNCIgeTI9IjI2Ljk1Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iMjYuOTUiIHgyPSI3Mi42IiB5Mj0iMjYuOTUiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNzkuMTYiIHkxPSIzNy43MiIgeDI9IjE1MC43IiB5Mj0iMzcuNzIiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSIzNy43MiIgeDI9IjcyLjYiIHkyPSIzNy43MiIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSIxNTAuNyIgeTE9IjQ4LjQ5IiB4Mj0iMTc1LjU5IiB5Mj0iNDguNDkiLz48bGluZSBjbGFzcz0iY2xzLTEyIiB4MT0iMTEwLjMyIiB5MT0iNDguNDkiIHgyPSIxNDMuMDUiIHkyPSI0OC40OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSI3OS4xNiIgeTE9IjQ4LjQ5IiB4Mj0iMTAxLjM3IiB5Mj0iNDguNDkiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSI0OC40OSIgeDI9IjcyLjYiIHkyPSI0OC40OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI3OS4xNiIgeTE9IjU5LjI2IiB4Mj0iMTUwLjciIHkyPSI1OS4yNiIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjU5LjI2IiB4Mj0iNzIuNiIgeTI9IjU5LjI2Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9Ijc5LjE2IiB5MT0iNzAuMDMiIHgyPSIxNzUuNTkiIHkyPSI3MC4wMyIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjcwLjAzIiB4Mj0iNzIuNiIgeTI9IjcwLjAzIi8+PGxpbmUgY2xhc3M9ImNscy0xMSIgeDE9Ijc5LjE2IiB5MT0iODAuNzkiIHgyPSIxMTcuMjQiIHkyPSI4MC43OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjgwLjc5IiB4Mj0iNzIuNiIgeTI9IjgwLjc5Ii8+PGxpbmUgY2xhc3M9ImNscy0xMyIgeDE9Ijc5LjE2IiB5MT0iOTEuNTYiIHgyPSIxNDkuMDYiIHkyPSI5MS41NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjkxLjU2IiB4Mj0iNzIuNiIgeTI9IjkxLjU2Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iODAuNzkiIHgyPSI3Mi42IiB5Mj0iODAuNzkiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSI5MS41NiIgeDI9IjcyLjYiIHkyPSI5MS41NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSI3OS4xNiIgeTE9IjEwMi4zMyIgeDI9IjExNy4yNCIgeTI9IjEwMi4zMyIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjEwMi4zMyIgeDI9IjcyLjYiIHkyPSIxMDIuMzMiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iMTI1Ljk4IiB5MT0iMTEzLjEiIHgyPSIxNDkuMDYiIHkyPSIxMTMuMSIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSI3OS4xNiIgeTE9IjExMy4xIiB4Mj0iMTE3LjI0IiB5Mj0iMTEzLjEiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSIxMTMuMSIgeDI9IjcyLjYiIHkyPSIxMTMuMSIvPjwvZz48L2c+PC9zdmc+",
+ "body": "\nEndpoints that return collections of objects should always apply pagination.\nPaginated collections are always wrapped in the following wrapper object:\n\n```json\n{\n \"size\": 5421,\n \"page\": 2,\n \"pagelen\": 10,\n \"next\": \"https://api.bitbucket.org/2.0/repositories/pypy/pypy/commits?page=3\",\n \"previous\": \"https://api.bitbucket.org/2.0/repositories/pypy/pypy/commits?page=1\",\n \"values\": [\n ...\n ]\n}\n```\n\nPagination is often page-bound, with a query parameter page indicating which\npage is to be returned.\n\nHowever, clients are not expected to construct URLs themselves by manipulating\nthe page number query parameter. Instead, the response contains a link to the\nnext page. This link should be treated as an opaque location that is not to be\nconstructed by clients or even assumed to be predictable. The only contract\naround the next link is that it will return the next chunk of results.\n\nLack of a next link in the response indicates the end of the collection.\n\nThe paginated response contains the following fields:\n\n| Field | Value |\n|------------|----------|\n| `size` | Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute. |\n| `page` | Page number of the current results. This is an optional element that is not provided in all responses. |\n| `pagelen` | Current number of objects on the existing page. Globally, the minimum length is 10 and the maximum is 100. Some APIs may specify a different default. |\n| `next` | Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs. |\n| `previous` | Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs. |\n| `values` | The list of objects. This contains at most `pagelen` objects. |\n\nThe link to the next page is included such that you don't have to hardcode or construct any links. Only values and next are guaranteed (except the last page, which lacks next). This is because the previous and size values can be expensive for some data sets.\n\nIt is important to realize that Bitbucket support both list-based pagination and iterator-based pagination. List-based pagination assumes that the collection is a discrete, immutable, consistently ordered, finite array of objects with a fixed size. Clients navigate a list-based collection by requesting offset-based chunks. In Bitbucket Cloud, list-based responses include the optional size, page, and previous element. The the next and previous links typically resemble something like /foo/bar?page=4.\n\nHowever, not all result sets can be treated as immutable and finite – much like how programming languages tend to distinguish between lists and arrays on one hand and iterators or stream on the other. Where an list-based pagination offers random access into any point in a collection, iterator-based pagination can only navigate forward one element at a time. In Bitbucket such iterator-based pagination contains the next link and pagelen elements, but not necessarily anything else. In these cases, the next link's value often contains an unpredictable hash instead of an explicit page number. The commits resource uses iterator-based pagination.\n"
},
{
"anchor": "partial-response",
"title": "Partial responses",
"description": "Tweak which fields are returned",
- "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTYyLjQ0ODcgMjEwLjExMTUiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yLCAuY2xzLTYsIC5jbHMtOCB7CiAgICAgICAgZmlsbDogbm9uZTsKICAgICAgfQoKICAgICAgLmNscy0yLCAuY2xzLTggewogICAgICAgIHN0cm9rZTogIzAwNjVmZjsKICAgICAgICBzdHJva2Utd2lkdGg6IDJweDsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBzdHJva2UtbGluZWpvaW46IHJvdW5kOwogICAgICB9CgogICAgICAuY2xzLTMgewogICAgICAgIGZpbGw6ICNlN2U4ZWM7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgZmlsbDogI2ZmZTM4MDsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjZmZmMGIyOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIHN0cm9rZTogI2ZmOTkxZjsKICAgICAgICBzdHJva2Utd2lkdGg6IDEuODE1NnB4OwogICAgICB9CgogICAgICAuY2xzLTYsIC5jbHMtOCB7CiAgICAgICAgc3Ryb2tlLW1pdGVybGltaXQ6IDEwOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIG1peC1ibGVuZC1tb2RlOiBtdWx0aXBseTsKICAgICAgICBmaWxsOiB1cmwoI2xpbmVhci1ncmFkaWVudCk7CiAgICAgIH0KCiAgICAgIC5jbHMtOSB7CiAgICAgICAgZmlsbDogI2Y0ZjVmNzsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iMTEzLjM4MTgiIHkxPSI0OS40MyIgeDI9IjE1My43ODkzIiB5Mj0iOS4wMjI1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZhZmJmYyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjAuMjc4NiIgc3RvcC1jb2xvcj0iI2VmZjFmMyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjAuNzY4OCIgc3RvcC1jb2xvcj0iI2QxZDZkZCIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNjMWM3ZDAiLz4KICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgPC9kZWZzPgogIDx0aXRsZT5Eb2N1bWVudCBUYWJsZTwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0yIiB4MT0iMTcuNDcxIiB5MT0iMTcxLjc1NzMiIHgyPSI3OS4yOTgiIHkyPSIxNzEuNzU3MyIvPgogICAgICAgIDxwb2x5Z29uIGlkPSJfUGF0aF8iIGRhdGEtbmFtZT0iJmx0O1BhdGgmZ3Q7IiBjbGFzcz0iY2xzLTMiIHBvaW50cz0iMTYyLjQ0NSAzOC43MTEgMTYyLjQ0NSAyMTAuMTExIDAgMjEwLjExMSAwIDAgMTIzLjcwNCAwIDE2Mi40MTUgMzguNzExIDE2Mi40NDUgMzguNzExIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy00IiB4PSIxOC45MTE3IiB5PSI3OC4xNTQyIiB3aWR0aD0iNDcuODQ4NSIgaGVpZ2h0PSI3OS42NTM3Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy01IiB4PSIxOC45MTE3IiB5PSI1MS42MDMiIHdpZHRoPSIxMjMuNDUyOSIgaGVpZ2h0PSIyNi41NTEyIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy02IiB4PSIxOC45MTE3IiB5PSI1MS42MDMiIHdpZHRoPSIxMjMuNDUyOSIgaGVpZ2h0PSIxMDYuMjA0OSIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNiIgeDE9IjY2Ljc2MDEiIHkxPSI1MS42MDMiIHgyPSI2Ni43NjAxIiB5Mj0iMTU3LjgwNzkiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTYiIHgxPSI5MS4zMjg0IiB5MT0iNTEuNjAzIiB4Mj0iOTEuMzI4NCIgeTI9IjE1Ny44MDc5Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTE1Ljg5NjciIHkxPSI1MS42MDMiIHgyPSIxMTUuODk2NyIgeTI9IjE1Ny44MDc5Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTguOTExNyIgeTE9Ijc4LjE1NDIiIHgyPSIxNDIuMzY0NiIgeTI9Ijc4LjE1NDIiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTYiIHgxPSIxOC45MTE3IiB5MT0iMTA0LjcwNTUiIHgyPSIxNDIuMzY0NiIgeTI9IjEwNC43MDU1Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTguOTExNyIgeTE9IjEzMS4yNTY3IiB4Mj0iMTQyLjM2NDYiIHkyPSIxMzEuMjU2NyIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNyIgcG9pbnRzPSIxNjIuNDQ1IDM4LjcxMSAxNjIuNDE1IDM4LjcxMSAxMjMuODcyIDAuMTY5IDEyMy44NzIgNTkuOTIxIDE2Mi40NDUgMzkuMTM3IDE2Mi40NDUgMzguNzExIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy04IiB4MT0iMTguMzk3MyIgeTE9IjE4MS4zNDQiIHgyPSI3OS4xMTM1IiB5Mj0iMTgxLjM0NCIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIxOTAuOTMwNiIgeDI9IjUxLjMwNDkiIHkyPSIxOTAuOTMwNiIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIxNzEuNzU3MyIgeDI9Ijc5LjExMzUiIHkyPSIxNzEuNzU3MyIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIzNi4xNzA1IiB4Mj0iNzkuMTEzNSIgeTI9IjM2LjE3MDUiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTgiIHgxPSIxOC4zOTczIiB5MT0iMjYuNTgzOCIgeDI9Ijc5LjExMzUiIHkyPSIyNi41ODM4Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy05IiBwb2ludHM9IjE2Mi40NDkgMzguNzQyIDEyMy43MDcgMzguNzQyIDEyMy43MDcgMCAxNjIuNDQ5IDM4Ljc0MiIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K'",
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTYyLjQ0ODcgMjEwLjExMTUiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yLCAuY2xzLTYsIC5jbHMtOCB7CiAgICAgICAgZmlsbDogbm9uZTsKICAgICAgfQoKICAgICAgLmNscy0yLCAuY2xzLTggewogICAgICAgIHN0cm9rZTogIzAwNjVmZjsKICAgICAgICBzdHJva2Utd2lkdGg6IDJweDsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBzdHJva2UtbGluZWpvaW46IHJvdW5kOwogICAgICB9CgogICAgICAuY2xzLTMgewogICAgICAgIGZpbGw6ICNlN2U4ZWM7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgZmlsbDogI2ZmZTM4MDsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjZmZmMGIyOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIHN0cm9rZTogI2ZmOTkxZjsKICAgICAgICBzdHJva2Utd2lkdGg6IDEuODE1NnB4OwogICAgICB9CgogICAgICAuY2xzLTYsIC5jbHMtOCB7CiAgICAgICAgc3Ryb2tlLW1pdGVybGltaXQ6IDEwOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIG1peC1ibGVuZC1tb2RlOiBtdWx0aXBseTsKICAgICAgICBmaWxsOiB1cmwoI2xpbmVhci1ncmFkaWVudCk7CiAgICAgIH0KCiAgICAgIC5jbHMtOSB7CiAgICAgICAgZmlsbDogI2Y0ZjVmNzsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iMTEzLjM4MTgiIHkxPSI0OS40MyIgeDI9IjE1My43ODkzIiB5Mj0iOS4wMjI1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZhZmJmYyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjAuMjc4NiIgc3RvcC1jb2xvcj0iI2VmZjFmMyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjAuNzY4OCIgc3RvcC1jb2xvcj0iI2QxZDZkZCIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNjMWM3ZDAiLz4KICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgPC9kZWZzPgogIDx0aXRsZT5Eb2N1bWVudCBUYWJsZTwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0yIiB4MT0iMTcuNDcxIiB5MT0iMTcxLjc1NzMiIHgyPSI3OS4yOTgiIHkyPSIxNzEuNzU3MyIvPgogICAgICAgIDxwb2x5Z29uIGlkPSJfUGF0aF8iIGRhdGEtbmFtZT0iJmx0O1BhdGgmZ3Q7IiBjbGFzcz0iY2xzLTMiIHBvaW50cz0iMTYyLjQ0NSAzOC43MTEgMTYyLjQ0NSAyMTAuMTExIDAgMjEwLjExMSAwIDAgMTIzLjcwNCAwIDE2Mi40MTUgMzguNzExIDE2Mi40NDUgMzguNzExIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy00IiB4PSIxOC45MTE3IiB5PSI3OC4xNTQyIiB3aWR0aD0iNDcuODQ4NSIgaGVpZ2h0PSI3OS42NTM3Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy01IiB4PSIxOC45MTE3IiB5PSI1MS42MDMiIHdpZHRoPSIxMjMuNDUyOSIgaGVpZ2h0PSIyNi41NTEyIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy02IiB4PSIxOC45MTE3IiB5PSI1MS42MDMiIHdpZHRoPSIxMjMuNDUyOSIgaGVpZ2h0PSIxMDYuMjA0OSIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNiIgeDE9IjY2Ljc2MDEiIHkxPSI1MS42MDMiIHgyPSI2Ni43NjAxIiB5Mj0iMTU3LjgwNzkiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTYiIHgxPSI5MS4zMjg0IiB5MT0iNTEuNjAzIiB4Mj0iOTEuMzI4NCIgeTI9IjE1Ny44MDc5Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTE1Ljg5NjciIHkxPSI1MS42MDMiIHgyPSIxMTUuODk2NyIgeTI9IjE1Ny44MDc5Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTguOTExNyIgeTE9Ijc4LjE1NDIiIHgyPSIxNDIuMzY0NiIgeTI9Ijc4LjE1NDIiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTYiIHgxPSIxOC45MTE3IiB5MT0iMTA0LjcwNTUiIHgyPSIxNDIuMzY0NiIgeTI9IjEwNC43MDU1Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTguOTExNyIgeTE9IjEzMS4yNTY3IiB4Mj0iMTQyLjM2NDYiIHkyPSIxMzEuMjU2NyIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNyIgcG9pbnRzPSIxNjIuNDQ1IDM4LjcxMSAxNjIuNDE1IDM4LjcxMSAxMjMuODcyIDAuMTY5IDEyMy44NzIgNTkuOTIxIDE2Mi40NDUgMzkuMTM3IDE2Mi40NDUgMzguNzExIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy04IiB4MT0iMTguMzk3MyIgeTE9IjE4MS4zNDQiIHgyPSI3OS4xMTM1IiB5Mj0iMTgxLjM0NCIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIxOTAuOTMwNiIgeDI9IjUxLjMwNDkiIHkyPSIxOTAuOTMwNiIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIxNzEuNzU3MyIgeDI9Ijc5LjExMzUiIHkyPSIxNzEuNzU3MyIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIzNi4xNzA1IiB4Mj0iNzkuMTEzNSIgeTI9IjM2LjE3MDUiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTgiIHgxPSIxOC4zOTczIiB5MT0iMjYuNTgzOCIgeDI9Ijc5LjExMzUiIHkyPSIyNi41ODM4Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy05IiBwb2ludHM9IjE2Mi40NDkgMzguNzQyIDEyMy43MDcgMzguNzQyIDEyMy43MDcgMCAxNjIuNDQ5IDM4Ljc0MiIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K",
"body": "\nBy default, each endpoint returns the full representation of a resource and in\nsome cases that can be a lot of data. For example, retrieving a list of pull\nrequests can amount to quite a large document.\n\nFor better performance, you can ask the server to only return the fields you\nreally need and to omit unwanted data. To request a partial response and to\nadd or remove specific fields from a response, use the `fields` query\nparameter.\n\n\n### Example\n\nMost API resources embed a substantial list of links pointing to related\nresources. This saves the client from constructing its own URLs, but is\nsomewhat wasteful when the client doesn't need them.\n\nTo significantly reduce the size of the response, use `?fields=-links`:\n\n```json\n$ curl https://api.bitbucket.org/2.0/users/evzijst?fields=-links\n{\n \"nickname\": \"evzijst\",\n \"account_status\": \"active\",\n \"website\": \"\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{a288a0ab-e13b-43f0-a689-c4ef0a249875}\",\n \"created_on\": \"2010-07-07T05:16:36+00:00\",\n \"location\": null,\n \"type\": \"user\"\n}\n```\n\n### Fields parameter syntax\n\nThe `fields` parameter supports 3 modes of operation:\n\n1. Removal of select fields (e.g. `-links`)\n2. Pulling in additional fields not normally returned by an endpoint, while\n still getting all the default fields (e.g. `+reviewers`)\n3. Omitting all fields, except those specified (e.g. `owner.display_name`)\n\nThe fields parameter can contain a list of multiple comma-separated field names\n(e.g. `fields=owner.display_name,uuid,links.self.href`). The parameter itself is\nnot repeated.\n\nAs discussed at [Condensed Versus Full Objects](serialization#representations),\nmost objects that are embedded inside other objects (like how `owner` is an\nembedded `user` object in `repository`) appear in \"condensed\" form that omits\nmany fields. The `fields` parameter allows us to pull in additional fields in\nsuch cases.\n\nFor example, the embedded repository object in a pull request does not normally\ncontain its `owner`. To add that in we can use:\n`+values.destination.repository.owner`.\n\n\n### Wildcards\n\nThe asterisk can be used to match all fields on a particular level. For\nexample, removing all entries from the `links` element can be done like this:\n\n```json\n$ curl https://api.bitbucket.org/2.0/users/evzijst?fields=-links.*\n{\n \"nickname\": \"evzijst\",\n \"account_status\": \"active\",\n \"website\": \"\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{a288a0ab-e13b-43f0-a689-c4ef0a249875}\",\n \"links\": {},\n \"created_on\": \"2010-07-07T05:16:36+00:00\",\n \"location\": null,\n \"type\": \"user\"\n}\n```\n\nWildcards can be used in combination with exclusion and inclusion. For\ninstance, `-*,+foo,+bar` will remove all elements from the root level and then\nadd in `foo` and `bar`.\n\n\n### URL encoding\n\nBe aware that when using the `+foo.bar` syntax in the query string, that the\n\"+\" must be URL encoded as \"%2B\" and so the URL will be:\n\n```\nhttps://api.bitbucket.org/2.0/repositories/evzijst/interruptingcow?fields=%2Bowner.created_on\n```\n\nWithout URL escaping, \"+\" is interpreted as an encoded space which will not\nmatch any fields.\n\n\n### Field discovery\n\nWhile a resource's `self` URL, as well its \"collection\" URL typically return\nthe full object with all its fields, there are some exceptions for fields that\nare overly verbose or costly to generate.\n\nFor instance, a pull request contains the embedded lists of reviewers and\nparticipants. These fields are included from the `self` URL, but not from the\n`/pullrequests` collections resource, as it would impact performance too much.\n\nTo discover any additional fields that might not be included by default,\n`fields=*` can be used.\n\n\n### More examples\n\nIf we want to get a list of all reviewer nicknames on pull requests I created,\nwe could combine a [filter](filtering) with a partial response. This will omit\nall other data from the response:\n\n```\n/2.0/repositories/bitbucket/bitbucket/pullrequests?fields=values.id,values.reviewers.nickname,values.state&q=author.uuid%3D%22%7Bd301aafa-d676-4ee0-88be-962be7417567%7D%22\n{\n \"values\": [\n {\n \"reviewers\": [\n {\n \"nickname\": \"abhin\"\n },\n {\n \"nickname\": \"dtao\"\n },\n {\n \"nickname\": \"csomme\"\n }\n ],\n \"state\": \"OPEN\",\n \"id\": 11355\n },\n {\n \"reviewers\": [\n {\n \"nickname\": \"csomme\"\n },\n {\n \"nickname\": \"abhin\"\n },\n {\n \"nickname\": \"dstevens\"\n }\n ],\n \"state\": \"MERGED\",\n \"id\": 11347\n },\n {\n \"reviewers\": [\n {\n \"nickname\": \"csomme\"\n },\n {\n \"nickname\": \"jmooring\"\n },\n {\n \"nickname\": \"zdavis\"\n },\n {\n \"nickname\": \"flexbox\"\n }\n ],\n \"state\": \"OPEN\",\n \"id\": 11344\n }\n ]\n}\n```\n"
},
{
"anchor": "serialization",
"title": "Schemas and Serialization",
"description": "Learn more about object representations",
- "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMjAuNzYyNCAyMDUuNTg2Ij4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLmNscy0xIHsKICAgICAgICBpc29sYXRpb246IGlzb2xhdGU7CiAgICAgIH0KCiAgICAgIC5jbHMtMiwgLmNscy02IHsKICAgICAgICBtaXgtYmxlbmQtbW9kZTogbXVsdGlwbHk7CiAgICAgIH0KCiAgICAgIC5jbHMtMTMsIC5jbHMtMywgLmNscy00LCAuY2xzLTYgewogICAgICAgIGZpbGw6IG5vbmU7CiAgICAgICAgc3Ryb2tlOiAjYzFjN2QwOwogICAgICAgIHN0cm9rZS1saW5lY2FwOiByb3VuZDsKICAgICAgICBzdHJva2UtbWl0ZXJsaW1pdDogMTA7CiAgICAgICAgc3Ryb2tlLXdpZHRoOiAycHg7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgc3Ryb2tlLWRhc2hhcnJheTogMy43ODE2IDUuMjk0MzsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjMDA2NWZmOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDMuOTIxOSA1LjQ5MDc7CiAgICAgIH0KCiAgICAgIC5jbHMtNyB7CiAgICAgICAgZmlsbDogIzAwNTJjYzsKICAgICAgfQoKICAgICAgLmNscy04IHsKICAgICAgICBmaWxsOiAjNGM5YWZmOwogICAgICB9CgogICAgICAuY2xzLTkgewogICAgICAgIGZpbGw6ICMwMDQ5YjA7CiAgICAgIH0KCiAgICAgIC5jbHMtMTAgewogICAgICAgIGZpbGw6ICM1N2Q5YTM7CiAgICAgIH0KCiAgICAgIC5jbHMtMTEgewogICAgICAgIGZpbGw6ICM3OWYyYzA7CiAgICAgIH0KCiAgICAgIC5jbHMtMTIgewogICAgICAgIGZpbGw6ICMzNmIzN2U7CiAgICAgIH0KCiAgICAgIC5jbHMtMTMgewogICAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDMuODY4MSA1LjQxNTQ7CiAgICAgIH0KCiAgICAgIC5jbHMtMTQgewogICAgICAgIGZpbGw6ICM0MjUyNmU7CiAgICAgIH0KCiAgICAgIC5jbHMtMTUgewogICAgICAgIGZpbGw6ICMzNDQ1NjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMTYgewogICAgICAgIGZpbGw6ICM1MDVmNzk7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDx0aXRsZT5JbnRlZ3JhdGlvbnM8L3RpdGxlPgogIDxnIGNsYXNzPSJjbHMtMSI+CiAgICA8ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj4KICAgICAgPGcgaWQ9Ik9iamVjdHMiPgogICAgICAgIDxnIGNsYXNzPSJjbHMtMiI+CiAgICAgICAgICA8Zz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iNzUuMjExNCIgeTE9IjE4Ny44NTczIiB4Mj0iNzcuMDI0OCIgeTI9IjE4Ny4xMTA5Ii8+CiAgICAgICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNCIgeDE9IjgxLjkyMDUiIHkxPSIxODUuMDk1NiIgeDI9IjEzOC4yMjEyIiB5Mj0iMTYxLjkyMDMiLz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMTQwLjY2OSIgeTE9IjE2MC45MTI2IiB4Mj0iMTQyLjQ4MjQiIHkyPSIxNjAuMTY2MiIvPgogICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTUiIHBvaW50cz0iMTk0LjUyNiAyNi41NTIgMTc2LjkwMSAzOC4yNDEgMTU5LjI3IDI2LjU1MiAxNzYuOTAxIDE0Ljg3IDE5NC41MjYgMjYuNTUyIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTgzLjcxMzUiIHkxPSI0My4yMTg4IiB4Mj0iMTgzLjcxMzUiIHkyPSI5Ny44ODMyIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy03IiBwb2ludHM9IjE3Ni45MDEgMzguMjQxIDE3Ni45MDEgNTguMTY2IDE1OS4yNyA0Ni40NzcgMTU5LjI3IDI2LjU1MiAxNzYuOTAxIDM4LjI0MSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtOCIgcG9pbnRzPSIxOTQuNTI2IDI2LjU1MiAxOTQuNTI2IDQ2LjQ3NyAxNzYuOTAxIDU4LjE2NiAxNzYuOTAxIDM4LjI0MSAxOTQuNTI2IDI2LjU1MiIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNiIgeDE9IjQ3Ljk0ODgiIHkxPSI0Mi4yMTg4IiB4Mj0iMTU5LjExNzIiIHkyPSI0Mi4yMTg4Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy01IiBwb2ludHM9IjIyMC43NjIgOTkuNzUyIDE2Ny44MTcgMTM0Ljg2NCAxMTQuODU0IDk5Ljc1MiAxNjcuODE3IDY0LjY1NyAyMjAuNzYyIDk5Ljc1MiIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtOSIgcG9pbnRzPSIxNjcuODE3IDEzNC44NjQgMTY3LjgxNyAxOTQuNzE4IDExNC44NTQgMTU5LjYwNiAxMTQuODU0IDk5Ljc1MiAxNjcuODE3IDEzNC44NjQiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTgiIHBvaW50cz0iMjIwLjc2MiA5OS43NTIgMjIwLjc2MiAxNTkuNjA2IDE2Ny44MTcgMTk0LjcxOCAxNjcuODE3IDEzNC44NjQgMjIwLjc2MiA5OS43NTIiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTEwIiBwb2ludHM9IjExMC41NDEgMjEuNjA0IDc3Ljk0OSA0My4yMTkgNDUuMzQ1IDIxLjYwNCA3Ny45NDkgMCAxMTAuNTQxIDIxLjYwNCIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTEiIHBvaW50cz0iMTEwLjU0MSAyMS42MDQgMTEwLjU0MSA1OC40NDkgNzcuOTQ5IDgwLjA2NCA3Ny45NDkgNDMuMjE5IDExMC41NDEgMjEuNjA0Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy01IiBwb2ludHM9IjE0MS4xOSAxNDguMDczIDE2Ny44MTMgMTMwLjQxNyAxOTQuNDQ0IDE0OC4wNzMgMTY3LjgxMyAxNjUuNzE5IDE0MS4xOSAxNDguMDczIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy05IiBwb2ludHM9IjE2Ny44MTMgMTMwLjQxNyAxNjcuODEzIDEwMC4zMjEgMTk0LjQ0NCAxMTcuOTc2IDE5NC40NDQgMTQ4LjA3MyAxNjcuODEzIDEzMC40MTciLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTgiIHBvaW50cz0iMTQxLjE5IDE0OC4wNzMgMTQxLjE5IDExNy45NzYgMTY3LjgxMyAxMDAuMzIxIDE2Ny44MTMgMTMwLjQxNyAxNDEuMTkgMTQ4LjA3MyIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTIiIHBvaW50cz0iNDUuMzQ1IDIxLjYwNCA0NS4zNDUgNDQuOTg0IDU3LjIzMSA1Mi44NjQgNTcuMjMxIDY2LjI5NiA3Ny45NDkgODAuMDY0IDc3Ljk0OSA0My4yMTkgNDUuMzQ1IDIxLjYwNCIvPgogICAgICAgIDxnIGNsYXNzPSJjbHMtMiI+CiAgICAgICAgICA8Zz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjQuNjQzOCIgeTE9Ijg2Ljk1NDQiIHgyPSIyNi4wMTU3IiB5Mj0iODUuNTUzMSIvPgogICAgICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTEzIiB4MT0iMjkuODA0IiB5MT0iODEuNjgzNCIgeDI9IjYwLjM4MTEiIHkyPSI1MC40NDkyIi8+CiAgICAgICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjYyLjI3NTIiIHkxPSI0OC41MTQzIiB4Mj0iNjMuNjQ3IiB5Mj0iNDcuMTEzIi8+CiAgICAgICAgICA8L2c+CiAgICAgICAgPC9nPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNSIgcG9pbnRzPSIzNS4yNTUgODkuNjQ1IDE3LjczNiAxMDEuNDkyIDAgODkuOTYyIDE3LjUyNSA3OC4xMjEgMzUuMjU1IDg5LjY0NSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNyIgcG9pbnRzPSIxNy43MzYgMTAxLjQ5MiAxNy45MTUgMTIxLjQxNiAwLjE3OSAxMDkuODg3IDAgODkuOTYyIDE3LjczNiAxMDEuNDkyIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMjAuNTg0OSIgeTE9IjEwNS41MzA1IiB4Mj0iNjUuODc0OSIgeTI9IjE3MS4zNjgxIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy04IiBwb2ludHM9IjM1LjI1NSA4OS42NDUgMzUuNDM0IDEwOS41NjkgMTcuOTE1IDEyMS40MTYgMTcuNzM2IDEwMS40OTIgMzUuMjU1IDg5LjY0NSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTQiIHBvaW50cz0iOTIuMzk0IDE3My44MTUgNzQuODc1IDE4NS42NjIgNTcuMTM5IDE3NC4xMzIgNzQuNjY0IDE2Mi4yOTEgOTIuMzk0IDE3My44MTUiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTE1IiBwb2ludHM9Ijc0Ljg3NSAxODUuNjYyIDc1LjA1NCAyMDUuNTg2IDU3LjMxOSAxOTQuMDU3IDU3LjEzOSAxNzQuMTMyIDc0Ljg3NSAxODUuNjYyIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy0xNiIgcG9pbnRzPSI5Mi4zOTQgMTczLjgxNSA5Mi41NzQgMTkzLjczOSA3NS4wNTQgMjA1LjU4NiA3NC44NzUgMTg1LjY2MiA5Mi4zOTQgMTczLjgxNSIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K'",
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMjAuNzYyNCAyMDUuNTg2Ij4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLmNscy0xIHsKICAgICAgICBpc29sYXRpb246IGlzb2xhdGU7CiAgICAgIH0KCiAgICAgIC5jbHMtMiwgLmNscy02IHsKICAgICAgICBtaXgtYmxlbmQtbW9kZTogbXVsdGlwbHk7CiAgICAgIH0KCiAgICAgIC5jbHMtMTMsIC5jbHMtMywgLmNscy00LCAuY2xzLTYgewogICAgICAgIGZpbGw6IG5vbmU7CiAgICAgICAgc3Ryb2tlOiAjYzFjN2QwOwogICAgICAgIHN0cm9rZS1saW5lY2FwOiByb3VuZDsKICAgICAgICBzdHJva2UtbWl0ZXJsaW1pdDogMTA7CiAgICAgICAgc3Ryb2tlLXdpZHRoOiAycHg7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgc3Ryb2tlLWRhc2hhcnJheTogMy43ODE2IDUuMjk0MzsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjMDA2NWZmOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDMuOTIxOSA1LjQ5MDc7CiAgICAgIH0KCiAgICAgIC5jbHMtNyB7CiAgICAgICAgZmlsbDogIzAwNTJjYzsKICAgICAgfQoKICAgICAgLmNscy04IHsKICAgICAgICBmaWxsOiAjNGM5YWZmOwogICAgICB9CgogICAgICAuY2xzLTkgewogICAgICAgIGZpbGw6ICMwMDQ5YjA7CiAgICAgIH0KCiAgICAgIC5jbHMtMTAgewogICAgICAgIGZpbGw6ICM1N2Q5YTM7CiAgICAgIH0KCiAgICAgIC5jbHMtMTEgewogICAgICAgIGZpbGw6ICM3OWYyYzA7CiAgICAgIH0KCiAgICAgIC5jbHMtMTIgewogICAgICAgIGZpbGw6ICMzNmIzN2U7CiAgICAgIH0KCiAgICAgIC5jbHMtMTMgewogICAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDMuODY4MSA1LjQxNTQ7CiAgICAgIH0KCiAgICAgIC5jbHMtMTQgewogICAgICAgIGZpbGw6ICM0MjUyNmU7CiAgICAgIH0KCiAgICAgIC5jbHMtMTUgewogICAgICAgIGZpbGw6ICMzNDQ1NjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMTYgewogICAgICAgIGZpbGw6ICM1MDVmNzk7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDx0aXRsZT5JbnRlZ3JhdGlvbnM8L3RpdGxlPgogIDxnIGNsYXNzPSJjbHMtMSI+CiAgICA8ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj4KICAgICAgPGcgaWQ9Ik9iamVjdHMiPgogICAgICAgIDxnIGNsYXNzPSJjbHMtMiI+CiAgICAgICAgICA8Zz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iNzUuMjExNCIgeTE9IjE4Ny44NTczIiB4Mj0iNzcuMDI0OCIgeTI9IjE4Ny4xMTA5Ii8+CiAgICAgICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNCIgeDE9IjgxLjkyMDUiIHkxPSIxODUuMDk1NiIgeDI9IjEzOC4yMjEyIiB5Mj0iMTYxLjkyMDMiLz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMTQwLjY2OSIgeTE9IjE2MC45MTI2IiB4Mj0iMTQyLjQ4MjQiIHkyPSIxNjAuMTY2MiIvPgogICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTUiIHBvaW50cz0iMTk0LjUyNiAyNi41NTIgMTc2LjkwMSAzOC4yNDEgMTU5LjI3IDI2LjU1MiAxNzYuOTAxIDE0Ljg3IDE5NC41MjYgMjYuNTUyIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTgzLjcxMzUiIHkxPSI0My4yMTg4IiB4Mj0iMTgzLjcxMzUiIHkyPSI5Ny44ODMyIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy03IiBwb2ludHM9IjE3Ni45MDEgMzguMjQxIDE3Ni45MDEgNTguMTY2IDE1OS4yNyA0Ni40NzcgMTU5LjI3IDI2LjU1MiAxNzYuOTAxIDM4LjI0MSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtOCIgcG9pbnRzPSIxOTQuNTI2IDI2LjU1MiAxOTQuNTI2IDQ2LjQ3NyAxNzYuOTAxIDU4LjE2NiAxNzYuOTAxIDM4LjI0MSAxOTQuNTI2IDI2LjU1MiIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNiIgeDE9IjQ3Ljk0ODgiIHkxPSI0Mi4yMTg4IiB4Mj0iMTU5LjExNzIiIHkyPSI0Mi4yMTg4Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy01IiBwb2ludHM9IjIyMC43NjIgOTkuNzUyIDE2Ny44MTcgMTM0Ljg2NCAxMTQuODU0IDk5Ljc1MiAxNjcuODE3IDY0LjY1NyAyMjAuNzYyIDk5Ljc1MiIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtOSIgcG9pbnRzPSIxNjcuODE3IDEzNC44NjQgMTY3LjgxNyAxOTQuNzE4IDExNC44NTQgMTU5LjYwNiAxMTQuODU0IDk5Ljc1MiAxNjcuODE3IDEzNC44NjQiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTgiIHBvaW50cz0iMjIwLjc2MiA5OS43NTIgMjIwLjc2MiAxNTkuNjA2IDE2Ny44MTcgMTk0LjcxOCAxNjcuODE3IDEzNC44NjQgMjIwLjc2MiA5OS43NTIiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTEwIiBwb2ludHM9IjExMC41NDEgMjEuNjA0IDc3Ljk0OSA0My4yMTkgNDUuMzQ1IDIxLjYwNCA3Ny45NDkgMCAxMTAuNTQxIDIxLjYwNCIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTEiIHBvaW50cz0iMTEwLjU0MSAyMS42MDQgMTEwLjU0MSA1OC40NDkgNzcuOTQ5IDgwLjA2NCA3Ny45NDkgNDMuMjE5IDExMC41NDEgMjEuNjA0Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy01IiBwb2ludHM9IjE0MS4xOSAxNDguMDczIDE2Ny44MTMgMTMwLjQxNyAxOTQuNDQ0IDE0OC4wNzMgMTY3LjgxMyAxNjUuNzE5IDE0MS4xOSAxNDguMDczIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy05IiBwb2ludHM9IjE2Ny44MTMgMTMwLjQxNyAxNjcuODEzIDEwMC4zMjEgMTk0LjQ0NCAxMTcuOTc2IDE5NC40NDQgMTQ4LjA3MyAxNjcuODEzIDEzMC40MTciLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTgiIHBvaW50cz0iMTQxLjE5IDE0OC4wNzMgMTQxLjE5IDExNy45NzYgMTY3LjgxMyAxMDAuMzIxIDE2Ny44MTMgMTMwLjQxNyAxNDEuMTkgMTQ4LjA3MyIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTIiIHBvaW50cz0iNDUuMzQ1IDIxLjYwNCA0NS4zNDUgNDQuOTg0IDU3LjIzMSA1Mi44NjQgNTcuMjMxIDY2LjI5NiA3Ny45NDkgODAuMDY0IDc3Ljk0OSA0My4yMTkgNDUuMzQ1IDIxLjYwNCIvPgogICAgICAgIDxnIGNsYXNzPSJjbHMtMiI+CiAgICAgICAgICA8Zz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjQuNjQzOCIgeTE9Ijg2Ljk1NDQiIHgyPSIyNi4wMTU3IiB5Mj0iODUuNTUzMSIvPgogICAgICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTEzIiB4MT0iMjkuODA0IiB5MT0iODEuNjgzNCIgeDI9IjYwLjM4MTEiIHkyPSI1MC40NDkyIi8+CiAgICAgICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjYyLjI3NTIiIHkxPSI0OC41MTQzIiB4Mj0iNjMuNjQ3IiB5Mj0iNDcuMTEzIi8+CiAgICAgICAgICA8L2c+CiAgICAgICAgPC9nPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNSIgcG9pbnRzPSIzNS4yNTUgODkuNjQ1IDE3LjczNiAxMDEuNDkyIDAgODkuOTYyIDE3LjUyNSA3OC4xMjEgMzUuMjU1IDg5LjY0NSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNyIgcG9pbnRzPSIxNy43MzYgMTAxLjQ5MiAxNy45MTUgMTIxLjQxNiAwLjE3OSAxMDkuODg3IDAgODkuOTYyIDE3LjczNiAxMDEuNDkyIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMjAuNTg0OSIgeTE9IjEwNS41MzA1IiB4Mj0iNjUuODc0OSIgeTI9IjE3MS4zNjgxIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy04IiBwb2ludHM9IjM1LjI1NSA4OS42NDUgMzUuNDM0IDEwOS41NjkgMTcuOTE1IDEyMS40MTYgMTcuNzM2IDEwMS40OTIgMzUuMjU1IDg5LjY0NSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTQiIHBvaW50cz0iOTIuMzk0IDE3My44MTUgNzQuODc1IDE4NS42NjIgNTcuMTM5IDE3NC4xMzIgNzQuNjY0IDE2Mi4yOTEgOTIuMzk0IDE3My44MTUiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTE1IiBwb2ludHM9Ijc0Ljg3NSAxODUuNjYyIDc1LjA1NCAyMDUuNTg2IDU3LjMxOSAxOTQuMDU3IDU3LjEzOSAxNzQuMTMyIDc0Ljg3NSAxODUuNjYyIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy0xNiIgcG9pbnRzPSI5Mi4zOTQgMTczLjgxNSA5Mi41NzQgMTkzLjczOSA3NS4wNTQgMjA1LjU4NiA3NC44NzUgMTg1LjY2MiA5Mi4zOTQgMTczLjgxNSIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K",
"body": "\n----\n\n* [Open API Specification](#open-api-specification)\n* [JSON Schema](#json-schema)\n* [Condensed Versus Full Objects](#condensed-versus-full-objects)\n\n____\n\n\n### Open API Specification\n\nBitbucket uses the [Open API Specification](https://openapis.org) (OAI,\nformerly known as Swagger) to describe its APIs. Our OAI specification schema\nis hosted at [https://api.bitbucket.org/swagger.json](https://api.bitbucket.org/swagger.json)\nand serves as the canonical definition and comprehensive declaration of all\navailable endpoints.\n\nThe OAI specification makes writing client applications easier by:\nauto-generating boilerplate code (like data object classes) and dealing with\nauthentication and error handling.\n\nYou can find a comprehensive set of open tools for the OAI specification at:\n[https://github.com/swagger-api](https://github.com/swagger-api).\n\n\n### JSON Schema\n\nBitbucket uses JSON Schema to describe the layout of every type of object\nconsumed or produced by the API. These schemas are collected under the\n`#definitions` element of our swagger.json file.\n\nWhen an endpoint expects an object as part of a POST or PUT, it also expects\nthe object to validate against the JSON schemas. The same applies to objects\nreturned by an endpoint.\n\n\n### Condensed Versus Full Objects\n\nMost objects in Bitbucket come both in \"full\" and \"partial\" representation.\nThe full representation is when all elements are included. This is the layout\nreturned by a resource's `self` location (e.g. `/2.0/repositories/foo/bar`),\nas well as resource collection endpoints (e.g. `/2.0/repositories`).\n\nHowever, Bitbucket objects often embed other objects. For example, a `repository`\nobject embeds a `user` object for its owner. Likewise, a `pullrequest` object\nembeds its `repository` object.\n\nThese related objects are embedded, or inlined, to reduce the \"chatter\" when\nclients make frequent followup API calls to collect information on common,\nrelated information.\n\nEmbedded related objects are typically limited in their fields to avoid such\nobject graphs from becoming too deep and noisy. They often exclude their own\nnested objects in an attempt to strike a balance between performance and\nutility.\n\nAn object's embedded or condensed representation tends to be standardized,\nmeaning the fields included is the same set, regardless of where the object\nwas embedded.\n"
},
{
"anchor": "uri-uuid",
"title": "URI, UUID, and structures",
"description": "URL's, UUID's, errors, and timestamps",
- "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTc5LjI2IDE3Ny42NSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50KTt9LmNscy0ye2ZpbGw6IzA5MWU0Mjt9LmNscy0xMCwuY2xzLTExLC5jbHMtMywuY2xzLTQsLmNscy05e2ZpbGw6bm9uZTt9LmNscy0ze3N0cm9rZTojOTljMWZmO30uY2xzLTMsLmNscy00e3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MDt9LmNscy0xMiwuY2xzLTQsLmNscy05e3N0cm9rZTojZTVlOGVjO30uY2xzLTV7ZmlsbDojMzQ0NTYzO30uY2xzLTZ7ZmlsbDojZmY4YjAwO30uY2xzLTd7ZmlsbDojZmZjNDAwO30uY2xzLTh7ZmlsbDojMDA2NWZmO30uY2xzLTEwLC5jbHMtMTEsLmNscy0xMiwuY2xzLTEzLC5jbHMtOXtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2Utd2lkdGg6MnB4O30uY2xzLTEwe3N0cm9rZTojZmZhYjAwO30uY2xzLTExLC5jbHMtMTN7c3Ryb2tlOiMwMDY1ZmY7fS5jbHMtMTJ7ZmlsbDojOTljMWZmO30uY2xzLTEze2ZpbGw6I2U1ZThlYzt9PC9zdHlsZT48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudCIgeDE9IjAuNCIgeTE9IjE3OC4wNSIgeDI9IjE3OC44NSIgeTI9Ii0wLjQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMwOTFlNDIiLz48c3RvcCBvZmZzZXQ9IjAuMDciIHN0b3AtY29sb3I9IiMwZDIyNDUiLz48c3RvcCBvZmZzZXQ9IjAuNDkiIHN0b3AtY29sb3I9IiMxZjMyNTMiLz48c3RvcCBvZmZzZXQ9IjAuNzkiIHN0b3AtY29sb3I9IiMyNTM4NTgiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48dGl0bGU+Q29kZTwvdGl0bGU+PGcgaWQ9IkxheWVyXzIiIGRhdGEtbmFtZT0iTGF5ZXIgMiI+PGcgaWQ9IlNvZnR3YXJlIj48cmVjdCBpZD0iX1JlY3RhbmdsZV8iIGRhdGEtbmFtZT0iJmx0O1JlY3RhbmdsZSZndDsiIGNsYXNzPSJjbHMtMSIgd2lkdGg9IjE3OS4yNiIgaGVpZ2h0PSIxNzcuNjUiLz48cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xNzkuMjYsMjYuNjRIMFY3MS43NUExNjYuNDEsMTY2LjQxLDAsMCwwLDYzLjI0LDU5LjUxYTE4OC40MSwxODguNDEsMCwwLDAsMTcuMzktOC4zNmMxOC40NC05LjQzLDQ4LjM3LTE3LjksOTguNjItMTNabS0xNTkuNDQsMzRoMFptMC0xNC4wOGgwWiIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjE5LjgxIiB5MT0iNDYuNTgiIHgyPSIyNS4wNyIgeTI9IjQ2LjU4Ii8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSI2MC42NiIgeDI9IjE5LjgxIiB5Mj0iNjAuNjYiLz48bGluZSBjbGFzcz0iY2xzLTMiIHgxPSIxOS44MSIgeTE9Ijc0Ljc0IiB4Mj0iMjUuMDciIHkyPSI3NC43NCIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iODguODIiIHgyPSIxOS44MSIgeTI9Ijg4LjgyIi8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSIxMDIuODkiIHgyPSIxOS44MSIgeTI9IjEwMi44OSIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iMTE2Ljk3IiB4Mj0iMTkuODEiIHkyPSIxMTYuOTciLz48bGluZSBjbGFzcz0iY2xzLTMiIHgxPSIyNS4wNyIgeTE9IjEzMS4wNSIgeDI9IjE5LjgxIiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSIxNDUuMTMiIHgyPSIxOS44MSIgeTI9IjE0NS4xMyIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iMTU5LjIxIiB4Mj0iMTkuODEiIHkyPSIxNTkuMjEiLz48bGluZSBjbGFzcz0iY2xzLTQiIHgxPSI1NS44OSIgeTE9IjEzMS4wNSIgeDI9IjkzLjk5IiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy00IiB4MT0iNTUuODkiIHkxPSIxNDUuMTMiIHgyPSIxMzkuNjciIHkyPSIxNDUuMTMiLz48cmVjdCBjbGFzcz0iY2xzLTUiIHdpZHRoPSIxNzkuMjYiIGhlaWdodD0iMjYuNjQiLz48Y2lyY2xlIGNsYXNzPSJjbHMtNiIgY3g9IjEzLjUiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxjaXJjbGUgY2xhc3M9ImNscy03IiBjeD0iMzAuMTgiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxjaXJjbGUgY2xhc3M9ImNscy04IiBjeD0iNDYuODYiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTc1LjQxLDg4LjgyIi8+PHBhdGggY2xhc3M9ImNscy05IiBkPSJNMzIuODksODguODIiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iMzIuODkiIHkxPSI3NC43NCIgeDI9Ijc1LjQxIiB5Mj0iNzQuNzQiLz48bGluZSBjbGFzcz0iY2xzLTExIiB4MT0iMzIuODkiIHkxPSI2MC42NiIgeDI9Ijc1LjQxIiB5Mj0iNjAuNjYiLz48bGluZSBjbGFzcz0iY2xzLTkiIHgxPSIzMi44OSIgeTE9IjQ2LjU4IiB4Mj0iNTUuODkiIHkyPSI0Ni41OCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjQ2LjU4IiB4Mj0iMjUuMDciIHkyPSI0Ni41OCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjYwLjY2IiB4Mj0iMjUuMDciIHkyPSI2MC42NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9Ijc0Ljc0IiB4Mj0iMjUuMDciIHkyPSI3NC43NCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9Ijg4LjgyIiB4Mj0iMjUuMDciIHkyPSI4OC44MiIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjEwMi44OSIgeDI9IjI1LjA3IiB5Mj0iMTAyLjg5Ii8+PGxpbmUgY2xhc3M9ImNscy0xMiIgeDE9IjE5LjgxIiB5MT0iMTE2Ljk3IiB4Mj0iMjUuMDciIHkyPSIxMTYuOTciLz48bGluZSBjbGFzcz0iY2xzLTEyIiB4MT0iMTkuODEiIHkxPSIxMzEuMDUiIHgyPSIyNS4wNyIgeTI9IjEzMS4wNSIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjE0NS4xMyIgeDI9IjI1LjA3IiB5Mj0iMTQ1LjEzIi8+PGxpbmUgY2xhc3M9ImNscy0xMiIgeDE9IjE5LjgxIiB5MT0iMTU5LjIxIiB4Mj0iMjUuMDciIHkyPSIxNTkuMjEiLz48bGluZSBpZD0iX0xpbmVfIiBkYXRhLW5hbWU9IiZsdDtMaW5lJmd0OyIgY2xhc3M9ImNscy0xMCIgeDE9Ijg0LjI0IiB5MT0iMTE2Ljk3IiB4Mj0iMTU2LjY1IiB5Mj0iMTE2Ljk3Ii8+PHBhdGggY2xhc3M9ImNscy05IiBkPSJNMzIuODksMTE3aDBaIi8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjEwMiIgeTE9IjEzMS4wNSIgeDI9IjE2My42MiIgeTI9IjEzMS4wNSIvPjxsaW5lIGNsYXNzPSJjbHMtMTMiIHgxPSI1NS44OSIgeTE9IjEzMS4wNSIgeDI9IjkzLjk5IiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy0xMyIgeDE9IjU1Ljg5IiB5MT0iMTQ1LjEzIiB4Mj0iMTM5LjY3IiB5Mj0iMTQ1LjEzIi8+PGxpbmUgY2xhc3M9ImNscy05IiB4MT0iNzguOSIgeTE9IjE1OS4yMSIgeDI9IjExMy41MSIgeTI9IjE1OS4yMSIvPjxsaW5lIGNsYXNzPSJjbHMtOSIgeDE9IjU5LjYxIiB5MT0iODguODIiIHgyPSI5OS4zMyIgeTI9Ijg4LjgyIi8+PGxpbmUgY2xhc3M9ImNscy05IiB4MT0iNTkuNjEiIHkxPSIxMDIuODkiIHgyPSI5OS4zMyIgeTI9IjEwMi44OSIvPjxjaXJjbGUgY2xhc3M9ImNscy02IiBjeD0iMTMuNSIgY3k9IjEyLjA4IiByPSI1LjExIi8+PGNpcmNsZSBjbGFzcz0iY2xzLTciIGN4PSIzMC4xOCIgY3k9IjEyLjA4IiByPSI1LjExIi8+PGNpcmNsZSBjbGFzcz0iY2xzLTgiIGN4PSI0Ni44NiIgY3k9IjEyLjA4IiByPSI1LjExIi8+PC9nPjwvZz48L3N2Zz4='",
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTc5LjI2IDE3Ny42NSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50KTt9LmNscy0ye2ZpbGw6IzA5MWU0Mjt9LmNscy0xMCwuY2xzLTExLC5jbHMtMywuY2xzLTQsLmNscy05e2ZpbGw6bm9uZTt9LmNscy0ze3N0cm9rZTojOTljMWZmO30uY2xzLTMsLmNscy00e3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MDt9LmNscy0xMiwuY2xzLTQsLmNscy05e3N0cm9rZTojZTVlOGVjO30uY2xzLTV7ZmlsbDojMzQ0NTYzO30uY2xzLTZ7ZmlsbDojZmY4YjAwO30uY2xzLTd7ZmlsbDojZmZjNDAwO30uY2xzLTh7ZmlsbDojMDA2NWZmO30uY2xzLTEwLC5jbHMtMTEsLmNscy0xMiwuY2xzLTEzLC5jbHMtOXtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2Utd2lkdGg6MnB4O30uY2xzLTEwe3N0cm9rZTojZmZhYjAwO30uY2xzLTExLC5jbHMtMTN7c3Ryb2tlOiMwMDY1ZmY7fS5jbHMtMTJ7ZmlsbDojOTljMWZmO30uY2xzLTEze2ZpbGw6I2U1ZThlYzt9PC9zdHlsZT48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudCIgeDE9IjAuNCIgeTE9IjE3OC4wNSIgeDI9IjE3OC44NSIgeTI9Ii0wLjQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMwOTFlNDIiLz48c3RvcCBvZmZzZXQ9IjAuMDciIHN0b3AtY29sb3I9IiMwZDIyNDUiLz48c3RvcCBvZmZzZXQ9IjAuNDkiIHN0b3AtY29sb3I9IiMxZjMyNTMiLz48c3RvcCBvZmZzZXQ9IjAuNzkiIHN0b3AtY29sb3I9IiMyNTM4NTgiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48dGl0bGU+Q29kZTwvdGl0bGU+PGcgaWQ9IkxheWVyXzIiIGRhdGEtbmFtZT0iTGF5ZXIgMiI+PGcgaWQ9IlNvZnR3YXJlIj48cmVjdCBpZD0iX1JlY3RhbmdsZV8iIGRhdGEtbmFtZT0iJmx0O1JlY3RhbmdsZSZndDsiIGNsYXNzPSJjbHMtMSIgd2lkdGg9IjE3OS4yNiIgaGVpZ2h0PSIxNzcuNjUiLz48cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xNzkuMjYsMjYuNjRIMFY3MS43NUExNjYuNDEsMTY2LjQxLDAsMCwwLDYzLjI0LDU5LjUxYTE4OC40MSwxODguNDEsMCwwLDAsMTcuMzktOC4zNmMxOC40NC05LjQzLDQ4LjM3LTE3LjksOTguNjItMTNabS0xNTkuNDQsMzRoMFptMC0xNC4wOGgwWiIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjE5LjgxIiB5MT0iNDYuNTgiIHgyPSIyNS4wNyIgeTI9IjQ2LjU4Ii8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSI2MC42NiIgeDI9IjE5LjgxIiB5Mj0iNjAuNjYiLz48bGluZSBjbGFzcz0iY2xzLTMiIHgxPSIxOS44MSIgeTE9Ijc0Ljc0IiB4Mj0iMjUuMDciIHkyPSI3NC43NCIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iODguODIiIHgyPSIxOS44MSIgeTI9Ijg4LjgyIi8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSIxMDIuODkiIHgyPSIxOS44MSIgeTI9IjEwMi44OSIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iMTE2Ljk3IiB4Mj0iMTkuODEiIHkyPSIxMTYuOTciLz48bGluZSBjbGFzcz0iY2xzLTMiIHgxPSIyNS4wNyIgeTE9IjEzMS4wNSIgeDI9IjE5LjgxIiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSIxNDUuMTMiIHgyPSIxOS44MSIgeTI9IjE0NS4xMyIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iMTU5LjIxIiB4Mj0iMTkuODEiIHkyPSIxNTkuMjEiLz48bGluZSBjbGFzcz0iY2xzLTQiIHgxPSI1NS44OSIgeTE9IjEzMS4wNSIgeDI9IjkzLjk5IiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy00IiB4MT0iNTUuODkiIHkxPSIxNDUuMTMiIHgyPSIxMzkuNjciIHkyPSIxNDUuMTMiLz48cmVjdCBjbGFzcz0iY2xzLTUiIHdpZHRoPSIxNzkuMjYiIGhlaWdodD0iMjYuNjQiLz48Y2lyY2xlIGNsYXNzPSJjbHMtNiIgY3g9IjEzLjUiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxjaXJjbGUgY2xhc3M9ImNscy03IiBjeD0iMzAuMTgiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxjaXJjbGUgY2xhc3M9ImNscy04IiBjeD0iNDYuODYiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTc1LjQxLDg4LjgyIi8+PHBhdGggY2xhc3M9ImNscy05IiBkPSJNMzIuODksODguODIiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iMzIuODkiIHkxPSI3NC43NCIgeDI9Ijc1LjQxIiB5Mj0iNzQuNzQiLz48bGluZSBjbGFzcz0iY2xzLTExIiB4MT0iMzIuODkiIHkxPSI2MC42NiIgeDI9Ijc1LjQxIiB5Mj0iNjAuNjYiLz48bGluZSBjbGFzcz0iY2xzLTkiIHgxPSIzMi44OSIgeTE9IjQ2LjU4IiB4Mj0iNTUuODkiIHkyPSI0Ni41OCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjQ2LjU4IiB4Mj0iMjUuMDciIHkyPSI0Ni41OCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjYwLjY2IiB4Mj0iMjUuMDciIHkyPSI2MC42NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9Ijc0Ljc0IiB4Mj0iMjUuMDciIHkyPSI3NC43NCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9Ijg4LjgyIiB4Mj0iMjUuMDciIHkyPSI4OC44MiIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjEwMi44OSIgeDI9IjI1LjA3IiB5Mj0iMTAyLjg5Ii8+PGxpbmUgY2xhc3M9ImNscy0xMiIgeDE9IjE5LjgxIiB5MT0iMTE2Ljk3IiB4Mj0iMjUuMDciIHkyPSIxMTYuOTciLz48bGluZSBjbGFzcz0iY2xzLTEyIiB4MT0iMTkuODEiIHkxPSIxMzEuMDUiIHgyPSIyNS4wNyIgeTI9IjEzMS4wNSIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjE0NS4xMyIgeDI9IjI1LjA3IiB5Mj0iMTQ1LjEzIi8+PGxpbmUgY2xhc3M9ImNscy0xMiIgeDE9IjE5LjgxIiB5MT0iMTU5LjIxIiB4Mj0iMjUuMDciIHkyPSIxNTkuMjEiLz48bGluZSBpZD0iX0xpbmVfIiBkYXRhLW5hbWU9IiZsdDtMaW5lJmd0OyIgY2xhc3M9ImNscy0xMCIgeDE9Ijg0LjI0IiB5MT0iMTE2Ljk3IiB4Mj0iMTU2LjY1IiB5Mj0iMTE2Ljk3Ii8+PHBhdGggY2xhc3M9ImNscy05IiBkPSJNMzIuODksMTE3aDBaIi8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjEwMiIgeTE9IjEzMS4wNSIgeDI9IjE2My42MiIgeTI9IjEzMS4wNSIvPjxsaW5lIGNsYXNzPSJjbHMtMTMiIHgxPSI1NS44OSIgeTE9IjEzMS4wNSIgeDI9IjkzLjk5IiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy0xMyIgeDE9IjU1Ljg5IiB5MT0iMTQ1LjEzIiB4Mj0iMTM5LjY3IiB5Mj0iMTQ1LjEzIi8+PGxpbmUgY2xhc3M9ImNscy05IiB4MT0iNzguOSIgeTE9IjE1OS4yMSIgeDI9IjExMy41MSIgeTI9IjE1OS4yMSIvPjxsaW5lIGNsYXNzPSJjbHMtOSIgeDE9IjU5LjYxIiB5MT0iODguODIiIHgyPSI5OS4zMyIgeTI9Ijg4LjgyIi8+PGxpbmUgY2xhc3M9ImNscy05IiB4MT0iNTkuNjEiIHkxPSIxMDIuODkiIHgyPSI5OS4zMyIgeTI9IjEwMi44OSIvPjxjaXJjbGUgY2xhc3M9ImNscy02IiBjeD0iMTMuNSIgY3k9IjEyLjA4IiByPSI1LjExIi8+PGNpcmNsZSBjbGFzcz0iY2xzLTciIGN4PSIzMC4xOCIgY3k9IjEyLjA4IiByPSI1LjExIi8+PGNpcmNsZSBjbGFzcz0iY2xzLTgiIGN4PSI0Ni44NiIgY3k9IjEyLjA4IiByPSI1LjExIi8+PC9nPjwvZz48L3N2Zz4=",
"body": "\nYou should be familiar with REST architecture before writing an integration. Read this overview page to gain a good understanding of Bitbucket's REST implementation.\n\n----\n\n* [URI structure](#uri-structure)\n* [HTTP methods](#http-methods)\n* [UUID](#universally-unique-identifier)\n * [User object and UUID](#user-object-and-uuid)\n * [Repository object and UUID](#repository-object-and-uuid)\n * [Team object and UUID](#team-object-and-uuid)\n* [Standard error responses](#standardized-error-responses)\n* [Standard ISO-8601 timestamps](#standard-iso-8601-timestamps)\n\n----\n\n\n### URI structure\n\nAll Bitbucket Cloud requests start with the `https://api.bitbucket.org/2.0` prefix (for the 2.0 API) and `https://api.bitbucket.org/1.0` prefix (1.0 API).\n\nThe next segment of the URI path depends on the endpoint of the request. For example, using the curl command and the repositories endpoint you can list all the issues on Bitbucket's tutorial repository:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/tutorials/tutorials.bitbucket.org\n```\nGiven a specific endpoint, you can then drill down to a particular aspect or resource of that endpoint. The issues resource on a repository is an example:\n\n```\ncurl https://api.bitbucket.org/1.0/repositories/tutorials/tutorials.bitbucket.org/issues\n```\n\n#### HTTP methods\n\nA given endpoint or resource has a series of actions (or methods) associated with it. The Bitbucket service supports these standard HTTP methods:\n\n| Call | Description |\n|------|-------------|\n| GET | Retrieves information. |\n| PUT | Updates existing information. |\n| POST | Creates new information. |\n| DELETE | Removes existing information. |\n\nFor example, you can call use the POST action on the issues resource and create an issue on the issue tracker.\n\n**Specifying content length**\n\nYou can get a `411 Length Required` response. If this happens, the API requires a Content-Length header but the client is not sending it. You should add the header yourself, for example using the curl client:\n\n```\ncurl -r PUT --header \"Content-Length: 0\" -u user:app_password https://api.bitbucket.org/1.0/emails/rap@atlassian.com\n```\n\n### Universally Unique Identifier\n\nUUID's provide a single point of recognition for users, teams, and repositories. The UUID is distinct from the username, team name, and repository name fields and remains the same even when those fields change. For example when a user changes their username or moves a repository you will need to modify calls which use those identifiers but not if you are pointing to the UUID.\n\n#### UUID examples and structure\n\nUUID's work with both the 1.0 and 2.0 APIs for the user, team, and repository objects. The following examples the following characters are replacements for curly brackets: `%7B` replaces `{` and `%7D` replaces `}`. You will see this structure in the following example sections.\n\n#### User object and UUID\n\nWhen you make a call using either the username or the UUID for that user the response is the same.\n\n**Call with username**:\n\n```\ncurl https://api.bitbucket.org/2.0/users/tutorials\n```\n\n***Call with UUID for the user**:\n\n```\ncurl https://api.bitbucket.org/2.0/users/%7Bc788b2da-b7a2-404c-9e26-d3f077557007%7D\n```\n\n**Response**\n```JSON\n{\n \"username\": \"tutorials\",\n \"nickname\": \"tutorials\",\n \"account_status\": \"active\",\n \"website\": \"https://tutorials.bitbucket.org/\",\n \"display_name\": \"tutorials account\",\n \"uuid\": \"{c788b2da-b7a2-404c-9e26-d3f077557007}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/tutorials\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/tutorials\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/followers\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Nov/25/tutorials-avatar-1563784409-6_avatar.png\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/following\"\n }\n },\n \"created_on\": \"2011-12-20T16:34:07.132459+00:00\",\n \"location\": \"Santa Monica, CA\",\n \"type\": \"user\"\n}\n```\n\n#### Repository object and UUID\n\nOnce you have the UUID for a repository you no longer need a username or team name to make the API call so long as you use an empty field. This helps you resolve repositories no matter if the username or team name changes.\n\n**Call with team name (1team) and repository name (moxie)**:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/1team/moxie\n```\n**Call with UUID and empty field**:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/%7B%7D/%7B21fa9bf8-b5b2-4891-97ed-d590bad0f871%7D\n```\n\n**Call with UUID and teamname**:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/1team/%7B21fa9bf8-b5b2-4891-97ed-d590bad0f871%7D\n```\n\n**Response**\n\n```JSON\n{\n \"created_on\": \"2013-11-08T01:11:03.222520+00:00\",\n \"description\": \"\",\n \"fork_policy\": \"allow_forks\",\n \"full_name\": \"1team/moxie\",\n \"has_issues\": false,\n \"has_wiki\": false,\n \"is_private\": false,\n \"language\": \"\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/1team/moxie/avatar/32/\"\n },\n \"branches\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/refs/branches\"\n },\n \"clone\": [\n {\n \"href\": \"https://bitbucket.org/1team/moxie.git\",\n \"name\": \"https\"\n },\n {\n \"href\": \"ssh://git@bitbucket.org/1team/moxie.git\",\n \"name\": \"ssh\"\n }\n ],\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/commits\"\n },\n \"downloads\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/downloads\"\n },\n \"forks\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/forks\"\n },\n \"hooks\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/hooks\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/1team/moxie\"\n },\n \"pullrequests\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/pullrequests\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie\"\n },\n \"tags\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/refs/tags\"\n },\n \"watchers\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/watchers\"\n }\n },\n \"name\": \"moxie\",\n \"owner\": {\n \"display_name\": \"the team\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/1team/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/1team/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/teams/1team\"\n }\n },\n \"type\": \"team\",\n \"username\": \"1team\",\n \"uuid\": \"{aa559944-83c9-4963-a9a8-69ac8d9cf5d2}\"\n },\n \"project\": {\n \"key\": \"PROJ\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/user/1team/projects/PROJ/avatar/32\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/account/user/1team/projects/PROJ\"\n }\n },\n \"name\": \"Untitled project\",\n \"type\": \"project\",\n \"uuid\": \"{ab52aaeb-16ad-4fb0-bb1d-47e4f00367ff}\"\n },\n \"scm\": \"git\",\n \"size\": 33348,\n \"type\": \"repository\",\n \"updated_on\": \"2013-11-08T01:11:03.263237+00:00\",\n \"uuid\": \"{21fa9bf8-b5b2-4891-97ed-d590bad0f871}\",\n \"website\": \"\"\n}\n```\n\n#### Team object and UUID\n\nThis example shows a call for a list of team members using both the team name and with the UUID for the team object. As the call is unauthenticated in the following example the response object will only show members with public profiles. The response is the same in either case.\n\n**Call with teamname**\n\n```\ncurl https://api.bitbucket.org/2.0/teams/1team/members\n```\n**Call with UUID for team object**\n\n```\ncurl https://api.bitbucket.org/2.0/teams/%7Baa559944-83c9-4963-a9a8-69ac8d9cf5d2%7D/members\n```\n\n**Response**\n\n```JSON\n{\n \"page\": 1,\n \"pagelen\": 50,\n \"size\": 2,\n \"values\": [\n {\n \"created_on\": \"2011-12-20T16:34:07.132459+00:00\",\n \"display_name\": \"tutorials account\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/tutorials/avatar/32/\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/followers\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/following\"\n },\n \"hooks\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/hooks\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/tutorials/\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/tutorials\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials\"\n },\n \"snippets\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/tutorials\"\n }\n },\n \"location\": null,\n \"type\": \"user\",\n \"username\": \"tutorials\",\n \"nickname\": \"tutorials\",\n \"account_status\": \"active\",\n \"uuid\": \"{c788b2da-b7a2-404c-9e26-d3f077557007}\",\n \"website\": \"https://tutorials.bitbucket.org/\"\n },\n {\n \"created_on\": \"2013-12-10T14:44:13+00:00\",\n \"display_name\": \"Dan Stevens [Atlassian]\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/dans9190/avatar/32/\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190/followers\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190/following\"\n },\n \"hooks\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190/hooks\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/dans9190/\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/dans9190\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190\"\n },\n \"snippets\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/dans9190\"\n }\n },\n \"location\": null,\n \"type\": \"user\",\n \"username\": \"dans9190\",\n \"nickname\": \"dans9190\",\n \"account_status\": \"active\",\n \"uuid\": \"{1cd06601-cd0e-4fce-be03-e9ac226978b7}\",\n \"website\": \"\"\n }\n ]\n}\n```\n\n### Standardized error responses\n\nThe 2.0 API standardizes the error response layout. The 2.0 API serves a JSON\nobject along with the appropriate HTTP status code. The JSON object provides a\ndetailed problem description.\n\n```json\n{\n \"type\": \"error\",\n \"error\": {\n \"message\": \"Bad request\",\n \"fields\": {\n \"src\": [\n \"This field is required.\"\n ]\n },\n \"detail\": \"You must specify a valid source branch when creating a pull request.\",\n \"id\": \"d23a1cc5178f7637f3d9bf2d13824258\",\n \"data\": {\n \"extra\": \"Optional, endpoint-specific data to further augment the error.\"\n }\n }\n}\n```\n\nThis object contains an error element which contains the following nested\nelements:\n\n| Element | Description |\n|---------|-------------|\n| message | A short description of the problem. This element is always present. Its value may be localized. |\n| fields | This optional element is used in response to POST or PUT operations in which clients have provided invalid input. It contains a list of one or more client-provided fields that failed validation. The values may be localized. |\n| detail | An optional detailed explanation of the failure. Its value may be localized.\n| id | An optional unique error identifier that identifies the error in Bitbucket's logging system. If you feel you hit a bug in an API and this field is provided, please mention it if you decide to contact support as it will greatly help us narrow down the problem. |\n\n### Standard ISO-8601 timestamps\n\nAll 2.0 APIs use standardized ISO-8601 timestamps. In most cases, our APIs return UTC timestamps and for these, the timezone offset part will be 00:00. In rare cases where the original localized timestamp has significance, the timezone offset may identify the event's original timezone.\n"
},
{
"anchor": "cors-hypermedia",
"title": "Cors and hypermedia",
"description": "Learn about resources and linking",
- "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjM2LjYgMjE4LjQzIj48ZGVmcz48c3R5bGU+LmNscy0xe2lzb2xhdGlvbjppc29sYXRlO30uY2xzLTIsLmNscy0zLC5jbHMtNHtmaWxsOm5vbmU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjExcHg7fS5jbHMtMntzdHJva2U6dXJsKCNsaW5lYXItZ3JhZGllbnQpO30uY2xzLTN7c3Ryb2tlOnVybCgjTmV3X0dyYWRpZW50X1N3YXRjaF8xNCk7fS5jbHMtNHtzdHJva2U6dXJsKCNOZXdfR3JhZGllbnRfU3dhdGNoXzEpO30uY2xzLTV7ZmlsbDojNDI1MjZlO30uY2xzLTZ7ZmlsbDojZmY1NjMwO30uY2xzLTEwLC5jbHMtNywuY2xzLTh7bWl4LWJsZW5kLW1vZGU6bXVsdGlwbHk7fS5jbHMtN3tmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTIpO30uY2xzLTh7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudC0zKTt9LmNscy05e2ZpbGw6IzAwNjVmZjt9LmNscy0xMHtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTQpO308L3N0eWxlPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB5MT0iMTY3Ljg3IiB4Mj0iMTkxLjU2IiB5Mj0iMTY3Ljg3IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNTA1Zjc5Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzQ0NTYzIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9Ik5ld19HcmFkaWVudF9Td2F0Y2hfMTQiIHgxPSIxMTIuODMiIHkxPSIxMzEuNzIiIHgyPSIyMzYuNiIgeTI9IjEzMS43MiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzAwNTJjYyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzI2ODRmZiIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJOZXdfR3JhZGllbnRfU3dhdGNoXzEiIHgxPSI0NS4wNiIgeTE9Ijg2LjY5IiB4Mj0iMTY4Ljg4IiB5Mj0iODYuNjkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNkZTM1MGIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZjc0NTIiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTIiIHgxPSIzNDQ3LjkzIiB5MT0iLTkxOC43OSIgeDI9IjM0NTEuNCIgeTI9Ii0xMDMyLjc2IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC01NzIuMzggMzcwNC4yOSkgcm90YXRlKC02NC4zNCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAuMzEiIHN0b3AtY29sb3I9IiNjMWM3ZDAiIHN0b3Atb3BhY2l0eT0iMCIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2MxYzdkMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtMyIgeDE9IjM1MDYuNiIgeTE9Ii03OTYuNjYiIHgyPSIzNTEwLjA3IiB5Mj0iLTkxMC42MyIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudC0yIi8+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNCIgeDE9IjM1ODEuNjgiIHkxPSItOTA2LjgyIiB4Mj0iMzU4NS4xNiIgeTI9Ii0xMDIwLjc5IiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50LTIiLz48L2RlZnM+PHRpdGxlPldlYmhvb2tzPC90aXRsZT48ZyBjbGFzcz0iY2xzLTEiPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJTb2Z0d2FyZSI+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTg2LjA2LDE2Ny44N0gxMTcuNzJBMjcuNTgsMjcuNTgsMCwwLDAsOTIuMjQsMTg1YTQ1LjA2LDQ1LjA2LDAsMSwxLTQxLjY4LTYyLjE5Ii8+PHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTE4LjMzLDUwLjUybDM0LjE1LDU5LjE4YTI3LjU5LDI3LjU5LDAsMCwwLDI3LjU4LDEzLjUxLDQ1LjA2LDQ1LjA2LDAsMSwxLTMzLDY3LjE5Ii8+PHBhdGggY2xhc3M9ImNscy00IiBkPSJNNTAuNTYsMTY3Ljg3bDM0LjE4LTU5LjE2YTI3LjU5LDI3LjU5LDAsMCwwLTIuMDktMzAuNjQsNDUuMDYsNDUuMDYsMCwxLDEsNzQuNy01Ii8+PHBhdGggY2xhc3M9ImNscy01IiBkPSJNMTg2LjA2LDE5OS42NmEzMS43OSwzMS43OSwwLDEsMSwzMS43OS0zMS43OUEzMS44MiwzMS44MiwwLDAsMSwxODYuMDYsMTk5LjY2WiIvPjxnIGlkPSJfR3JvdXBfIiBkYXRhLW5hbWU9IiZsdDtHcm91cCZndDsiPjxwYXRoIGNsYXNzPSJjbHMtNiIgZD0iTTQ5LjU2LDE5OS42NGEzMS43OSwzMS43OSwwLDEsMSwzMi43Ny0zMC43N0EzMS44MiwzMS44MiwwLDAsMSw0OS41NiwxOTkuNjRaIi8+PC9nPjxwYXRoIGNsYXNzPSJjbHMtNyIgZD0iTTU0LjEyLDE4MC4zNmE1OC45LDU4LjksMCwwLDAtMS41OS0xMS4yN3MtMi4zNS05LjQ0LTcuMzMtMTYuODNhNDMuODksNDMuODksMCwwLDAtMTEuNzMtMTEuMTcsMzEuNzcsMzEuNzcsMCwwLDAsMjkuMjgsNTYuMTNDNTguMjksMTkyLjQ0LDU0LjY4LDE4Ni45LDU0LjEyLDE4MC4zNloiLz48cGF0aCBjbGFzcz0iY2xzLTgiIGQ9Ik0xODkuNjIsMTgwLjM2QTU4LjksNTguOSwwLDAsMCwxODgsMTY5LjA4cy0yLjM1LTkuNDQtNy4zMy0xNi44M0E0My44OSw0My44OSwwLDAsMCwxNjksMTQxLjA4YTMxLjc3LDMxLjc3LDAsMCwwLDI5LjI4LDU2LjEzQzE5My43OSwxOTIuNDQsMTkwLjE4LDE4Ni45LDE4OS42MiwxODAuMzZaIi8+PGcgaWQ9Il9Hcm91cF8yIiBkYXRhLW5hbWU9IiZsdDtHcm91cCZndDsiPjxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTg5LjksMzMuNzhhMzIuNDYsMzIuNDYsMCwxLDEsMTEuODgsNDQuMzRBMzIuNDksMzIuNDksMCwwLDEsODkuOSwzMy43OFoiLz48L2c+PHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTEyMi4yMyw2Ni4xM2E1OC45LDU4LjksMCwwLDAtMS41OS0xMS4yN3MtMi4zNS05LjQ0LTcuMzMtMTYuODNjLTMuMzctNS05LjA2LTkuNzktMTUuNDMtMTMuNDhBMzIuNDQsMzIuNDQsMCwwLDAsMTI4Ljc3LDgwLjZDMTI1LjMxLDc2LjM5LDEyMi43LDcxLjYxLDEyMi4yMyw2Ni4xM1oiLz48L2c+PC9nPjwvZz48L3N2Zz4='",
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjM2LjYgMjE4LjQzIj48ZGVmcz48c3R5bGU+LmNscy0xe2lzb2xhdGlvbjppc29sYXRlO30uY2xzLTIsLmNscy0zLC5jbHMtNHtmaWxsOm5vbmU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjExcHg7fS5jbHMtMntzdHJva2U6dXJsKCNsaW5lYXItZ3JhZGllbnQpO30uY2xzLTN7c3Ryb2tlOnVybCgjTmV3X0dyYWRpZW50X1N3YXRjaF8xNCk7fS5jbHMtNHtzdHJva2U6dXJsKCNOZXdfR3JhZGllbnRfU3dhdGNoXzEpO30uY2xzLTV7ZmlsbDojNDI1MjZlO30uY2xzLTZ7ZmlsbDojZmY1NjMwO30uY2xzLTEwLC5jbHMtNywuY2xzLTh7bWl4LWJsZW5kLW1vZGU6bXVsdGlwbHk7fS5jbHMtN3tmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTIpO30uY2xzLTh7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudC0zKTt9LmNscy05e2ZpbGw6IzAwNjVmZjt9LmNscy0xMHtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTQpO308L3N0eWxlPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB5MT0iMTY3Ljg3IiB4Mj0iMTkxLjU2IiB5Mj0iMTY3Ljg3IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNTA1Zjc5Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzQ0NTYzIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9Ik5ld19HcmFkaWVudF9Td2F0Y2hfMTQiIHgxPSIxMTIuODMiIHkxPSIxMzEuNzIiIHgyPSIyMzYuNiIgeTI9IjEzMS43MiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzAwNTJjYyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzI2ODRmZiIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJOZXdfR3JhZGllbnRfU3dhdGNoXzEiIHgxPSI0NS4wNiIgeTE9Ijg2LjY5IiB4Mj0iMTY4Ljg4IiB5Mj0iODYuNjkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNkZTM1MGIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZjc0NTIiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTIiIHgxPSIzNDQ3LjkzIiB5MT0iLTkxOC43OSIgeDI9IjM0NTEuNCIgeTI9Ii0xMDMyLjc2IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC01NzIuMzggMzcwNC4yOSkgcm90YXRlKC02NC4zNCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAuMzEiIHN0b3AtY29sb3I9IiNjMWM3ZDAiIHN0b3Atb3BhY2l0eT0iMCIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2MxYzdkMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtMyIgeDE9IjM1MDYuNiIgeTE9Ii03OTYuNjYiIHgyPSIzNTEwLjA3IiB5Mj0iLTkxMC42MyIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudC0yIi8+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNCIgeDE9IjM1ODEuNjgiIHkxPSItOTA2LjgyIiB4Mj0iMzU4NS4xNiIgeTI9Ii0xMDIwLjc5IiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50LTIiLz48L2RlZnM+PHRpdGxlPldlYmhvb2tzPC90aXRsZT48ZyBjbGFzcz0iY2xzLTEiPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJTb2Z0d2FyZSI+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTg2LjA2LDE2Ny44N0gxMTcuNzJBMjcuNTgsMjcuNTgsMCwwLDAsOTIuMjQsMTg1YTQ1LjA2LDQ1LjA2LDAsMSwxLTQxLjY4LTYyLjE5Ii8+PHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTE4LjMzLDUwLjUybDM0LjE1LDU5LjE4YTI3LjU5LDI3LjU5LDAsMCwwLDI3LjU4LDEzLjUxLDQ1LjA2LDQ1LjA2LDAsMSwxLTMzLDY3LjE5Ii8+PHBhdGggY2xhc3M9ImNscy00IiBkPSJNNTAuNTYsMTY3Ljg3bDM0LjE4LTU5LjE2YTI3LjU5LDI3LjU5LDAsMCwwLTIuMDktMzAuNjQsNDUuMDYsNDUuMDYsMCwxLDEsNzQuNy01Ii8+PHBhdGggY2xhc3M9ImNscy01IiBkPSJNMTg2LjA2LDE5OS42NmEzMS43OSwzMS43OSwwLDEsMSwzMS43OS0zMS43OUEzMS44MiwzMS44MiwwLDAsMSwxODYuMDYsMTk5LjY2WiIvPjxnIGlkPSJfR3JvdXBfIiBkYXRhLW5hbWU9IiZsdDtHcm91cCZndDsiPjxwYXRoIGNsYXNzPSJjbHMtNiIgZD0iTTQ5LjU2LDE5OS42NGEzMS43OSwzMS43OSwwLDEsMSwzMi43Ny0zMC43N0EzMS44MiwzMS44MiwwLDAsMSw0OS41NiwxOTkuNjRaIi8+PC9nPjxwYXRoIGNsYXNzPSJjbHMtNyIgZD0iTTU0LjEyLDE4MC4zNmE1OC45LDU4LjksMCwwLDAtMS41OS0xMS4yN3MtMi4zNS05LjQ0LTcuMzMtMTYuODNhNDMuODksNDMuODksMCwwLDAtMTEuNzMtMTEuMTcsMzEuNzcsMzEuNzcsMCwwLDAsMjkuMjgsNTYuMTNDNTguMjksMTkyLjQ0LDU0LjY4LDE4Ni45LDU0LjEyLDE4MC4zNloiLz48cGF0aCBjbGFzcz0iY2xzLTgiIGQ9Ik0xODkuNjIsMTgwLjM2QTU4LjksNTguOSwwLDAsMCwxODgsMTY5LjA4cy0yLjM1LTkuNDQtNy4zMy0xNi44M0E0My44OSw0My44OSwwLDAsMCwxNjksMTQxLjA4YTMxLjc3LDMxLjc3LDAsMCwwLDI5LjI4LDU2LjEzQzE5My43OSwxOTIuNDQsMTkwLjE4LDE4Ni45LDE4OS42MiwxODAuMzZaIi8+PGcgaWQ9Il9Hcm91cF8yIiBkYXRhLW5hbWU9IiZsdDtHcm91cCZndDsiPjxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTg5LjksMzMuNzhhMzIuNDYsMzIuNDYsMCwxLDEsMTEuODgsNDQuMzRBMzIuNDksMzIuNDksMCwwLDEsODkuOSwzMy43OFoiLz48L2c+PHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTEyMi4yMyw2Ni4xM2E1OC45LDU4LjksMCwwLDAtMS41OS0xMS4yN3MtMi4zNS05LjQ0LTcuMzMtMTYuODNjLTMuMzctNS05LjA2LTkuNzktMTUuNDMtMTMuNDhBMzIuNDQsMzIuNDQsMCwwLDAsMTI4Ljc3LDgwLjZDMTI1LjMxLDc2LjM5LDEyMi43LDcxLjYxLDEyMi4yMyw2Ni4xM1oiLz48L2c+PC9nPjwvZz48L3N2Zz4=",
"body": "\nThis section describes [Cross-origin resource sharing](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) (CORS), what content types we support in requests and responses, and hyperlinking resources in each json responses.\n\n\n----\n\n* [CORS](#cors)\n* [Supported content types](#supported-content-types)\n* [Resource links](#resource-links)\n\n----\n\n### Cors\n\nThe Bitbucket API supports Cross-origin resource sharing to allow requests for restricted resources across domains. For more information you can refer to:\n\n* [Wikipedia article on CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)\n* [W3C CORS recommendation](https://www.w3.org/TR/cors/)\n\nSending a general request from the api to bitbucket.com:\n\n`curl -i https://api.bitbucket.org -H \"origin: http://bitbucket.com\"`\n\nGives this result:\n\n HTTP/1.1 302 FOUND\n Server: nginx/1.6.2\n Vary: Cookie\n Cache-Control: max-age=900\n Content-Type: text/html; charset=utf-8\n Strict-Transport-Security: max-age=31536000\n Date: Tue, 21 Jun 2016 17:54:37 GMT\n Location: http://confluence.atlassian.com/x/IYBGDQ\n X-Served-By: app-110\n X-Static-Version: 2c820eb0d2b3\n ETag: \"d41d8cd98f00b204e9800998ecf8427e\"\n X-Content-Type-Options: nosniff\n X-Render-Time: 0.00379920005798\n Connection: Keep-Alive\n X-Version: 2c820eb0d2b3\n X-Frame-Options: SAMEORIGIN\n X-Request-Count: 383\n X-Cache-Info: cached\n Content-Length: 0\n\nSending the same request with the CORS check -X OPTIONS in the call:\n\n`curl -i https://api.bitbucket.org -H \"origin: http://bitbucket.com\" -X OPTIONS`\n\nGives this result:\n\n HTTP/1.1 302 FOUND\n Server: nginx/1.6.2\n Vary: Cookie\n Cache-Control: max-age=900\n Content-Type: text/html; charset=utf-8\n Access-Control-Expose-Headers: Accept-Ranges, Content-Encoding, Content-Length, Content-Type, ETag, Last-Modified\n Strict-Transport-Security: max-age=31536000\n Date: Tue, 21 Jun 2016 18:04:30 GMT\n Access-Control-Max-Age: 86400\n Location: http://confluence.atlassian.com/x/IYBGDQ\n X-Served-By: app-111\n Access-Control-Allow-Origin: *\n X-Static-Version: 2c820eb0d2b3\n ETag: \"d41d8cd98f00b204e9800998ecf8427e\"\n X-Content-Type-Options: nosniff\n X-Render-Time: 0.00371098518372\n Connection: keep-alive\n X-Version: 2c820eb0d2b3\n X-Frame-Options: SAMEORIGIN\n X-Request-Count: 357\n Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS\n Access-Control-Allow-Headers: Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, Range, X-CsrftokenX-Requested-With\n X-Cache-Info: not cacheable; request wasn't a GET or HEAD\n Content-Length: 0\n\n\n\n### Supported content types\n\nThe default and primary content type for 2.0 APIs is JSON. This applies both to responses from the server and to the request bodies provided by the client.\n\nUnless documented otherwise, whenever creating a new (POST) or modifying an existing (PUT) object, your client must provide the object's normal representation. Not every object element can be mutated. For example, a repository's created_on date is an auto-generated, immutable field. Your client can omit immutable fields from a request body.\n\nIn some cases, a resource might also accept regular application/x-www-url-form-encoded POST and PUT bodies. Such bodies can be more convenient in scripts and command line usage. Requests bodies can contain contain nested elements or they can be flat (without nested elements). Clients can send flat request bodies as either as application/json or as application/x-www-url-form-encoded. Nested objects always require JSON.\n\n### Resource links\n\nEvery 2.0 object contains a links element that points to related resources or alternate representations. Use links to quickly discover and traverse to related objects. Links serve a \"self-documenting\" function for each endpoint. For example, the following request for a specific user:\n\n\n`$ curl https://api.bitbucket.org/2.0/users/tutorials`\n\n```json\n{\n \"username\": \"tutorials\",\n \"nickname\": \"tutorials\",\n \"account_status\": \"active\",\n \"website\": \"https://tutorials.bitbucket.org/\",\n \"display_name\": \"tutorials account\",\n \"uuid\": \"{c788b2da-b7a2-404c-9e26-d3f077557007}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/tutorials\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/tutorials\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/followers\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Nov/25/tutorials-avatar-1563784409-6_avatar.png\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/following\"\n }\n },\n \"created_on\": \"2011-12-20T16:34:07.132459+00:00\",\n \"location\": \"Santa Monica, CA\",\n \"type\": \"user\"\n}\n```\nLinks can be actual REST API resources or they can be informational. In this example, informative resources include the user's avatar and the HTML URL for the user's Bitbucket account. Your client should avoid hardcoding an API's URL and instead use the URLs returned in API responses.\n\nA link's key is its `rel` (relationship) attribute and it contains a mandatory href element. For example, the following link:\n\n```json\n\"self\": {\n \"href\": \"https://api.bitbucket.org/api/2.0/users/tutorials\"\n}\n```\n\nThe rel for this link is self and the href is https://api.bitbucket.org/api/2.0/users/tutorials. A single rel key can contain an list (array) of href objects. Your client should anticipate that any rel key can contain one or more href objects.\n\nFinally, links can also contain optional elements. Two common optional elements are the name element and the title element. They are often used to disambiguate links that share the same rel key. In the example below, the repository object that contains a clone link with two href objects. Each object contains the optional name element to clarify its use.\n\n```json\n\"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/bitbucket\"\n },\n \"clone\": [\n {\n \"href\": \"https://api.bitbucket.org/evzijst/bitbucket.git\",\n \"name\": \"https\"\n },\n {\n \"href\": \"ssh://git@bitbucket.org/erik/bitbucket.git\",\n \"name\": \"ssh\"\n }\n ],\n ...\n}\n```\nLinks can support [URI Templates](https://tools.ietf.org/html/rfc6570); Those that do contain a `\"templated\": \"true\"` element.\n"
},
{
"anchor": "bb-connect",
- "title": "Atlassian Connect",
- "description": "Build Bitbucket add-ons with Connect",
- "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjU3LjMxNjMgMTYyLjU5OTQiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjMzQ0NTYzOwogICAgICB9CgogICAgICAuY2xzLTMgewogICAgICAgIGZpbGw6ICMxZGI5ZDQ7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgZmlsbDogIzAwNTdkODsKICAgICAgfQoKICAgICAgLmNscy01LCAuY2xzLTcsIC5jbHMtOSB7CiAgICAgICAgbWl4LWJsZW5kLW1vZGU6IG11bHRpcGx5OwogICAgICB9CgogICAgICAuY2xzLTUgewogICAgICAgIGZpbGw6IHVybCgjTjc1KTsKICAgICAgfQoKICAgICAgLmNscy02IHsKICAgICAgICBmaWxsOiAjMDA2NWZmOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIGZpbGw6IHVybCgjTjc1LTIpOwogICAgICB9CgogICAgICAuY2xzLTggewogICAgICAgIGZpbGw6IHVybCgjbGluZWFyLWdyYWRpZW50KTsKICAgICAgfQoKICAgICAgLmNscy05IHsKICAgICAgICBmaWxsOiB1cmwoI043NS0zKTsKICAgICAgfQoKICAgICAgLmNscy0xMCB7CiAgICAgICAgZmlsbDogdXJsKCNUMjAwLVQ3NSk7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9Ik43NSIgeDE9Ii0yMTg5LjU1NiIgeTE9IjI4MDguMjI4NCIgeDI9Ii0yMDkxLjE1NTEiIHkyPSIyODA4LjIyODQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMC4xNjgxLCAwLjk4NTgsIDAuOTg1OCwgLTAuMTY4MSwgLTIzNzMuNzM2LCAyNzIyLjEyNzgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2U1ZThlYyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlNWU4ZWMiIHN0b3Atb3BhY2l0eT0iMC4xIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJONzUtMiIgZGF0YS1uYW1lPSJONzUiIHgxPSItMjE2OC41OTQ3IiB5MT0iMjkzNS4wNTU2IiB4Mj0iLTIwNzAuMTkzNyIgeTI9IjI5MzUuMDU1NiIgeGxpbms6aHJlZj0iI043NSIvPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHgxPSIxOTAuMzU0NyIgeTE9IjE1OS45NjciIHgyPSIyNTkuOTQ4NyIgeTI9IjkwLjM3MyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzNDQ1NjMiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNWU2Yzg0Ii8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJONzUtMyIgZGF0YS1uYW1lPSJONzUiIHgxPSItMjI1Ny4zOTc3IiB5MT0iMjg4NC4yMjYzIiB4Mj0iLTIxNTguOTk2NyIgeTI9IjI4ODQuMjI2MyIgeGxpbms6aHJlZj0iI043NSIvPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJUMjAwLVQ3NSIgeDE9IjEyNi4wMjU3IiB5MT0iODUuMTA4IiB4Mj0iMTk1LjYxOTciIHkyPSIxNS41MTQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM2RjN2RjIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzljZTNlZSIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICA8L2RlZnM+CiAgPHRpdGxlPkFkZCBPbiBCbG9ja3MgMjwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPHJlY3QgaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTIiIHg9IjEyOC42NTgxIiB5PSI4Ny43NDA1IiB3aWR0aD0iNjQuMzI5MSIgaGVpZ2h0PSI3NC44NTkiLz4KICAgICAgICA8cmVjdCBpZD0iX1JlY3RhbmdsZV8yIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTMiIHg9IjY0LjMyOTEiIHk9IjEyLjg4MTUiIHdpZHRoPSI2NC4zMjkxIiBoZWlnaHQ9Ijc0Ljg1OSIvPgogICAgICAgIDxyZWN0IGlkPSJfUmVjdGFuZ2xlXzMiIGRhdGEtbmFtZT0iJmx0O1JlY3RhbmdsZSZndDsiIGNsYXNzPSJjbHMtNCIgeT0iODcuNzQwNSIgd2lkdGg9IjY0LjMyOTEiIGhlaWdodD0iNzQuODU5Ii8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy01IiBkPSJNNjQuMzI5MSw4Ny43NEg1OC42NTIzYTYyLjc4NzcsNjIuNzg3NywwLDAsMC0yNC41Njg0LDIwLjc2MjFjLTguMjYwNywxMi4xOTYtNC4zNDM3LDE4LjI0MTUtMTEuNjYzNywzMS42Mzk1QzE2LjUxLDE1MC45Niw4LjA1MSwxNTcuODIsMCwxNjIuNTU2di4wNDM1aDY0LjMyOVoiLz4KICAgICAgICA8cmVjdCBpZD0iX1JlY3RhbmdsZV80IiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIHg9IjY0LjMyOTEiIHk9Ijg3Ljc0MDUiIHdpZHRoPSI2NC4zMjkxIiBoZWlnaHQ9Ijc0Ljg1OSIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNyIgZD0iTTE5Mi45ODcyLDg3Ljc0SDE4My4zNDNhNjIuNzg3Nyw2Mi43ODc3LDAsMCwwLTI0LjU2ODQsMjAuNzYyMWMtOC4yNjA3LDEyLjE5Ni00LjM0MzgsMTguMjQxNS0xMS42NjM3LDMxLjYzOTVhNTYuNzI0Niw1Ni43MjQ2LDAsMCwxLTE4LjQ1MjcsMTkuOTF2Mi41NDc0aDY0LjMyOVoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTgiIHg9IjE5Mi45ODcyIiB5PSI4Ny43NDA1IiB3aWR0aD0iNjQuMzI5MSIgaGVpZ2h0PSI3NC44NTkiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTkiIGQ9Ik0xMjguNjU4MiwxMi44ODE1SDExNi41OUE2MS45NjQ2LDYxLjk2NDYsMCwwLDAsMTAxLjMyMzMsMjguMjE1QzkzLjA2MjUsNDAuNDEwOSw5Ni45Nzk0LDQ2LjQ1NjUsODkuNjYsNTkuODU0NCw4My4wMzE2LDcxLjk4NTcsNzMuMTk3LDc5LjE1MTQsNjQuMzI5MSw4My45MTA4djMuODNoNjQuMzI5MVoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTEwIiB4PSIxMjguNjU4MSIgeT0iMTIuODgxNSIgd2lkdGg9IjY0LjMyOTEiIGhlaWdodD0iNzQuODU5Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy00IiB4PSIxMC4xNjA1IiB5PSI3NC44NTkiIHdpZHRoPSIyNC43NTM2IiBoZWlnaHQ9IjEyLjg4MTUiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTQiIHg9IjUxLjk1MjMiIHk9Ijc0Ljg1OSIgd2lkdGg9IjI0Ljc1MzYiIGhlaWdodD0iMTIuODgxNSIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtNCIgeD0iOTMuNzQ0MSIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxMzguODE4NiIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxODAuNjEwNCIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIyMjIuNDAyMiIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0zIiB4PSI3NC40ODk1IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0zIiB4PSIxMTYuMjgxNCIgd2lkdGg9IjI0Ljc1MzYiIGhlaWdodD0iMTIuODgxNSIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMyIgeD0iMTU4LjA3MzIiIHdpZHRoPSIyNC43NTM2IiBoZWlnaHQ9IjEyLjg4MTUiLz4KICAgICAgPC9nPgogICAgPC9nPgogIDwvZz4KPC9zdmc+Cg=='",
- "body": "\nYou can use the Atlassian Connect for Bitbucket Cloud to build add-ons which\ncan connect with the Bitbucket UI and your own application set. An add-on could\nbe an integration with another existing service, new features for the Atlassian\napplication, or even a new product that runs within the Atlassian application.\n\nFor complete information see:\n[Atlassian Connect for Bitbucket Cloud](https://developer.atlassian.com/bitbucket/index.html)\n"
+ "title": "Integrating with Bitbucket Cloud",
+ "description": "Build Bitbucket integration with Forge or Atlassian Connect",
+ "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjU3LjMxNjMgMTYyLjU5OTQiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjMzQ0NTYzOwogICAgICB9CgogICAgICAuY2xzLTMgewogICAgICAgIGZpbGw6ICMxZGI5ZDQ7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgZmlsbDogIzAwNTdkODsKICAgICAgfQoKICAgICAgLmNscy01LCAuY2xzLTcsIC5jbHMtOSB7CiAgICAgICAgbWl4LWJsZW5kLW1vZGU6IG11bHRpcGx5OwogICAgICB9CgogICAgICAuY2xzLTUgewogICAgICAgIGZpbGw6IHVybCgjTjc1KTsKICAgICAgfQoKICAgICAgLmNscy02IHsKICAgICAgICBmaWxsOiAjMDA2NWZmOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIGZpbGw6IHVybCgjTjc1LTIpOwogICAgICB9CgogICAgICAuY2xzLTggewogICAgICAgIGZpbGw6IHVybCgjbGluZWFyLWdyYWRpZW50KTsKICAgICAgfQoKICAgICAgLmNscy05IHsKICAgICAgICBmaWxsOiB1cmwoI043NS0zKTsKICAgICAgfQoKICAgICAgLmNscy0xMCB7CiAgICAgICAgZmlsbDogdXJsKCNUMjAwLVQ3NSk7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9Ik43NSIgeDE9Ii0yMTg5LjU1NiIgeTE9IjI4MDguMjI4NCIgeDI9Ii0yMDkxLjE1NTEiIHkyPSIyODA4LjIyODQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMC4xNjgxLCAwLjk4NTgsIDAuOTg1OCwgLTAuMTY4MSwgLTIzNzMuNzM2LCAyNzIyLjEyNzgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2U1ZThlYyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlNWU4ZWMiIHN0b3Atb3BhY2l0eT0iMC4xIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJONzUtMiIgZGF0YS1uYW1lPSJONzUiIHgxPSItMjE2OC41OTQ3IiB5MT0iMjkzNS4wNTU2IiB4Mj0iLTIwNzAuMTkzNyIgeTI9IjI5MzUuMDU1NiIgeGxpbms6aHJlZj0iI043NSIvPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHgxPSIxOTAuMzU0NyIgeTE9IjE1OS45NjciIHgyPSIyNTkuOTQ4NyIgeTI9IjkwLjM3MyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzNDQ1NjMiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNWU2Yzg0Ii8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJONzUtMyIgZGF0YS1uYW1lPSJONzUiIHgxPSItMjI1Ny4zOTc3IiB5MT0iMjg4NC4yMjYzIiB4Mj0iLTIxNTguOTk2NyIgeTI9IjI4ODQuMjI2MyIgeGxpbms6aHJlZj0iI043NSIvPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJUMjAwLVQ3NSIgeDE9IjEyNi4wMjU3IiB5MT0iODUuMTA4IiB4Mj0iMTk1LjYxOTciIHkyPSIxNS41MTQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM2RjN2RjIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzljZTNlZSIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICA8L2RlZnM+CiAgPHRpdGxlPkFkZCBPbiBCbG9ja3MgMjwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPHJlY3QgaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTIiIHg9IjEyOC42NTgxIiB5PSI4Ny43NDA1IiB3aWR0aD0iNjQuMzI5MSIgaGVpZ2h0PSI3NC44NTkiLz4KICAgICAgICA8cmVjdCBpZD0iX1JlY3RhbmdsZV8yIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTMiIHg9IjY0LjMyOTEiIHk9IjEyLjg4MTUiIHdpZHRoPSI2NC4zMjkxIiBoZWlnaHQ9Ijc0Ljg1OSIvPgogICAgICAgIDxyZWN0IGlkPSJfUmVjdGFuZ2xlXzMiIGRhdGEtbmFtZT0iJmx0O1JlY3RhbmdsZSZndDsiIGNsYXNzPSJjbHMtNCIgeT0iODcuNzQwNSIgd2lkdGg9IjY0LjMyOTEiIGhlaWdodD0iNzQuODU5Ii8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy01IiBkPSJNNjQuMzI5MSw4Ny43NEg1OC42NTIzYTYyLjc4NzcsNjIuNzg3NywwLDAsMC0yNC41Njg0LDIwLjc2MjFjLTguMjYwNywxMi4xOTYtNC4zNDM3LDE4LjI0MTUtMTEuNjYzNywzMS42Mzk1QzE2LjUxLDE1MC45Niw4LjA1MSwxNTcuODIsMCwxNjIuNTU2di4wNDM1aDY0LjMyOVoiLz4KICAgICAgICA8cmVjdCBpZD0iX1JlY3RhbmdsZV80IiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIHg9IjY0LjMyOTEiIHk9Ijg3Ljc0MDUiIHdpZHRoPSI2NC4zMjkxIiBoZWlnaHQ9Ijc0Ljg1OSIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNyIgZD0iTTE5Mi45ODcyLDg3Ljc0SDE4My4zNDNhNjIuNzg3Nyw2Mi43ODc3LDAsMCwwLTI0LjU2ODQsMjAuNzYyMWMtOC4yNjA3LDEyLjE5Ni00LjM0MzgsMTguMjQxNS0xMS42NjM3LDMxLjYzOTVhNTYuNzI0Niw1Ni43MjQ2LDAsMCwxLTE4LjQ1MjcsMTkuOTF2Mi41NDc0aDY0LjMyOVoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTgiIHg9IjE5Mi45ODcyIiB5PSI4Ny43NDA1IiB3aWR0aD0iNjQuMzI5MSIgaGVpZ2h0PSI3NC44NTkiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTkiIGQ9Ik0xMjguNjU4MiwxMi44ODE1SDExNi41OUE2MS45NjQ2LDYxLjk2NDYsMCwwLDAsMTAxLjMyMzMsMjguMjE1QzkzLjA2MjUsNDAuNDEwOSw5Ni45Nzk0LDQ2LjQ1NjUsODkuNjYsNTkuODU0NCw4My4wMzE2LDcxLjk4NTcsNzMuMTk3LDc5LjE1MTQsNjQuMzI5MSw4My45MTA4djMuODNoNjQuMzI5MVoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTEwIiB4PSIxMjguNjU4MSIgeT0iMTIuODgxNSIgd2lkdGg9IjY0LjMyOTEiIGhlaWdodD0iNzQuODU5Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy00IiB4PSIxMC4xNjA1IiB5PSI3NC44NTkiIHdpZHRoPSIyNC43NTM2IiBoZWlnaHQ9IjEyLjg4MTUiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTQiIHg9IjUxLjk1MjMiIHk9Ijc0Ljg1OSIgd2lkdGg9IjI0Ljc1MzYiIGhlaWdodD0iMTIuODgxNSIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtNCIgeD0iOTMuNzQ0MSIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxMzguODE4NiIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxODAuNjEwNCIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIyMjIuNDAyMiIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0zIiB4PSI3NC40ODk1IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0zIiB4PSIxMTYuMjgxNCIgd2lkdGg9IjI0Ljc1MzYiIGhlaWdodD0iMTIuODgxNSIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMyIgeD0iMTU4LjA3MzIiIHdpZHRoPSIyNC43NTM2IiBoZWlnaHQ9IjEyLjg4MTUiLz4KICAgICAgPC9nPgogICAgPC9nPgogIDwvZz4KPC9zdmc+Cg==",
+ "body": "\nYou can use [Forge](https://developer.atlassian.com/cloud/bitbucket/getting-started-with-forge/) \nor [Atlassian Connect](https://developer.atlassian.com/cloud/bitbucket/getting-started-with-connect/) \nto build apps which can connect with the Bitbucket UI and your own application set. An app could\nbe an integration with another existing service, new features for the Atlassian\napplication, or even a new product that runs within the Atlassian application.\n\nFor complete information see:\n[integrating with Bitbucket Cloud](https://developer.atlassian.com/cloud/bitbucket/)\n"
}
]
},
@@ -24536,17 +25287,17 @@
"authorizationUrl": "https://bitbucket.org/site/oauth2/authorize",
"tokenUrl": "https://bitbucket.org/site/oauth2/access_token",
"scopes": {
- "email": "Read your account's primary email address",
- "account": "Read your account information",
- "account:write": "Read and modify your account information",
- "team": "Read your team membership information",
- "team:write": "Read and modify your team membership information",
"repository": "Read your repositories",
"repository:write": "Read and modify your repositories",
"repository:admin": "Administer your repositories",
"repository:delete": "Delete your repositories",
"project": "Read your workspace's project settings and read repositories contained within your workspace's projects",
"project:admin": "Read and modify settings for projects in your workspace",
+ "email": "Read your account's primary email address",
+ "account": "Read your account information",
+ "account:write": "Read and modify your account information",
+ "team": "Read your team membership information",
+ "team:write": "Read and modify your team membership information",
"pipeline": "Access your repositories' build pipelines",
"pipeline:write": "Access and rerun your repositories' build pipelines",
"pipeline:variable": "Access your repositories' build pipelines and configure their variables",
@@ -24572,32 +25323,49 @@
}
},
"schemas": {
- "A_pull_request_task": {
+ "GPG_account_key": {
"allOf": [
{
- "$ref": "#/components/schemas/task"
+ "$ref": "#/components/schemas/object"
},
{
"type": "object",
"properties": {
+ "added_on": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "comment": {
+ "type": "string",
+ "description": "The comment parsed from the GPG key (if present)"
+ },
+ "created_on": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "expires_on": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "fingerprint": {
+ "type": "string",
+ "description": "The GPG key fingerprint."
+ },
+ "key": {
+ "type": "string",
+ "description": "The GPG key value in X format."
+ },
+ "key_id": {
+ "type": "string",
+ "description": "The unique identifier for the GPG key"
+ },
+ "last_used": {
+ "type": "string",
+ "format": "date-time"
+ },
"links": {
"type": "object",
"properties": {
- "html": {
- "type": "object",
- "title": "Link",
- "description": "A link to a resource related to this object.",
- "properties": {
- "href": {
- "type": "string",
- "format": "uri"
- },
- "name": {
- "type": "string"
- }
- },
- "additionalProperties": false
- },
"self": {
"type": "object",
"title": "Link",
@@ -24615,84 +25383,32 @@
}
},
"additionalProperties": false
+ },
+ "name": {
+ "type": "string",
+ "description": "The user-defined label for the GPG key"
+ },
+ "owner": {
+ "$ref": "#/components/schemas/account"
+ },
+ "parent_fingerprint": {
+ "type": "string",
+ "description": "The fingerprint of the parent key. This value is null unless the current key is a subkey."
+ },
+ "subkeys": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/GPG_account_key"
+ },
+ "minItems": 0,
+ "uniqueItems": true
}
},
- "additionalProperties": false
+ "additionalProperties": true
}
],
- "title": "Pull Request Task",
- "description": "A pull request task."
- },
- "A_pullrequest_comment_task": {
- "allOf": [
- {
- "$ref": "#/components/schemas/A_pull_request_task"
- },
- {
- "type": "object",
- "properties": {
- "comment": {
- "$ref": "#/components/schemas/comment"
- }
- },
- "additionalProperties": false
- }
- ],
- "title": "Pull Request Comment Task",
- "description": "A pullrequest comment task"
- },
- "A_pullrequest_task_create": {
- "type": "object",
- "title": "Pull Request Task Create",
- "description": "A pullrequest task create",
- "properties": {
- "comment": {
- "$ref": "#/components/schemas/comment"
- },
- "content": {
- "type": "object",
- "title": "Task Raw Content",
- "description": "task raw content",
- "properties": {
- "raw": {
- "type": "string",
- "description": "The task contents"
- }
- },
- "required": ["raw"],
- "additionalProperties": false
- },
- "pending": {
- "type": "boolean"
- }
- },
- "required": ["content"],
- "additionalProperties": false
- },
- "A_pullrequest_task_update": {
- "type": "object",
- "title": "Pull Request Task Update",
- "description": "A pullrequest task update",
- "properties": {
- "content": {
- "type": "object",
- "title": "Task Raw Content",
- "description": "task raw content",
- "properties": {
- "raw": {
- "type": "string",
- "description": "The task contents"
- }
- },
- "required": ["raw"],
- "additionalProperties": false
- },
- "state": {
- "type": "string",
- "enum": ["RESOLVED", "UNRESOLVED"]
- }
- },
- "additionalProperties": false
+ "title": "GPG Account Key",
+ "description": "Represents a GPG public key for a user."
},
"account": {
"allOf": [
@@ -24712,10 +25428,6 @@
"links": {
"$ref": "#/components/schemas/account_links"
},
- "username": {
- "type": "string",
- "pattern": "^[a-zA-Z0-9_\\-]+$"
- },
"uuid": {
"type": "string"
}
@@ -24812,6 +25524,9 @@
"author": {
"$ref": "#/components/schemas/author"
},
+ "committer": {
+ "$ref": "#/components/schemas/committer"
+ },
"date": {
"type": "string",
"format": "date-time"
@@ -24895,7 +25610,14 @@
"description": "Available merge strategies for pull requests targeting this branch.",
"items": {
"type": "string",
- "enum": ["merge_commit", "squash", "fast_forward"]
+ "enum": [
+ "merge_commit",
+ "squash",
+ "fast_forward",
+ "squash_fast_forward",
+ "rebase_fast_forward",
+ "rebase_merge"
+ ]
}
}
},
@@ -25089,6 +25811,23 @@
{
"type": "object",
"properties": {
+ "branch_match_kind": {
+ "type": "string",
+ "description": "Indicates how the restriction is matched against a branch. The default is `glob`.",
+ "enum": ["branching_model", "glob"]
+ },
+ "branch_type": {
+ "type": "string",
+ "description": "Apply the restriction to branches of this type. Active when `branch_match_kind` is `branching_model`. The branch type will be calculated using the branching model configured for the repository.",
+ "enum": [
+ "feature",
+ "bugfix",
+ "release",
+ "hotfix",
+ "development",
+ "production"
+ ]
+ },
"groups": {
"type": "array",
"items": {
@@ -25096,14 +25835,72 @@
},
"minItems": 0
},
+ "id": {
+ "type": "integer",
+ "description": "The branch restriction status' id."
+ },
+ "kind": {
+ "type": "string",
+ "description": "The type of restriction that is being applied.",
+ "enum": [
+ "push",
+ "delete",
+ "force",
+ "restrict_merges",
+ "require_tasks_to_be_completed",
+ "require_approvals_to_merge",
+ "require_review_group_approvals_to_merge",
+ "require_default_reviewer_approvals_to_merge",
+ "require_no_changes_requested",
+ "require_passing_builds_to_merge",
+ "require_commits_behind",
+ "reset_pullrequest_approvals_on_change",
+ "smart_reset_pullrequest_approvals",
+ "reset_pullrequest_changes_requested_on_change",
+ "require_all_dependencies_merged",
+ "enforce_merge_checks",
+ "allow_auto_merge_when_builds_pass",
+ "require_all_comments_resolved"
+ ]
+ },
+ "links": {
+ "type": "object",
+ "properties": {
+ "self": {
+ "type": "object",
+ "title": "Link",
+ "description": "A link to a resource related to this object.",
+ "properties": {
+ "href": {
+ "type": "string",
+ "format": "uri"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+ },
+ "pattern": {
+ "type": "string",
+ "description": "Apply the restriction to branches that match this pattern. Active when `branch_match_kind` is `glob`. Will be empty when `branch_match_kind` is `branching_model`."
+ },
"users": {
"type": "array",
"items": {
"$ref": "#/components/schemas/account"
},
"minItems": 0
+ },
+ "value": {
+ "type": "integer",
+ "description": "Value with kind-specific semantics:\n\n* `require_approvals_to_merge` uses it to require a minimum number of approvals on a PR.\n\n* `require_default_reviewer_approvals_to_merge` uses it to require a minimum number of approvals from default reviewers on a PR.\n\n* `require_passing_builds_to_merge` uses it to require a minimum number of passing builds.\n\n* `require_commits_behind` uses it to require the current branch is up to a maximum number of commits behind it destination."
}
},
+ "required": ["kind", "branch_match_kind", "pattern"],
"additionalProperties": true
}
],
@@ -25393,7 +26190,7 @@
"state": {
"type": "string",
"description": "Provides some indication of the status of this commit",
- "enum": ["STOPPED", "INPROGRESS", "FAILED", "SUCCESSFUL"]
+ "enum": ["FAILED", "INPROGRESS", "STOPPED", "SUCCESSFUL"]
},
"updated_on": {
"type": "string",
@@ -25414,6 +26211,28 @@
"title": "Commit Status",
"description": "A commit status object."
},
+ "committer": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/object"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "raw": {
+ "type": "string",
+ "description": "The raw committer value from the repository. This may be the only value available if the committer does not match a user in Bitbucket."
+ },
+ "user": {
+ "$ref": "#/components/schemas/account"
+ }
+ },
+ "additionalProperties": true
+ }
+ ],
+ "title": "Committer",
+ "description": "The committer of a change in a repository"
+ },
"component": {
"allOf": [
{
@@ -25456,20 +26275,6 @@
"title": "Component",
"description": "A component as defined in a repository's issue tracker."
},
- "ddev_report": {
- "allOf": [
- {
- "$ref": "#/components/schemas/object"
- },
- {
- "additionalProperties": true,
- "type": "object",
- "description": "A report for a commit."
- }
- ],
- "x-bb-default-fields": ["uuid", "commitHash"],
- "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/commits/{commitHash}/reports/{uuid}"
- },
"default_reviewer_and_type": {
"type": "object",
"title": "Default Reviewer and Type",
@@ -25605,28 +26410,6 @@
"title": "Deployment Environment",
"description": "A Bitbucket Deployment Environment."
},
- "deployment_environment_lock": {
- "allOf": [
- {
- "$ref": "#/components/schemas/object"
- },
- {
- "additionalProperties": true,
- "type": "object",
- "properties": {
- "environmentUuid": {
- "type": "string",
- "description": "The UUID identifying the environment."
- }
- }
- }
- ],
- "x-bb-default-fields": ["*", "lock_opener.*", "owner.*"],
- "x-bb-batch-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments/locks_batch",
- "x-bb-batch-max-size": 100,
- "title": "Deployment Environment Lock",
- "description": "A Bitbucket Deployment Environment Lock."
- },
"deployment_release": {
"allOf": [
{
@@ -25875,148 +26658,6 @@
"title": "Deployment Variable",
"description": "A Pipelines deployment variable."
},
- "deployments_ddev_deployment_environment": {
- "allOf": [
- {
- "$ref": "#/components/schemas/object"
- },
- {
- "additionalProperties": true,
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the environment."
- },
- "uuid": {
- "type": "string",
- "description": "The UUID identifying the environment."
- }
- }
- }
- ],
- "x-bb-default-fields": ["uuid"],
- "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments/{uuid}",
- "x-bb-batch-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments_batch",
- "x-bb-batch-max-size": 100,
- "title": "Deployment Environment",
- "description": "A Bitbucket Deployment Environment."
- },
- "deployments_ddev_deployment_environment_lock": {
- "allOf": [
- {
- "$ref": "#/components/schemas/object"
- },
- {
- "additionalProperties": true,
- "type": "object",
- "properties": {
- "environmentUuid": {
- "type": "string",
- "description": "The UUID identifying the environment."
- }
- }
- }
- ],
- "x-bb-default-fields": ["*", "lock_opener.*", "owner.*"],
- "x-bb-batch-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments/locks_batch",
- "x-bb-batch-max-size": 100,
- "title": "Deployment Environment Lock",
- "description": "A Bitbucket Deployment Environment Lock."
- },
- "deployments_ddev_paginated_environments": {
- "title": "Paginated Deployment Environments",
- "description": "A paged list of environments",
- "allOf": [
- {
- "$ref": "#/components/schemas/paginated"
- },
- {
- "type": "object",
- "properties": {
- "values": {
- "type": "array",
- "minItems": 0,
- "items": {
- "$ref": "#/components/schemas/deployments_ddev_deployment_environment"
- },
- "description": "The values of the current page."
- }
- }
- }
- ]
- },
- "deployments_stg_west_deployment_environment": {
- "allOf": [
- {
- "$ref": "#/components/schemas/object"
- },
- {
- "additionalProperties": true,
- "type": "object",
- "properties": {
- "name": {
- "type": "string",
- "description": "The name of the environment."
- },
- "uuid": {
- "type": "string",
- "description": "The UUID identifying the environment."
- }
- }
- }
- ],
- "x-bb-default-fields": ["uuid"],
- "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments/{uuid}",
- "x-bb-batch-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments_batch",
- "x-bb-batch-max-size": 100,
- "title": "Deployment Environment",
- "description": "A Bitbucket Deployment Environment."
- },
- "deployments_stg_west_deployment_environment_lock": {
- "allOf": [
- {
- "$ref": "#/components/schemas/object"
- },
- {
- "additionalProperties": true,
- "type": "object",
- "properties": {
- "environmentUuid": {
- "type": "string",
- "description": "The UUID identifying the environment."
- }
- }
- }
- ],
- "x-bb-default-fields": ["*", "lock_opener.*", "owner.*"],
- "x-bb-batch-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments/locks_batch",
- "x-bb-batch-max-size": 100,
- "title": "Deployment Environment Lock",
- "description": "A Bitbucket Deployment Environment Lock."
- },
- "deployments_stg_west_paginated_environments": {
- "title": "Paginated Deployment Environments",
- "description": "A paged list of environments",
- "allOf": [
- {
- "$ref": "#/components/schemas/paginated"
- },
- {
- "type": "object",
- "properties": {
- "values": {
- "type": "array",
- "minItems": 0,
- "items": {
- "$ref": "#/components/schemas/deployments_stg_west_deployment_environment"
- },
- "description": "The values of the current page."
- }
- }
- }
- ]
- },
"diffstat": {
"type": "object",
"title": "Diff Stat",
@@ -26260,32 +26901,33 @@
"description": "The event identifier.",
"enum": [
"issue:comment_created",
- "repo:push",
- "repo:transfer",
- "pullrequest:changes_request_removed",
- "pullrequest:comment_updated",
- "pullrequest:unapproved",
- "pullrequest:comment_created",
- "repo:created",
- "pullrequest:changes_request_created",
- "repo:imported",
- "repo:fork",
- "project:updated",
"issue:created",
- "repo:deleted",
"issue:updated",
+ "project:updated",
+ "pullrequest:approved",
+ "pullrequest:changes_request_created",
+ "pullrequest:changes_request_removed",
+ "pullrequest:comment_created",
+ "pullrequest:comment_deleted",
"pullrequest:comment_reopened",
+ "pullrequest:comment_resolved",
+ "pullrequest:comment_updated",
+ "pullrequest:created",
+ "pullrequest:fulfilled",
+ "pullrequest:push",
+ "pullrequest:rejected",
+ "pullrequest:unapproved",
"pullrequest:updated",
"repo:commit_comment_created",
- "pullrequest:created",
- "pullrequest:approved",
- "pullrequest:rejected",
- "pullrequest:fulfilled",
- "pullrequest:comment_resolved",
+ "repo:commit_status_created",
"repo:commit_status_updated",
- "repo:updated",
- "pullrequest:comment_deleted",
- "repo:commit_status_created"
+ "repo:created",
+ "repo:deleted",
+ "repo:fork",
+ "repo:imported",
+ "repo:push",
+ "repo:transfer",
+ "repo:updated"
]
},
"label": {
@@ -26771,36 +27413,6 @@
},
"additionalProperties": false
},
- "jira_project": {
- "allOf": [
- {
- "$ref": "#/components/schemas/object"
- },
- {
- "additionalProperties": true,
- "type": "object",
- "description": "A Jira Project."
- }
- ],
- "x-bb-default-fields": ["type", "cloudId", "id"],
- "x-bb-detail-fields": ["key", "name", "url", "avatarUrls.*", "site"],
- "x-bb-url": "/api/{target_user.uuid}/jira/sites/{cloudId}/projects/{id}?atlassian_account_id={user.account_id}"
- },
- "jira_site": {
- "allOf": [
- {
- "$ref": "#/components/schemas/object"
- },
- {
- "additionalProperties": true,
- "type": "object",
- "description": "A Jira Site."
- }
- ],
- "x-bb-default-fields": ["type", "cloudId", "cloudUrl", "cloudName"],
- "x-bb-detail-fields": ["connected"],
- "x-bb-url": "/api/{target_user.uuid}/jira/sites/{cloudId}?atlassian_account_id={user.account_id}"
- },
"link": {
"type": "object",
"title": "Link",
@@ -27262,6 +27874,29 @@
}
]
},
+ "paginated_gpg_user_keys": {
+ "title": "Paginated GPG User Keys",
+ "description": "A paginated list of GPG keys.",
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/paginated"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "values": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/GPG_account_key"
+ },
+ "minItems": 0,
+ "uniqueItems": true,
+ "description": "The values of the current page."
+ }
+ }
+ }
+ ]
+ },
"paginated_hook_events": {
"title": "Paginated Hook Events",
"description": "A paginated list of webhook types available to subscribe on.",
@@ -27954,7 +28589,7 @@
"values": {
"type": "array",
"items": {
- "$ref": "#/components/schemas/A_pullrequest_comment_task"
+ "$ref": "#/components/schemas/pullrequest_comment_task"
},
"minItems": 0,
"uniqueItems": true,
@@ -28135,6 +28770,14 @@
"format": "date-time",
"description": "The timestamp when the Pipeline was completed. This is not set if the pipeline is still in progress."
},
+ "configuration_sources": {
+ "type": "array",
+ "description": "An ordered list of sources of the pipeline configuration",
+ "minItems": 0,
+ "items": {
+ "$ref": "#/components/schemas/pipeline_configuration_source"
+ }
+ },
"created_on": {
"type": "string",
"format": "date-time",
@@ -28143,6 +28786,9 @@
"creator": {
"$ref": "#/components/schemas/account"
},
+ "links": {
+ "$ref": "#/components/schemas/pipelines_pipeline_links"
+ },
"repository": {
"$ref": "#/components/schemas/repository"
},
@@ -28288,6 +28934,22 @@
"title": "Pipeline Commit Target",
"description": "A Bitbucket Pipelines commit target."
},
+ "pipeline_configuration_source": {
+ "type": "object",
+ "description": "Information about the source of the pipeline configuration",
+ "properties": {
+ "source": {
+ "type": "string",
+ "description": "Identifier of the configuration source"
+ },
+ "uri": {
+ "type": "string",
+ "format": "uri",
+ "description": "Link to the configuration source view or its immediate content"
+ }
+ },
+ "required": ["source", "uri"]
+ },
"pipeline_error": {
"allOf": [
{
@@ -29263,7 +29925,7 @@
"title": "Pipelines Configuration",
"description": "The Pipelines configuration for a repository."
},
- "pipelines_ddev_pipeline_step": {
+ "pipelines_links_section_href": {
"allOf": [
{
"$ref": "#/components/schemas/object"
@@ -29271,15 +29933,19 @@
{
"additionalProperties": true,
"type": "object",
- "description": "A step of a Bitbucket pipeline. This represents the actual result of the step execution."
+ "properties": {
+ "href": {
+ "type": "string",
+ "format": "uri",
+ "description": "A link"
+ }
+ }
}
],
- "x-bb-default-fields": ["uuid"],
- "x-bb-url": "/rest/1.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/pipelines/{pipeline.uuid}/steps/{uuid}",
- "x-bb-batch-url": "/rest/1.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/pipelines/steps_batch",
- "x-bb-batch-max-size": 100
+ "title": "Pipeline Links href",
+ "description": "A links section href"
},
- "pipelines_stg_west_pipeline_step": {
+ "pipelines_pipeline_links": {
"allOf": [
{
"$ref": "#/components/schemas/object"
@@ -29287,13 +29953,18 @@
{
"additionalProperties": true,
"type": "object",
- "description": "A step of a Bitbucket pipeline. This represents the actual result of the step execution."
+ "properties": {
+ "self": {
+ "$ref": "#/components/schemas/pipelines_links_section_href"
+ },
+ "steps": {
+ "$ref": "#/components/schemas/pipelines_links_section_href"
+ }
+ }
}
],
- "x-bb-default-fields": ["uuid"],
- "x-bb-url": "/rest/1.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/pipelines/{pipeline.uuid}/steps/{uuid}",
- "x-bb-batch-url": "/rest/1.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/pipelines/steps_batch",
- "x-bb-batch-max-size": 100
+ "title": "Pipeline Links",
+ "description": "Links section for a Pipeline."
},
"project": {
"allOf": [
@@ -29627,6 +30298,10 @@
"destination": {
"$ref": "#/components/schemas/pullrequest_endpoint"
},
+ "draft": {
+ "type": "boolean",
+ "description": "A boolean flag indicating whether the pull request is a draft."
+ },
"id": {
"type": "integer",
"description": "The pull request's unique ID. Note that pull request IDs are only unique within their associated repository."
@@ -29953,6 +30628,24 @@
"title": "Pull Request Comment",
"description": "A pullrequest comment."
},
+ "pullrequest_comment_task": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/pullrequest_task"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "comment": {
+ "$ref": "#/components/schemas/comment"
+ }
+ },
+ "additionalProperties": false
+ }
+ ],
+ "title": "Pull Request Comment Task",
+ "description": "A pullrequest comment task"
+ },
"pullrequest_endpoint": {
"type": "object",
"title": "Pull Request Endpoint",
@@ -29970,7 +30663,14 @@
"description": "Available merge strategies, when this endpoint is the destination of the pull request.",
"items": {
"type": "string",
- "enum": ["merge_commit", "squash", "fast_forward"]
+ "enum": [
+ "merge_commit",
+ "squash",
+ "fast_forward",
+ "squash_fast_forward",
+ "rebase_fast_forward",
+ "rebase_merge"
+ ]
}
},
"name": {
@@ -30008,12 +30708,19 @@
"merge_strategy": {
"type": "string",
"description": "The merge strategy that will be used to merge the pull request.",
- "enum": ["merge_commit", "squash", "fast_forward"],
+ "enum": [
+ "merge_commit",
+ "squash",
+ "fast_forward",
+ "squash_fast_forward",
+ "rebase_fast_forward",
+ "rebase_merge"
+ ],
"default": "merge_commit"
},
"message": {
"type": "string",
- "description": "The commit message that will be used on the resulting commit."
+ "description": "The commit message that will be used on the resulting commit. Note that the size of the message is limited to 128 KiB."
},
"type": {
"type": "string"
@@ -30022,6 +30729,110 @@
"required": ["type"],
"additionalProperties": true
},
+ "pullrequest_task": {
+ "allOf": [
+ {
+ "$ref": "#/components/schemas/task"
+ },
+ {
+ "type": "object",
+ "properties": {
+ "links": {
+ "type": "object",
+ "properties": {
+ "html": {
+ "type": "object",
+ "title": "Link",
+ "description": "A link to a resource related to this object.",
+ "properties": {
+ "href": {
+ "type": "string",
+ "format": "uri"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ },
+ "self": {
+ "type": "object",
+ "title": "Link",
+ "description": "A link to a resource related to this object.",
+ "properties": {
+ "href": {
+ "type": "string",
+ "format": "uri"
+ },
+ "name": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "additionalProperties": false
+ }
+ ],
+ "title": "Pull Request Task",
+ "description": "A pull request task."
+ },
+ "pullrequest_task_create": {
+ "type": "object",
+ "title": "Pull Request Task Create",
+ "description": "A pullrequest task create",
+ "properties": {
+ "comment": {
+ "$ref": "#/components/schemas/comment"
+ },
+ "content": {
+ "type": "object",
+ "title": "Task Raw Content",
+ "description": "task raw content",
+ "properties": {
+ "raw": {
+ "type": "string",
+ "description": "The task contents"
+ }
+ },
+ "required": ["raw"],
+ "additionalProperties": false
+ },
+ "pending": {
+ "type": "boolean"
+ }
+ },
+ "required": ["content"],
+ "additionalProperties": false
+ },
+ "pullrequest_task_update": {
+ "type": "object",
+ "title": "Pull Request Task Update",
+ "description": "A pullrequest task update",
+ "properties": {
+ "content": {
+ "type": "object",
+ "title": "Task Raw Content",
+ "description": "task raw content",
+ "properties": {
+ "raw": {
+ "type": "string",
+ "description": "The task contents"
+ }
+ },
+ "required": ["raw"],
+ "additionalProperties": false
+ },
+ "state": {
+ "type": "string",
+ "enum": ["RESOLVED", "UNRESOLVED"]
+ }
+ },
+ "additionalProperties": false
+ },
"ref": {
"type": "object",
"title": "Ref",
@@ -30903,6 +31714,14 @@
{
"type": "object",
"properties": {
+ "expires_on": {
+ "type": "string",
+ "format": "date-time"
+ },
+ "fingerprint": {
+ "type": "string",
+ "description": "The SSH key fingerprint in SHA-256 format."
+ },
"owner": {
"$ref": "#/components/schemas/account"
}
@@ -30973,20 +31792,6 @@
"title": "SSH Key",
"description": "Base type for representing SSH public keys."
},
- "stg_west_report": {
- "allOf": [
- {
- "$ref": "#/components/schemas/object"
- },
- {
- "additionalProperties": true,
- "type": "object",
- "description": "A report for a commit."
- }
- ],
- "x-bb-default-fields": ["uuid", "commitHash"],
- "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/commits/{commitHash}/reports/{uuid}"
- },
"subject_types": {
"type": "object",
"title": "Subject Types",
@@ -31216,9 +32021,6 @@
"nickname": {
"type": "string",
"description": "Account name defined by the owner. Should be used instead of the \"username\" field. Note that \"nickname\" cannot be used in place of \"username\" in URLs and queries, as \"nickname\" is not guaranteed to be unique."
- },
- "website": {
- "type": "string"
}
},
"additionalProperties": true
@@ -31319,32 +32121,33 @@
"type": "string",
"enum": [
"issue:comment_created",
- "repo:push",
- "repo:transfer",
- "pullrequest:changes_request_removed",
- "pullrequest:comment_updated",
- "pullrequest:unapproved",
- "pullrequest:comment_created",
- "repo:created",
- "pullrequest:changes_request_created",
- "repo:imported",
- "repo:fork",
- "project:updated",
"issue:created",
- "repo:deleted",
"issue:updated",
+ "project:updated",
+ "pullrequest:approved",
+ "pullrequest:changes_request_created",
+ "pullrequest:changes_request_removed",
+ "pullrequest:comment_created",
+ "pullrequest:comment_deleted",
"pullrequest:comment_reopened",
+ "pullrequest:comment_resolved",
+ "pullrequest:comment_updated",
+ "pullrequest:created",
+ "pullrequest:fulfilled",
+ "pullrequest:push",
+ "pullrequest:rejected",
+ "pullrequest:unapproved",
"pullrequest:updated",
"repo:commit_comment_created",
- "pullrequest:created",
- "pullrequest:approved",
- "pullrequest:rejected",
- "pullrequest:fulfilled",
- "pullrequest:comment_resolved",
+ "repo:commit_status_created",
"repo:commit_status_updated",
- "repo:updated",
- "pullrequest:comment_deleted",
- "repo:commit_status_created"
+ "repo:created",
+ "repo:deleted",
+ "repo:fork",
+ "repo:imported",
+ "repo:push",
+ "repo:transfer",
+ "repo:updated"
]
},
"minItems": 1,
@@ -31396,6 +32199,15 @@
"type": "string",
"format": "date-time"
},
+ "forking_mode": {
+ "type": "string",
+ "description": "Controls the rules for forking repositories within this workspace.\n\n* **allow_forks**: unrestricted forking\n* **internal_only**: prevents forking of private repositories outside the workspace or to public repositories\n",
+ "enum": ["allow_forks", "internal_only"]
+ },
+ "is_privacy_enforced": {
+ "type": "boolean",
+ "description": "Indicates whether the workspace enforces private content, or whether it allows public content."
+ },
"is_private": {
"type": "boolean",
"description": "Indicates whether the workspace is publicly accessible, or whether it is\nprivate to the members and consequently only visible to members."
diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json
index a6ac3ecd19..ecacd6966c 100644
--- a/plugins/bitbucket-cloud-common/package.json
+++ b/plugins/bitbucket-cloud-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-bitbucket-cloud-common",
- "version": "0.2.29",
+ "version": "0.3.0-next.1",
"description": "Common functionalities for bitbucket-cloud plugins",
"backstage": {
"role": "common-library",
diff --git a/plugins/bitbucket-cloud-common/report.api.md b/plugins/bitbucket-cloud-common/report.api.md
index 833a367c03..c53863b92f 100644
--- a/plugins/bitbucket-cloud-common/report.api.md
+++ b/plugins/bitbucket-cloud-common/report.api.md
@@ -70,6 +70,30 @@ export namespace Events {
html: Models.Link;
}
// (undocumented)
+ export interface RepoChange {
+ // (undocumented)
+ new: T;
+ // (undocumented)
+ old: T;
+ }
+ // (undocumented)
+ export interface RepoChanges {
+ // (undocumented)
+ description?: RepoChange;
+ // (undocumented)
+ full_name?: RepoChange;
+ // (undocumented)
+ language?: RepoChange;
+ // (undocumented)
+ links?: RepoChange<
+ Pick
+ >;
+ // (undocumented)
+ name?: RepoChange;
+ // (undocumented)
+ website?: RepoChange;
+ }
+ // (undocumented)
export interface RepoEvent {
// (undocumented)
actor: Models.Account;
@@ -88,6 +112,11 @@ export namespace Events {
// (undocumented)
push: RepoPush;
}
+ // (undocumented)
+ export interface RepoUpdatedEvent extends RepoEvent {
+ // (undocumented)
+ changes: RepoChanges;
+ }
}
// @public (undocumented)
@@ -106,8 +135,6 @@ export namespace Models {
// (undocumented)
links?: AccountLinks;
// (undocumented)
- username?: string;
- // (undocumented)
uuid?: string;
}
export interface AccountLinks {
@@ -125,6 +152,8 @@ export namespace Models {
// (undocumented)
author?: Author;
// (undocumented)
+ committer?: Committer;
+ // (undocumented)
date?: string;
// (undocumented)
hash?: string;
@@ -163,6 +192,9 @@ export namespace Models {
readonly MergeCommit: 'merge_commit';
readonly Squash: 'squash';
readonly FastForward: 'fast_forward';
+ readonly SquashFastForward: 'squash_fast_forward';
+ readonly RebaseFastForward: 'rebase_fast_forward';
+ readonly RebaseMerge: 'rebase_merge';
};
export type BranchMergeStrategiesEnum =
(typeof BranchMergeStrategiesEnum)[keyof typeof BranchMergeStrategiesEnum];
@@ -195,6 +227,11 @@ export namespace Models {
// (undocumented)
export type CommitFileAttributesEnum =
(typeof CommitFileAttributesEnum)[keyof typeof CommitFileAttributesEnum];
+ export interface Committer extends ModelObject {
+ raw?: string;
+ // (undocumented)
+ user?: Account;
+ }
export interface Link {
// (undocumented)
href?: string;
@@ -416,6 +453,8 @@ export namespace Models {
export interface Workspace extends ModelObject {
// (undocumented)
created_on?: string;
+ forking_mode?: WorkspaceForkingModeEnum;
+ is_privacy_enforced?: boolean;
is_private?: boolean;
// (undocumented)
links?: WorkspaceLinks;
@@ -425,6 +464,12 @@ export namespace Models {
updated_on?: string;
uuid?: string;
}
+ const WorkspaceForkingModeEnum: {
+ readonly AllowForks: 'allow_forks';
+ readonly InternalOnly: 'internal_only';
+ };
+ export type WorkspaceForkingModeEnum =
+ (typeof WorkspaceForkingModeEnum)[keyof typeof WorkspaceForkingModeEnum];
// (undocumented)
export interface WorkspaceLinks {
// (undocumented)
diff --git a/plugins/bitbucket-cloud-common/src/events/index.ts b/plugins/bitbucket-cloud-common/src/events/index.ts
index 53658be032..5058ef5230 100644
--- a/plugins/bitbucket-cloud-common/src/events/index.ts
+++ b/plugins/bitbucket-cloud-common/src/events/index.ts
@@ -30,6 +30,29 @@ export namespace Events {
push: RepoPush;
}
+ /** @public */
+ export interface RepoUpdatedEvent extends RepoEvent {
+ changes: RepoChanges;
+ }
+
+ /** @public */
+ export interface RepoChanges {
+ description?: RepoChange;
+ full_name?: RepoChange;
+ language?: RepoChange;
+ links?: RepoChange<
+ Pick
+ >;
+ name?: RepoChange;
+ website?: RepoChange;
+ }
+
+ /** @public */
+ export interface RepoChange {
+ new: T;
+ old: T;
+ }
+
/** @public */
export interface RepoPush {
changes: Change[];
diff --git a/plugins/bitbucket-cloud-common/src/models/index.ts b/plugins/bitbucket-cloud-common/src/models/index.ts
index 28b1b45e1b..757a181c08 100644
--- a/plugins/bitbucket-cloud-common/src/models/index.ts
+++ b/plugins/bitbucket-cloud-common/src/models/index.ts
@@ -36,7 +36,6 @@ export namespace Models {
created_on?: string;
display_name?: string;
links?: AccountLinks;
- username?: string;
uuid?: string;
}
@@ -67,6 +66,7 @@ export namespace Models {
*/
export interface BaseCommit extends ModelObject {
author?: Author;
+ committer?: Committer;
date?: string;
hash?: string;
message?: string;
@@ -139,6 +139,9 @@ export namespace Models {
MergeCommit: 'merge_commit',
Squash: 'squash',
FastForward: 'fast_forward',
+ SquashFastForward: 'squash_fast_forward',
+ RebaseFastForward: 'rebase_fast_forward',
+ RebaseMerge: 'rebase_merge',
} as const;
/**
@@ -193,6 +196,18 @@ export namespace Models {
export type CommitFileAttributesEnum =
(typeof CommitFileAttributesEnum)[keyof typeof CommitFileAttributesEnum];
+ /**
+ * The committer of a change in a repository
+ * @public
+ */
+ export interface Committer extends ModelObject {
+ /**
+ * The raw committer value from the repository. This may be the only value available if the committer does not match a user in Bitbucket.
+ */
+ raw?: string;
+ user?: Account;
+ }
+
/**
* A link to a resource related to this object.
* @public
@@ -242,6 +257,28 @@ export namespace Models {
values?: Array | Set;
}
+ /**
+ * A paginated list of branches.
+ * @public
+ */
+ export interface PaginatedBranches extends Paginated {
+ /**
+ * The values of the current page.
+ */
+ values?: Set;
+ }
+
+ /**
+ * A paginated list of projects
+ * @public
+ */
+ export interface PaginatedProjects extends Paginated {
+ /**
+ * The values of the current page.
+ */
+ values?: Set;
+ }
+
/**
* A paginated list of repositories.
* @public
@@ -253,17 +290,6 @@ export namespace Models {
values?: Set;
}
- /**
- * A paginated list of projects.
- * @public
- */
- export interface PaginatedProjects extends Paginated {
- /**
- * The values of the current page.
- */
- values?: Set;
- }
-
/**
* A paginated list of workspaces.
* @public
@@ -275,17 +301,6 @@ export namespace Models {
values?: Set;
}
- /**
- * A paginated list of branches.
- * @public
- */
- export interface PaginatedBranches extends Paginated {
- /**
- * The values of the current page.
- */
- values?: Set;
- }
-
/**
* Object describing a user's role on resources like commits or pull requests.
* @public
@@ -571,6 +586,17 @@ export namespace Models {
*/
export interface Workspace extends ModelObject {
created_on?: string;
+ /**
+ * Controls the rules for forking repositories within this workspace.
+ *
+ * * **allow_forks**: unrestricted forking
+ * * **internal_only**: prevents forking of private repositories outside the workspace or to public repositories
+ */
+ forking_mode?: WorkspaceForkingModeEnum;
+ /**
+ * Indicates whether the workspace enforces private content, or whether it allows public content.
+ */
+ is_privacy_enforced?: boolean;
/**
* Indicates whether the workspace is publicly accessible, or whether it is
* private to the members and consequently only visible to members.
@@ -592,6 +618,28 @@ export namespace Models {
uuid?: string;
}
+ /**
+ * Controls the rules for forking repositories within this workspace.
+ *
+ * * **allow_forks**: unrestricted forking
+ * * **internal_only**: prevents forking of private repositories outside the workspace or to public repositories
+ * @public
+ */
+ export const WorkspaceForkingModeEnum = {
+ AllowForks: 'allow_forks',
+ InternalOnly: 'internal_only',
+ } as const;
+
+ /**
+ * Controls the rules for forking repositories within this workspace.
+ *
+ * * **allow_forks**: unrestricted forking
+ * * **internal_only**: prevents forking of private repositories outside the workspace or to public repositories
+ * @public
+ */
+ export type WorkspaceForkingModeEnum =
+ (typeof WorkspaceForkingModeEnum)[keyof typeof WorkspaceForkingModeEnum];
+
/**
* @public
*/
diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md
index be03fd3c88..929fac5677 100644
--- a/plugins/catalog-backend-module-aws/CHANGELOG.md
+++ b/plugins/catalog-backend-module-aws/CHANGELOG.md
@@ -1,5 +1,38 @@
# @backstage/plugin-catalog-backend-module-aws
+## 0.4.11-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-kubernetes-common@0.9.5-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## 0.4.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-kubernetes-common@0.9.4
+
## 0.4.10
### Patch Changes
diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json
index bd81cc19c9..0308b1f6e1 100644
--- a/plugins/catalog-backend-module-aws/package.json
+++ b/plugins/catalog-backend-module-aws/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-aws",
- "version": "0.4.10",
+ "version": "0.4.11-next.1",
"description": "A Backstage catalog backend module that helps integrate towards AWS",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts
index a5f5ce2f6c..de4473a1bc 100644
--- a/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts
+++ b/plugins/catalog-backend-module-aws/src/processors/AwsEKSClusterProcessor.ts
@@ -49,10 +49,10 @@ export class AwsEKSClusterProcessor implements CatalogProcessor {
clusterEntityTransformer?: EksClusterEntityTransformer;
},
): AwsEKSClusterProcessor {
- const awsCredentaislManager =
+ const awsCredentialsManager =
DefaultAwsCredentialsManager.fromConfig(configRoot);
return new AwsEKSClusterProcessor({
- credentialsManager: awsCredentaislManager,
+ credentialsManager: awsCredentialsManager,
...options,
});
}
diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md
index 2a42057bb4..600cb2080c 100644
--- a/plugins/catalog-backend-module-azure/CHANGELOG.md
+++ b/plugins/catalog-backend-module-azure/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-catalog-backend-module-azure
+## 0.3.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## 0.3.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.3.4
### Patch Changes
diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json
index e69deb467b..e8dcea480b 100644
--- a/plugins/catalog-backend-module-azure/package.json
+++ b/plugins/catalog-backend-module-azure/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-azure",
- "version": "0.3.4",
+ "version": "0.3.5-next.1",
"description": "A Backstage catalog backend module that helps integrate towards Azure",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md
index 6f98902af9..3bffb1d42e 100644
--- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md
+++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md
@@ -1,5 +1,31 @@
# @backstage/plugin-catalog-backend-module-backstage-openapi
+## 0.5.2-next.1
+
+### Patch Changes
+
+- d385854: Do not swallow errors; instead allow them to bubble up to the task scheduler for better tracking and logging.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/backend-openapi-utils@0.5.3-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
+## 0.5.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/backend-openapi-utils@0.5.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.5.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json
index d8df10bffa..de8dc971e5 100644
--- a/plugins/catalog-backend-module-backstage-openapi/package.json
+++ b/plugins/catalog-backend-module-backstage-openapi/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-backstage-openapi",
- "version": "0.5.1",
+ "version": "0.5.2-next.1",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "catalog",
diff --git a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts
index bc2a39d5bf..8ce406d3b6 100644
--- a/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts
+++ b/plugins/catalog-backend-module-backstage-openapi/src/InternalOpenApiDocumentationProvider.ts
@@ -232,11 +232,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider {
taskId,
taskInstanceId: uuid.v4(),
});
- try {
- await this.refresh(logger);
- } catch (error) {
- logger.error(`${this.getProviderName()} refresh failed`, error);
- }
+ await this.refresh(logger);
},
});
};
@@ -295,7 +291,7 @@ export class InternalOpenApiDocumentationProvider implements EntityProvider {
// Overwrite baseConfig with options from config file.
const mergedConfig = lodash.merge(baseConfig, configToMerge);
- // Overwite mergedConfig with requiredConfig (i.e., spec.type and spec.definition) to avoid bad configuration.
+ // Overwrite mergedConfig with requiredConfig (i.e., spec.type and spec.definition) to avoid bad configuration.
const documentationEntity = lodash.merge(
mergedConfig,
requiredConfig,
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
index 86830f82bb..c203b575c8 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
+++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
@@ -1,5 +1,43 @@
# @backstage/plugin-catalog-backend-module-bitbucket-cloud
+## 0.4.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.1
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.4.8-next.0
+
+### Patch Changes
+
+- 3bce578: Support Bitbucket Cloud's `repo:updated` events at `BitbucketCloudEntityProvider`.
+
+ To make use of the new event type, you have to configure your webhook or add a new ones
+ that delivers this event type to Backstage similar to `repo:push` before.
+
+ Only `repo:updated` events that modify a repository's URL (e.g., due to a name change)
+ will cause changes (removing the "old", adding the "new" repository).
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.4.7
### Patch Changes
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json
index 55a2fb2d09..eeae1afeb7 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/package.json
+++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud",
- "version": "0.4.7",
+ "version": "0.4.8-next.1",
"description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts
index 5b2f9992c9..6ea92dac96 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.test.ts
@@ -123,6 +123,55 @@ describe('BitbucketCloudEntityProvider', () => {
eventPayload: repoPushEvent,
metadata: { 'x-event-key': 'repo:push' },
};
+ const repoUpdatedEvent: Events.RepoUpdatedEvent = {
+ actor: {
+ type: 'user',
+ },
+ repository: {
+ type: 'repository',
+ full_name: 'test-ws/test-repo-new',
+ links: {
+ html: {
+ href: 'https://bitbucket.org/test-ws/test-repo-new',
+ },
+ },
+ workspace: {
+ type: 'workspace',
+ slug: 'test-ws',
+ },
+ project: {
+ type: 'project',
+ key: 'test-project',
+ },
+ },
+ changes: {
+ name: {
+ new: 'test-repo-new',
+ old: 'test-repo-old',
+ },
+ links: {
+ new: {
+ html: {
+ href: 'https://bitbucket.org/test-ws/test-repo-new',
+ },
+ },
+ old: {
+ html: {
+ href: 'https://bitbucket.org/test-ws/test-repo-old',
+ },
+ },
+ },
+ full_name: {
+ new: 'test-ws/test-repo-new',
+ old: 'test-ws/test-repo-old',
+ },
+ },
+ };
+ const repoUpdatedEventParams = {
+ topic: 'bitbucketCloud.repo:updated',
+ eventPayload: repoUpdatedEvent,
+ metadata: { 'x-event-key': 'repo:updated' },
+ };
const createLocationEntity = (
repoUrl: string,
@@ -705,4 +754,210 @@ describe('BitbucketCloudEntityProvider', () => {
expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0);
expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0);
});
+
+ it('update onRepoUpdated', async () => {
+ const oldModule = createLocationEntity(
+ 'https://bitbucket.org/test-ws/test-repo-old',
+ 'main',
+ 'module/catalog-custom.yaml',
+ );
+ const newModule = createLocationEntity(
+ 'https://bitbucket.org/test-ws/test-repo-new',
+ 'main',
+ 'module/catalog-custom.yaml',
+ );
+
+ const auth = mockServices.auth.mock({
+ getPluginRequestToken: async () => ({ token: 'fake-token' }),
+ });
+ const events = DefaultEventsService.create({ logger });
+ const catalogApi = catalogServiceMock.mock({
+ getEntities: async (
+ request: { filter: Record },
+ options: { token: string },
+ ): Promise<{ items: Entity[] }> => {
+ if (
+ options.token !== 'fake-token' ||
+ request.filter.kind !== 'Location' ||
+ request.filter['metadata.annotations.bitbucket.org/repo-url'] !==
+ 'https://bitbucket.org/test-ws/test-repo-old'
+ ) {
+ return { items: [] };
+ }
+
+ return {
+ items: [oldModule],
+ };
+ },
+ });
+ const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
+ auth,
+ catalogApi,
+ events,
+ logger,
+ schedule,
+ })[0];
+
+ server.use(
+ rest.get(
+ `https://api.bitbucket.org/2.0/workspaces/test-ws/search/code`,
+ (req, res, ctx) => {
+ const query = req.url.searchParams.get('search_query');
+ if (!query || !query.includes('repo:test-repo-new')) {
+ return res(ctx.json({ values: [] }));
+ }
+
+ const response = {
+ values: [
+ {
+ path_matches: [
+ {
+ match: true,
+ text: 'catalog-custom.yaml',
+ },
+ ],
+ file: {
+ type: 'commit_file',
+ path: 'module/catalog-custom.yaml',
+ commit: {
+ repository: {
+ slug: 'test-repo-new',
+ project: {
+ key: 'test-project',
+ },
+ mainbranch: {
+ name: 'main',
+ },
+ links: {
+ html: {
+ href: 'https://bitbucket.org/test-ws/test-repo-new',
+ },
+ },
+ },
+ },
+ },
+ },
+ ],
+ };
+ return res(ctx.json(response));
+ },
+ ),
+ );
+
+ await provider.connect(entityProviderConnection);
+ await events.publish(repoUpdatedEventParams);
+
+ const addedEntities = [
+ {
+ entity: newModule,
+ locationKey: 'bitbucketCloud-provider:myProvider',
+ },
+ ];
+ const removedEntities = [
+ {
+ entity: oldModule,
+ locationKey: 'bitbucketCloud-provider:myProvider',
+ },
+ ];
+
+ expect(entityProviderConnection.refresh).toHaveBeenCalledTimes(0);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({
+ type: 'delta',
+ added: addedEntities,
+ removed: removedEntities,
+ });
+ });
+
+ it('no onRepoUpdated update on non-matching workspace slug', async () => {
+ const auth = mockServices.auth.mock();
+ const catalogApi = catalogServiceMock();
+ jest.spyOn(catalogApi, 'refreshEntity');
+ const events = DefaultEventsService.create({ logger });
+ const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
+ auth,
+ catalogApi,
+ events,
+ logger,
+ schedule,
+ })[0];
+
+ await provider.connect(entityProviderConnection);
+ await events.publish({
+ ...repoUpdatedEventParams,
+ eventPayload: {
+ ...repoUpdatedEventParams.eventPayload,
+ repository: {
+ ...repoUpdatedEventParams.eventPayload.repository,
+ workspace: {
+ ...repoUpdatedEventParams.eventPayload.repository.workspace,
+ slug: 'not-matching',
+ },
+ },
+ },
+ });
+
+ expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0);
+ });
+
+ it('no onRepoUpdated update on non-matching repo slug', async () => {
+ const auth = mockServices.auth.mock();
+ const catalogApi = catalogServiceMock();
+ jest.spyOn(catalogApi, 'refreshEntity');
+ const events = DefaultEventsService.create({ logger });
+ const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
+ auth,
+ catalogApi,
+ events,
+ logger,
+ schedule,
+ })[0];
+
+ await provider.connect(entityProviderConnection);
+ await events.publish({
+ ...repoUpdatedEventParams,
+ eventPayload: {
+ ...repoUpdatedEventParams.eventPayload,
+ repository: {
+ ...repoUpdatedEventParams.eventPayload.repository,
+ full_name: `${repoUpdatedEventParams.eventPayload.repository.workspace.slug}/not-matching`,
+ },
+ },
+ });
+
+ expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0);
+ });
+
+ it('no onRepoUpdated update on non-relevant repo update', async () => {
+ const auth = mockServices.auth.mock();
+ const catalogApi = catalogServiceMock();
+ jest.spyOn(catalogApi, 'refreshEntity');
+ const events = DefaultEventsService.create({ logger });
+ const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
+ auth,
+ catalogApi,
+ events,
+ logger,
+ schedule,
+ })[0];
+
+ await provider.connect(entityProviderConnection);
+ await events.publish({
+ ...repoUpdatedEventParams,
+ eventPayload: {
+ ...repoUpdatedEventParams.eventPayload,
+ changes: {
+ description: {
+ new: 'New description',
+ old: 'Old description',
+ },
+ },
+ },
+ });
+
+ expect(catalogApi.refreshEntity).toHaveBeenCalledTimes(0);
+ expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(0);
+ });
});
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts
index 5beb98ac08..88d0cb80ef 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts
+++ b/plugins/catalog-backend-module-bitbucket-cloud/src/providers/BitbucketCloudEntityProvider.ts
@@ -48,6 +48,7 @@ import * as uuid from 'uuid';
const DEFAULT_BRANCH = 'master';
const TOPIC_REPO_PUSH = 'bitbucketCloud.repo:push';
+const TOPIC_REPO_UPDATED = 'bitbucketCloud.repo:updated';
/** @public */
export const ANNOTATION_BITBUCKET_CLOUD_REPO_URL = 'bitbucket.org/repo-url';
@@ -186,13 +187,17 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
await this.events.subscribe({
id: this.getProviderName(),
- topics: [TOPIC_REPO_PUSH],
+ topics: [TOPIC_REPO_PUSH, TOPIC_REPO_UPDATED],
onEvent: async params => {
- if (params.topic !== TOPIC_REPO_PUSH) {
- return;
+ if (params.topic === TOPIC_REPO_PUSH) {
+ await this.onRepoPush(params.eventPayload as Events.RepoPushEvent);
}
- await this.onRepoPush(params.eventPayload as Events.RepoPushEvent);
+ if (params.topic === TOPIC_REPO_UPDATED) {
+ await this.onRepoUpdated(
+ params.eventPayload as Events.RepoUpdatedEvent,
+ );
+ }
},
});
}
@@ -217,12 +222,12 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
);
}
- private enhanceEvent(event: Events.RepoPushEvent): void {
+ private enhanceEvent(event: Events.RepoEvent): void {
// add missing slug
event.repository.slug = event.repository.full_name!.split('/', 2)[1];
}
- async onRepoPush(event: Events.RepoPushEvent): Promise {
+ private shouldProcessEvent(event: Events.RepoEvent): boolean {
if (!this.connection) {
throw new Error('Not initialized');
}
@@ -230,10 +235,18 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
this.enhanceEvent(event);
if (event.repository.workspace.slug !== this.config.workspace) {
- return;
+ return false;
}
if (!this.matchesFilters(event.repository)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ async onRepoPush(event: Events.RepoPushEvent): Promise {
+ if (!this.shouldProcessEvent(event)) {
return;
}
@@ -248,12 +261,43 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
// Hence, we will just trigger a refresh for catalog file(s) within the repository
// if we get notified about changes there.
- const targets = await this.findCatalogFiles(repoSlug);
-
+ const expected = await this.findCatalogFiles(repoSlug);
const existing = await this.findExistingLocations(repoUrl);
+ await this.updateForChanges(expected, existing);
+ }
+
+ private async onRepoUpdated(event: Events.RepoUpdatedEvent): Promise {
+ if (!this.shouldProcessEvent(event)) {
+ return;
+ }
+
+ // The event is triggered on every change to the repository.
+ // We are only interested in changes that affect the repository URL.
+ // This is the case when the repository name (slug), or the full name changes.
+ if (!event.changes.links?.old.html?.href) {
+ return;
+ }
+
+ const repoSlug = event.repository.slug!;
+ const repoUrl = event.repository.links!.html!.href!;
+ const oldRepoUrl = event.changes.links.old.html.href;
+ this.logger.info(
+ `handle repo:updated event for ${repoUrl}, previous: ${oldRepoUrl}`,
+ );
+
+ const expected = await this.findCatalogFiles(repoSlug);
+ const existing = await this.findExistingLocations(oldRepoUrl);
+
+ await this.updateForChanges(expected, existing);
+ }
+
+ private async updateForChanges(
+ expected: IngestionTarget[],
+ existing: LocationEntity[],
+ ): Promise {
const added: DeferredEntity[] = this.toDeferredEntities(
- targets.filter(
+ expected.filter(
// All Locations are managed by this provider and only have `target`, never `targets`.
// All URLs (fileUrl, target) are created using `BitbucketCloudEntityProvider.toUrl`.
// Hence, we can keep the comparison simple and don't need to handle different
@@ -265,7 +309,7 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
const stillExisting: LocationEntity[] = [];
const removed: DeferredEntity[] = [];
existing.forEach(item => {
- if (targets.find(value => value.fileUrl === item.spec.target)) {
+ if (expected.find(value => value.fileUrl === item.spec.target)) {
stillExisting.push(item);
} else {
removed.push({
@@ -275,14 +319,17 @@ export class BitbucketCloudEntityProvider implements EntityProvider {
}
});
- const promises: Promise[] = [
- this.connection.refresh({
- keys: stillExisting.map(entity => `url:${entity.spec.target}`),
- }),
- ];
+ const promises: Promise[] =
+ stillExisting.length === 0
+ ? []
+ : [
+ this.connection!.refresh({
+ keys: stillExisting.map(entity => `url:${entity.spec.target}`),
+ }),
+ ];
if (added.length > 0 || removed.length > 0) {
- const connection = this.connection;
+ const connection = this.connection!;
promises.push(
connection.applyMutation({
type: 'delta',
diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
index 1b90eab4d9..18ea70c09b 100644
--- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
+++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
@@ -1,5 +1,35 @@
# @backstage/plugin-catalog-backend-module-bitbucket-server
+## 0.4.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.4.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.4.0
### Minor Changes
diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json
index 1b93ec85db..0c3b601cb1 100644
--- a/plugins/catalog-backend-module-bitbucket-server/package.json
+++ b/plugins/catalog-backend-module-bitbucket-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-bitbucket-server",
- "version": "0.4.0",
+ "version": "0.4.1-next.1",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "catalog",
diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md
index 22c2d8f505..3ccb395b27 100644
--- a/plugins/catalog-backend-module-gcp/CHANGELOG.md
+++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-catalog-backend-module-gcp
+## 0.3.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-kubernetes-common@0.9.5-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+
+## 0.3.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-kubernetes-common@0.9.4
+
## 0.3.7
### Patch Changes
diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json
index cb97d4507c..8b70f03bf0 100644
--- a/plugins/catalog-backend-module-gcp/package.json
+++ b/plugins/catalog-backend-module-gcp/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-gcp",
- "version": "0.3.7",
+ "version": "0.3.8-next.1",
"description": "A Backstage catalog backend module that helps integrate towards GCP",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md
index 2e71fa4640..66de538a1a 100644
--- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md
+++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/plugin-catalog-backend-module-gerrit
+## 0.3.2-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## 0.3.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.3.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json
index 2759ae8f55..c907cb6bfd 100644
--- a/plugins/catalog-backend-module-gerrit/package.json
+++ b/plugins/catalog-backend-module-gerrit/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-gerrit",
- "version": "0.3.1",
+ "version": "0.3.2-next.1",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "catalog",
diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md
index a67df6d78b..7a8de4a045 100644
--- a/plugins/catalog-backend-module-github-org/CHANGELOG.md
+++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/plugin-catalog-backend-module-github-org
+## 0.3.10-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-catalog-backend-module-github@0.9.0-next.1
+ - @backstage/config@1.3.2
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.3.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend-module-github@0.9.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+
## 0.3.9
### Patch Changes
diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json
index 2f8b2f6abd..c445e926c6 100644
--- a/plugins/catalog-backend-module-github-org/package.json
+++ b/plugins/catalog-backend-module-github-org/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-github-org",
- "version": "0.3.9",
+ "version": "0.3.10-next.1",
"description": "The github-org backend module for the catalog plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-github-org/src/index.ts b/plugins/catalog-backend-module-github-org/src/index.ts
index 03d22b1a79..3c548c4bdb 100644
--- a/plugins/catalog-backend-module-github-org/src/index.ts
+++ b/plugins/catalog-backend-module-github-org/src/index.ts
@@ -26,7 +26,7 @@ export {
} from './module';
/**
*
- * TODO(djamaile): GithubMultiOrgEntityProvider should be mirgated over to this module.
+ * TODO(djamaile): GithubMultiOrgEntityProvider should be migrated over to this module.
* Afterwards, mark it as deprecated in catalog-backend-module-github and export them there from this module.
*/
export {
diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md
index a60e33a4da..9e28f853f9 100644
--- a/plugins/catalog-backend-module-github/CHANGELOG.md
+++ b/plugins/catalog-backend-module-github/CHANGELOG.md
@@ -1,5 +1,39 @@
# @backstage/plugin-catalog-backend-module-github
+## 0.9.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@2.0.0-next.1
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.9.0-next.0
+
+### Minor Changes
+
+- ff335e5: **BREAKING** The `GithubLocationAnalyzer` now requires the `AuthService` and the `CatalogService` when being constructed and the `TokenManger` has been removed.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/plugin-catalog-backend@1.32.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.8.0
### Minor Changes
diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json
index 42c56abf36..84fd003bcb 100644
--- a/plugins/catalog-backend-module-github/package.json
+++ b/plugins/catalog-backend-module-github/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-github",
- "version": "0.8.0",
+ "version": "0.9.0-next.1",
"description": "A Backstage catalog backend module that helps integrate towards GitHub",
"backstage": {
"role": "backend-plugin-module",
@@ -51,7 +51,6 @@
"test": "backstage-cli package test"
},
"dependencies": {
- "@backstage/backend-common": "^0.25.0",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/catalog-client": "workspace:^",
"@backstage/catalog-model": "workspace:^",
diff --git a/plugins/catalog-backend-module-github/report.api.md b/plugins/catalog-backend-module-github/report.api.md
index 8cd87f646a..691cf271b4 100644
--- a/plugins/catalog-backend-module-github/report.api.md
+++ b/plugins/catalog-backend-module-github/report.api.md
@@ -6,11 +6,10 @@
import { AnalyzeOptions } from '@backstage/plugin-catalog-node';
import { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
-import { CatalogApi } from '@backstage/catalog-client';
import { CatalogProcessor } from '@backstage/plugin-catalog-node';
import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node';
+import { CatalogService } from '@backstage/plugin-catalog-node';
import { Config } from '@backstage/config';
-import { DiscoveryService } from '@backstage/backend-plugin-api';
import { Entity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-node';
import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
@@ -26,7 +25,6 @@ import { SchedulerService } from '@backstage/backend-plugin-api';
import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-node';
-import { TokenManager } from '@backstage/backend-common';
import { UserEntity } from '@backstage/catalog-model';
// @public
@@ -127,11 +125,9 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer {
// @public (undocumented)
export type GithubLocationAnalyzerOptions = {
config: Config;
- discovery: DiscoveryService;
- tokenManager?: TokenManager;
- auth?: AuthService;
+ auth: AuthService;
githubCredentialsProvider?: GithubCredentialsProvider;
- catalog?: CatalogApi;
+ catalog: CatalogService;
};
// @public
diff --git a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts
index 25a19ba1ff..9fccfd57eb 100644
--- a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts
+++ b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts
@@ -38,15 +38,12 @@ import {
mockServices,
} from '@backstage/backend-test-utils';
import { setupServer } from 'msw/node';
-import { http, HttpResponse } from 'msw';
+import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
const server = setupServer();
describe('GithubLocationAnalyzer', () => {
- const mockDiscovery = mockServices.discovery.mock({
- getBaseUrl: async () => 'http://localhost:7007',
- });
- const mockAuthService = mockServices.auth.mock({
+ const auth = mockServices.auth.mock({
getPluginRequestToken: async () => ({ token: 'abc123' }),
});
const config = mockServices.rootConfig({
@@ -56,49 +53,48 @@ describe('GithubLocationAnalyzer', () => {
},
},
});
+ const catalog = catalogServiceMock.mock({
+ addLocation: jest.fn(async location => ({
+ location: {
+ id: 'test',
+ target: location.target,
+ type: location.type ?? 'url',
+ },
+ exists: false,
+ entities: [
+ {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Location',
+ metadata: {
+ name: 'test-entity',
+ },
+ spec: {
+ type: 'url',
+ target: 'whatever',
+ },
+ },
+ {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ title: 'Test Entity',
+ name: 'test-entity-2',
+ description: 'The expected description 2',
+ },
+ spec: {
+ type: 'some-type',
+ lifecycle: 'experimental',
+ owner: 'someone',
+ },
+ },
+ ],
+ })),
+ });
registerMswTestHooks(server);
beforeEach(() => {
- server.use(
- http.post('http://localhost:7007/locations', () =>
- HttpResponse.json(
- {
- location: 'test',
- exists: false,
- entities: [
- {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Location',
- metadata: {
- name: 'test-entity',
- },
- spec: {
- type: 'url',
- target: 'whatever',
- },
- },
- {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Component',
- metadata: {
- title: 'Test Entity',
- name: 'test-entity-2',
- description: 'The expected description 2',
- },
- spec: {
- type: 'some-type',
- lifecycle: 'experimental',
- owner: 'someone',
- },
- },
- ],
- },
- { status: 201 },
- ),
- ),
- );
-
+ jest.clearAllMocks();
octokit.repos.get.mockResolvedValue({
data: { default_branch: 'my_default_branch' },
});
@@ -114,11 +110,8 @@ describe('GithubLocationAnalyzer', () => {
return Promise.reject();
});
- const analyzer = new GithubLocationAnalyzer({
- discovery: mockDiscovery,
- auth: mockAuthService,
- config,
- });
+ const analyzer = new GithubLocationAnalyzer({ catalog, auth, config });
+
const result = await analyzer.analyze({
url: 'https://github.com/foo/bar',
});
@@ -141,11 +134,8 @@ describe('GithubLocationAnalyzer', () => {
return Promise.reject();
});
- const analyzer = new GithubLocationAnalyzer({
- discovery: mockDiscovery,
- auth: mockAuthService,
- config,
- });
+ const analyzer = new GithubLocationAnalyzer({ catalog, auth, config });
+
const result = await analyzer.analyze({
url: 'https://github.com/foo/bar',
catalogFilename: 'anvil.yaml',
@@ -167,11 +157,8 @@ describe('GithubLocationAnalyzer', () => {
return Promise.reject();
});
- const analyzer = new GithubLocationAnalyzer({
- discovery: mockDiscovery,
- auth: mockAuthService,
- config,
- });
+ const analyzer = new GithubLocationAnalyzer({ catalog, auth, config });
+
const result = await analyzer.analyze({
url: 'https://github.com/foo/bar',
catalogFilename: '.gitignore',
diff --git a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts
index cbf43287a3..6a073394ad 100644
--- a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts
+++ b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
import {
DefaultGithubCredentialsProvider,
GithubCredentialsProvider,
@@ -26,46 +25,36 @@ import { isEmpty, trimEnd } from 'lodash';
import parseGitUrl from 'git-url-parse';
import {
AnalyzeOptions,
+ CatalogService,
ScmLocationAnalyzer,
} from '@backstage/plugin-catalog-node';
-import {
- TokenManager,
- createLegacyAuthAdapters,
-} from '@backstage/backend-common';
import { Config } from '@backstage/config';
-import { AuthService, DiscoveryService } from '@backstage/backend-plugin-api';
+import { AuthService } from '@backstage/backend-plugin-api';
import { extname } from 'path';
/** @public */
export type GithubLocationAnalyzerOptions = {
config: Config;
- discovery: DiscoveryService;
- tokenManager?: TokenManager;
- auth?: AuthService;
+ auth: AuthService;
githubCredentialsProvider?: GithubCredentialsProvider;
- catalog?: CatalogApi;
+ catalog: CatalogService;
};
/** @public */
export class GithubLocationAnalyzer implements ScmLocationAnalyzer {
- private readonly catalogClient: CatalogApi;
+ private readonly catalogClient: CatalogService;
private readonly githubCredentialsProvider: GithubCredentialsProvider;
private readonly integrations: ScmIntegrationRegistry;
private readonly auth: AuthService;
constructor(options: GithubLocationAnalyzerOptions) {
- this.catalogClient =
- options.catalog ?? new CatalogClient({ discoveryApi: options.discovery });
+ this.catalogClient = options.catalog;
this.integrations = ScmIntegrations.fromConfig(options.config);
this.githubCredentialsProvider =
options.githubCredentialsProvider ||
DefaultGithubCredentialsProvider.fromIntegrations(this.integrations);
- this.auth = createLegacyAuthAdapters({
- auth: options.auth,
- discovery: options.discovery,
- tokenManager: options.tokenManager,
- }).auth;
+ this.auth = options.auth;
}
supports(url: string) {
@@ -115,11 +104,6 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer {
});
const defaultBranch = repoInformation.data.default_branch;
- const { token: serviceToken } = await this.auth.getPluginRequestToken({
- onBehalfOf: await this.auth.getOwnServiceCredentials(),
- targetPluginId: 'catalog',
- });
-
const result = await Promise.all(
searchResult.data.items
.map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`)
@@ -130,7 +114,7 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer {
target,
dryRun: true,
},
- { token: serviceToken },
+ { credentials: await this.auth.getOwnServiceCredentials() },
);
return addLocationResult.entities.map(e => ({
location: { type: 'url', target },
diff --git a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts
index bc10b04802..4ae5adbb31 100644
--- a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts
+++ b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts
@@ -21,8 +21,8 @@ import {
import {
catalogAnalysisExtensionPoint,
catalogProcessingExtensionPoint,
- catalogServiceRef,
} from '@backstage/plugin-catalog-node/alpha';
+import { catalogServiceRef } from '@backstage/plugin-catalog-node';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { GithubEntityProvider } from '../providers/GithubEntityProvider';
import { GithubLocationAnalyzer } from '../analyzers/GithubLocationAnalyzer';
@@ -42,7 +42,6 @@ export const githubCatalogModule = createBackendModule({
auth: coreServices.auth,
catalogProcessing: catalogProcessingExtensionPoint,
config: coreServices.rootConfig,
- discovery: coreServices.discovery,
events: eventsServiceRef,
logger: coreServices.logger,
scheduler: coreServices.scheduler,
@@ -55,13 +54,11 @@ export const githubCatalogModule = createBackendModule({
logger,
scheduler,
catalogAnalyzers,
- discovery,
auth,
catalog,
}) {
catalogAnalyzers.addScmLocationAnalyzer(
new GithubLocationAnalyzer({
- discovery,
config,
auth,
catalog,
diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md
index a6924117f3..64f599f3a0 100644
--- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md
+++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-catalog-backend-module-gitlab-org
+## 0.2.9-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.2.9-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
## 0.2.8
### Patch Changes
diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json
index cfb8cb3d3f..39f1864944 100644
--- a/plugins/catalog-backend-module-gitlab-org/package.json
+++ b/plugins/catalog-backend-module-gitlab-org/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-gitlab-org",
- "version": "0.2.8",
+ "version": "0.2.9-next.1",
"description": "The gitlab-org backend module for the catalog plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md
index 86ff9cece9..44623a8405 100644
--- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md
+++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md
@@ -1,5 +1,34 @@
# @backstage/plugin-catalog-backend-module-gitlab
+## 0.6.6-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.6.6-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.6.5
### Patch Changes
diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json
index 640fdede20..10f5c92b8b 100644
--- a/plugins/catalog-backend-module-gitlab/package.json
+++ b/plugins/catalog-backend-module-gitlab/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-gitlab",
- "version": "0.6.5",
+ "version": "0.6.6-next.1",
"description": "A Backstage catalog backend module that helps integrate towards GitLab",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-gitlab/src/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts
index 864eca7dfe..23b03ec366 100644
--- a/plugins/catalog-backend-module-gitlab/src/lib/types.ts
+++ b/plugins/catalog-backend-module-gitlab/src/lib/types.ts
@@ -256,7 +256,7 @@ export type GitlabProviderConfig = {
membership?: boolean;
/**
- * Optional comma seperated list of topics to filter projects by, as specified in the GitLab API documentation:
+ * Optional comma separated list of topics to filter projects by, as specified in the GitLab API documentation:
* https://docs.gitlab.com/api/projects/#list-projects
*/
topics?: string;
diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
index d9d48599a7..988e281d76 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
+++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
@@ -1,5 +1,41 @@
# @backstage/plugin-catalog-backend-module-incremental-ingestion
+## 0.7.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@2.0.0-next.1
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.7.0-next.0
+
+### Minor Changes
+
+- 10f693c: **BREAKING** Removed support for the legacy backend, please [migrate to the new backend system](https://github.com/backstage/backstage/tree/v1.38.0/plugins/catalog-backend-module-incremental-ingestion#installation). Also, if you were importing from the `/alpha` export of this package, you should remove the `/alpha` part.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/plugin-catalog-backend@1.32.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-common@0.8.4
+
## 0.6.5
### Patch Changes
diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json
index bb5222ad0c..fdf6641505 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/package.json
+++ b/plugins/catalog-backend-module-incremental-ingestion/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-incremental-ingestion",
- "version": "0.6.5",
+ "version": "0.7.0-next.1",
"description": "An entity provider for streaming large asset sources into the catalog",
"backstage": {
"role": "backend-plugin-module",
@@ -22,16 +22,12 @@
"license": "Apache-2.0",
"exports": {
".": "./src/index.ts",
- "./alpha": "./src/alpha.ts",
"./package.json": "./package.json"
},
"main": "src/index.ts",
"types": "src/index.ts",
"typesVersions": {
"*": {
- "alpha": [
- "src/alpha.ts"
- ],
"package.json": [
"package.json"
]
@@ -62,7 +58,6 @@
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/types": "workspace:^",
"@opentelemetry/api": "^1.9.0",
- "@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
"knex": "^3.0.0",
@@ -72,6 +67,7 @@
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
+ "@types/express": "^4.17.6",
"@types/luxon": "^3.0.0"
}
}
diff --git a/plugins/catalog-backend-module-incremental-ingestion/report-alpha.api.md b/plugins/catalog-backend-module-incremental-ingestion/report-alpha.api.md
deleted file mode 100644
index 30717e8752..0000000000
--- a/plugins/catalog-backend-module-incremental-ingestion/report-alpha.api.md
+++ /dev/null
@@ -1,13 +0,0 @@
-## API Report File for "@backstage/plugin-catalog-backend-module-incremental-ingestion"
-
-> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
-
-```ts
-import { BackendFeature } from '@backstage/backend-plugin-api';
-
-// @alpha @deprecated (undocumented)
-const _feature: BackendFeature;
-export default _feature;
-
-// (No @packageDocumentation comment for this package)
-```
diff --git a/plugins/catalog-backend-module-incremental-ingestion/report.api.md b/plugins/catalog-backend-module-incremental-ingestion/report.api.md
index df77a254c8..b1fac447b2 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/report.api.md
+++ b/plugins/catalog-backend-module-incremental-ingestion/report.api.md
@@ -4,21 +4,12 @@
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
-import { CatalogBuilder } from '@backstage/plugin-catalog-backend';
-import { DatabaseService } from '@backstage/backend-plugin-api';
import type { DeferredEntity } from '@backstage/plugin-catalog-node';
import { EventParams } from '@backstage/plugin-events-node';
-import { EventSubscriber } from '@backstage/plugin-events-node';
import { ExtensionPoint } from '@backstage/backend-plugin-api';
import { HumanDuration } from '@backstage/types';
import { IncrementalEntityProvider as IncrementalEntityProvider_2 } from '@backstage/plugin-catalog-backend-module-incremental-ingestion';
import { IncrementalEntityProviderOptions as IncrementalEntityProviderOptions_2 } from '@backstage/plugin-catalog-backend-module-incremental-ingestion';
-import { PermissionsService } from '@backstage/backend-plugin-api';
-import { RootConfigService } from '@backstage/backend-plugin-api';
-import { RootLoggerService } from '@backstage/backend-plugin-api';
-import { Router } from 'express';
-import { SchedulerService } from '@backstage/backend-plugin-api';
-import { UrlReaderService } from '@backstage/backend-plugin-api';
// @public
const catalogModuleIncrementalIngestionEntityProvider: BackendFeature;
@@ -37,23 +28,6 @@ export type EntityIteratorResult =
cursor?: T;
};
-// @public @deprecated (undocumented)
-export class IncrementalCatalogBuilder {
- // (undocumented)
- addIncrementalEntityProvider(
- provider: IncrementalEntityProvider,
- options: IncrementalEntityProviderOptions,
- ): EventSubscriber;
- // (undocumented)
- build(): Promise<{
- incrementalAdminRouter: Router;
- }>;
- static create(
- env: PluginEnvironment,
- builder: CatalogBuilder,
- ): Promise;
-}
-
// @public
export type IncrementalEntityEventResult =
| {
@@ -101,14 +75,4 @@ export interface IncrementalIngestionProviderExtensionPoint {
// @public
export const incrementalIngestionProvidersExtensionPoint: ExtensionPoint;
-
-// @public (undocumented)
-export type PluginEnvironment = {
- logger: RootLoggerService;
- database: DatabaseService;
- scheduler: SchedulerService;
- config: RootConfigService;
- reader: UrlReaderService;
- permissions: PermissionsService;
-};
```
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/index.ts b/plugins/catalog-backend-module-incremental-ingestion/src/index.ts
index 73c0e33946..2772da2041 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/index.ts
+++ b/plugins/catalog-backend-module-incremental-ingestion/src/index.ts
@@ -22,11 +22,9 @@
export { default } from './module';
export * from './module';
-export * from './service';
export {
type EntityIteratorResult,
type IncrementalEntityEventResult,
type IncrementalEntityProvider,
type IncrementalEntityProviderOptions,
- type PluginEnvironment,
} from './types';
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts
index ed57ad9b46..e35b671634 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts
+++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/catalogModuleIncrementalIngestionEntityProvider.test.ts
@@ -72,6 +72,5 @@ describe('catalogModuleIncrementalIngestionEntityProvider', () => {
expect(addEntityProvider.mock.calls[0][0].getProviderName()).toBe(
'provider1',
);
- expect(httpRouterMock.use).toHaveBeenCalledTimes(1);
});
});
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/service/IncrementalCatalogBuilder.ts b/plugins/catalog-backend-module-incremental-ingestion/src/service/IncrementalCatalogBuilder.ts
deleted file mode 100644
index 6ea2f10a64..0000000000
--- a/plugins/catalog-backend-module-incremental-ingestion/src/service/IncrementalCatalogBuilder.ts
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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 {
- IncrementalEntityProvider,
- IncrementalEntityProviderOptions,
- PluginEnvironment,
-} from '../types';
-import { CatalogBuilder as CoreCatalogBuilder } from '@backstage/plugin-catalog-backend';
-import { createDeferred } from '@backstage/types';
-import { Duration } from 'luxon';
-import { Knex } from 'knex';
-import { IncrementalIngestionEngine } from '../engine/IncrementalIngestionEngine';
-import { applyDatabaseMigrations } from '../database/migrations';
-import { IncrementalIngestionDatabaseManager } from '../database/IncrementalIngestionDatabaseManager';
-import { IncrementalProviderRouter } from '../router/routes';
-import { EventParams, EventSubscriber } from '@backstage/plugin-events-node';
-
-/**
- * @public
- * @deprecated Please migrate to the new backend system and import `@backstage/plugin-catalog-backend-module-incremental-ingestion` as a module, and add providers to the `incrementalIngestionProvidersExtensionPoint` instead
- */
-export class IncrementalCatalogBuilder {
- /**
- * Creates the incremental catalog builder, which extends the regular catalog builder.
- * @param env - PluginEnvironment
- * @param builder - CatalogBuilder
- * @returns IncrementalCatalogBuilder
- */
- static async create(env: PluginEnvironment, builder: CoreCatalogBuilder) {
- const client = await env.database.getClient();
- const manager = new IncrementalIngestionDatabaseManager({ client });
- return new IncrementalCatalogBuilder(env, builder, client, manager);
- }
-
- private ready = createDeferred();
-
- private constructor(
- private env: PluginEnvironment,
- private builder: CoreCatalogBuilder,
- private client: Knex,
- private manager: IncrementalIngestionDatabaseManager,
- ) {}
-
- async build() {
- await applyDatabaseMigrations(this.client);
- this.ready.resolve();
-
- const routerLogger = this.env.logger.child({
- router: 'IncrementalProviderAdmin',
- });
-
- const incrementalAdminRouter = await new IncrementalProviderRouter(
- this.manager,
- routerLogger,
- ).createRouter();
-
- return { incrementalAdminRouter };
- }
-
- addIncrementalEntityProvider(
- provider: IncrementalEntityProvider,
- options: IncrementalEntityProviderOptions,
- ): EventSubscriber {
- const { burstInterval, burstLength, restLength } = options;
- const { logger: catalogLogger, scheduler } = this.env;
- const ready = this.ready;
-
- const manager = this.manager;
-
- let engine: IncrementalIngestionEngine;
-
- this.builder.addEntityProvider({
- getProviderName: provider.getProviderName.bind(provider),
- async connect(connection) {
- const logger = catalogLogger.child({
- entityProvider: provider.getProviderName(),
- });
-
- engine = new IncrementalIngestionEngine({
- ...options,
- ready,
- manager,
- logger,
- provider,
- restLength,
- connection,
- });
-
- let frequency = Duration.isDuration(burstInterval)
- ? burstInterval
- : Duration.fromObject(burstInterval);
- if (frequency.as('milliseconds') < 5000) {
- frequency = Duration.fromObject({ seconds: 5 }); // don't let it be silly low, to not overload the scheduler
- }
-
- let length = Duration.isDuration(burstLength)
- ? burstLength
- : Duration.fromObject(burstLength);
- length = length.plus(Duration.fromObject({ minutes: 1 })); // some margin from the actual completion
-
- await scheduler.scheduleTask({
- id: provider.getProviderName(),
- fn: engine.taskFn.bind(engine),
- frequency,
- timeout: length,
- });
- },
- });
-
- return {
- onEvent: (params: EventParams) => engine.onEvent(params),
- supportsEventTopics() {
- return engine.supportsEventTopics();
- },
- };
- }
-}
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/types.ts b/plugins/catalog-backend-module-incremental-ingestion/src/types.ts
index 2c951aea07..75aecf8fa5 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/types.ts
+++ b/plugins/catalog-backend-module-incremental-ingestion/src/types.ts
@@ -15,14 +15,8 @@
*/
import {
- DatabaseService,
LoggerService,
- PermissionsService,
- RootConfigService,
- RootLoggerService,
- SchedulerService,
SchedulerServiceTaskFunction,
- UrlReaderService,
} from '@backstage/backend-plugin-api';
import type {
DeferredEntity,
@@ -184,16 +178,6 @@ export interface IncrementalEntityProviderOptions {
rejectEmptySourceCollections?: boolean;
}
-/** @public */
-export type PluginEnvironment = {
- logger: RootLoggerService;
- database: DatabaseService;
- scheduler: SchedulerService;
- config: RootConfigService;
- reader: UrlReaderService;
- permissions: PermissionsService;
-};
-
export interface IterationEngine {
taskFn: SchedulerServiceTaskFunction;
}
diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md
index aaa6f61a5e..cc7072e891 100644
--- a/plugins/catalog-backend-module-ldap/CHANGELOG.md
+++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md
@@ -1,5 +1,31 @@
# @backstage/plugin-catalog-backend-module-ldap
+## 0.11.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## 0.11.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.11.4
### Patch Changes
diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json
index 81c787ab5c..bd04eb70c3 100644
--- a/plugins/catalog-backend-module-ldap/package.json
+++ b/plugins/catalog-backend-module-ldap/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-ldap",
- "version": "0.11.4",
+ "version": "0.11.5-next.1",
"description": "A Backstage catalog backend module that helps integrate towards LDAP",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md
index 70232b092d..73ba915c4a 100644
--- a/plugins/catalog-backend-module-logs/CHANGELOG.md
+++ b/plugins/catalog-backend-module-logs/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/plugin-catalog-backend-module-logs
+## 0.1.10-next.1
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@2.0.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.1.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.32.2-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
## 0.1.9
### Patch Changes
diff --git a/plugins/catalog-backend-module-logs/catalog-info.yaml b/plugins/catalog-backend-module-logs/catalog-info.yaml
index f2223174c3..f2dd90ae96 100644
--- a/plugins/catalog-backend-module-logs/catalog-info.yaml
+++ b/plugins/catalog-backend-module-logs/catalog-info.yaml
@@ -3,7 +3,7 @@ kind: Component
metadata:
name: backstage-plugin-catalog-backend-module-logs
title: '@backstage/plugin-catalog-backend-module-logs'
- description: A module that subscribes to catalog releated events and logs them.
+ description: A module that subscribes to catalog related events and logs them.
spec:
lifecycle: experimental
type: backstage-backend-plugin-module
diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json
index 833e1f4d15..27a5a04766 100644
--- a/plugins/catalog-backend-module-logs/package.json
+++ b/plugins/catalog-backend-module-logs/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-catalog-backend-module-logs",
- "version": "0.1.9",
- "description": "A module that subscribes to catalog releated events and logs them.",
+ "version": "0.1.10-next.1",
+ "description": "A module that subscribes to catalog related events and logs them.",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "catalog",
diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md
index 6162f66192..f4ea154223 100644
--- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md
+++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md
@@ -1,5 +1,32 @@
# @backstage/plugin-catalog-backend-module-msgraph
+## 0.7.0-next.1
+
+### Minor Changes
+
+- 20c1ea7: Add new `userGroupMember.path`, `user.path` and, `group.path` option to each query type to allow more complex msgraph queries
+
+### Patch Changes
+
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## 0.6.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.6.9
### Patch Changes
diff --git a/plugins/catalog-backend-module-msgraph/README.md b/plugins/catalog-backend-module-msgraph/README.md
index 7ee416ecd4..8e8c84de37 100644
--- a/plugins/catalog-backend-module-msgraph/README.md
+++ b/plugins/catalog-backend-module-msgraph/README.md
@@ -58,6 +58,8 @@ catalog:
loadPhotos: true
# See https://docs.microsoft.com/en-us/graph/api/resources/schemaextension?view=graph-rest-1.0
select: ['id', 'displayName', 'description']
+ # Optional /users by default but allow to query users from different msgraph endpoints
+ path: /users
# Optional configuration block
userGroupMember:
# Optional filter for users, use group membership to get users.
@@ -69,6 +71,8 @@ catalog:
# (Search for groups and fetch their members.)
# This and userFilter are mutually exclusive, only one can be specified
search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")'
+ # Optional /groups by default but allow to query groups from different msgraph endpoints
+ path: /groups
# Optional configuration block
group:
# Optional parameter to include the expanded resource or collection referenced
@@ -87,6 +91,8 @@ catalog:
# in order to add extra information to your groups that can be used on your custom groupTransformers
# See https://docs.microsoft.com/en-us/graph/api/resources/schemaextension?view=graph-rest-1.0
select: ['id', 'displayName', 'description']
+ # Optional /groups by default but allow to query groups from different msgraph endpoints
+ path: /groups
schedule: # optional; same options as in TaskScheduleDefinition
# supports cron, ISO duration, "human duration" as used in code
frequency: { hours: 1 }
diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts
index 8928da6f53..dfcad06593 100644
--- a/plugins/catalog-backend-module-msgraph/config.d.ts
+++ b/plugins/catalog-backend-module-msgraph/config.d.ts
@@ -148,6 +148,12 @@ export interface Config {
*/
queryMode?: string;
user?: {
+ /**
+ * The url path to fetch groups, defaults to `/users`.
+ *
+ * E.g. "groups/{id}/transitiveMembers/microsoft.graph.user/".
+ */
+ path?: string;
/**
* The "expand" argument to apply to users.
*
@@ -174,6 +180,12 @@ export interface Config {
};
group?: {
+ /**
+ * The url path to fetch groups, defaults to `/groups`.
+ *
+ * E.g. "groups/{id}/transitiveMembers/microsoft.graph.group/".
+ */
+ path?: string;
/**
* The "expand" argument to apply to groups.
*
@@ -206,6 +218,12 @@ export interface Config {
};
userGroupMember?: {
+ /**
+ * The url path to fetch groups, defaults to `/groups`.
+ *
+ * E.g. "groups/{id}/transitiveMembers/microsoft.graph.group/".
+ */
+ path?: string;
/**
* The filter to apply to extract users by groups memberships.
*
@@ -263,6 +281,12 @@ export interface Config {
*/
queryMode?: string;
user?: {
+ /**
+ * The url path to fetch groups, defaults to `/groups`.
+ *
+ * E.g. "groups/{id}/transitiveMembers/microsoft.graph.group/".
+ */
+ path?: string;
/**
* The "expand" argument to apply to users.
*
@@ -289,6 +313,12 @@ export interface Config {
};
group?: {
+ /**
+ * The url path to fetch groups, defaults to `/groups`.
+ *
+ * E.g. "groups/{id}/transitiveMembers/microsoft.graph.group/".
+ */
+ path?: string;
/**
* The "expand" argument to apply to groups.
*
@@ -321,6 +351,12 @@ export interface Config {
};
userGroupMember?: {
+ /**
+ * The url path to fetch groups, defaults to `/groups`.
+ *
+ * E.g. "groups/{id}/transitiveMembers/microsoft.graph.group/".
+ */
+ path?: string;
/**
* The filter to apply to extract users by groups memberships.
*
diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json
index 587d8cc0d4..ae116ca435 100644
--- a/plugins/catalog-backend-module-msgraph/package.json
+++ b/plugins/catalog-backend-module-msgraph/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-msgraph",
- "version": "0.6.9",
+ "version": "0.7.0-next.1",
"description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-msgraph/report.api.md b/plugins/catalog-backend-module-msgraph/report.api.md
index 912ca16e85..02d3949d04 100644
--- a/plugins/catalog-backend-module-msgraph/report.api.md
+++ b/plugins/catalog-backend-module-msgraph/report.api.md
@@ -92,6 +92,7 @@ export class MicrosoftGraphClient {
getGroups(
query?: ODataQuery,
queryMode?: 'basic' | 'advanced',
+ path?: string,
): AsyncIterable;
getGroupUserMembers(
groupId: string,
@@ -108,6 +109,7 @@ export class MicrosoftGraphClient {
getUsers(
query?: ODataQuery,
queryMode?: 'basic' | 'advanced',
+ path?: string,
): AsyncIterable;
requestApi(
path: string,
@@ -239,12 +241,15 @@ export type MicrosoftGraphProviderConfig = {
userFilter?: string;
userSelect?: string[];
userExpand?: string;
+ userPath?: string;
userGroupMemberFilter?: string;
userGroupMemberSearch?: string;
+ userGroupMemberPath?: string;
groupExpand?: string;
groupFilter?: string;
groupSearch?: string;
groupSelect?: string[];
+ groupPath?: string;
groupIncludeSubGroups?: boolean;
queryMode?: 'basic' | 'advanced';
loadUserPhotos?: boolean;
@@ -287,13 +292,16 @@ export function readMicrosoftGraphOrg(
userExpand?: string;
userFilter?: string;
userSelect?: string[];
+ userPath?: string;
loadUserPhotos?: boolean;
userGroupMemberSearch?: string;
userGroupMemberFilter?: string;
+ userGroupMemberPath?: string;
groupExpand?: string;
groupSearch?: string;
groupFilter?: string;
groupSelect?: string[];
+ groupPath?: string;
groupIncludeSubGroups?: boolean;
queryMode?: 'basic' | 'advanced';
userTransformer?: UserTransformer;
diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts
index 624b6e42b0..1f59ee9c93 100644
--- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts
+++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.test.ts
@@ -326,7 +326,7 @@ describe('MicrosoftGraphClient', () => {
it('should load organization', async () => {
worker.use(
- rest.get('https://example.com/organization/tentant-id', (_, res, ctx) =>
+ rest.get('https://example.com/organization/tenant-id', (_, res, ctx) =>
res(
ctx.status(200),
ctx.json({
@@ -336,7 +336,7 @@ describe('MicrosoftGraphClient', () => {
),
);
- const organization = await client.getOrganization('tentant-id');
+ const organization = await client.getOrganization('tenant-id');
expect(organization).toEqual({ displayName: 'Example' });
});
diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts
index aa2d735a07..81b57273a8 100644
--- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts
+++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts
@@ -272,16 +272,14 @@ export class MicrosoftGraphClient {
* @public
* @param query - OData Query {@link ODataQuery}
* @param queryMode - Mode to use while querying. Some features are only available at "advanced".
+ * @param path - Resource endpoint in Microsoft Graph
*/
async *getUsers(
query?: ODataQuery,
queryMode?: 'basic' | 'advanced',
+ path: string = 'users',
): AsyncIterable {
- yield* this.requestCollection(
- `users`,
- query,
- queryMode,
- );
+ yield* this.requestCollection(path, query, queryMode);
}
/**
@@ -314,16 +312,14 @@ export class MicrosoftGraphClient {
* @public
* @param query - OData Query {@link ODataQuery}
* @param queryMode - Mode to use while querying. Some features are only available at "advanced".
+ * @param path - Resource endpoint in Microsoft Graph
*/
async *getGroups(
query?: ODataQuery,
queryMode?: 'basic' | 'advanced',
+ path: string = 'groups',
): AsyncIterable {
- yield* this.requestCollection(
- `groups`,
- query,
- queryMode,
- );
+ yield* this.requestCollection(path, query, queryMode);
}
/**
diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts
index 0ec56f134d..182c4842dd 100644
--- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts
+++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts
@@ -148,6 +148,8 @@ describe('readProviderConfigs', () => {
id: 'customProviderId',
target: 'https://graph.microsoft.com/v1.0',
tenantId: 'tenantId',
+ userPath: 'users',
+ groupPath: 'groups',
},
];
expect(actual).toEqual(expected);
@@ -169,12 +171,14 @@ describe('readProviderConfigs', () => {
expand: 'manager',
filter: 'accountEnabled eq true',
select: ['id', 'displayName', 'department'],
+ path: '/groups/{groupId}/members',
},
group: {
expand: 'member',
filter: 'securityEnabled eq false',
select: ['id', 'displayName', 'description'],
includeSubGroups: true,
+ path: '/groups/{groupId}/members',
},
schedule: {
frequency: 'PT30M',
@@ -200,9 +204,11 @@ describe('readProviderConfigs', () => {
userExpand: 'manager',
userFilter: 'accountEnabled eq true',
userSelect: ['id', 'displayName', 'department'],
+ userPath: '/groups/{groupId}/members',
groupExpand: 'member',
groupSelect: ['id', 'displayName', 'description'],
groupFilter: 'securityEnabled eq false',
+ groupPath: '/groups/{groupId}/members',
groupIncludeSubGroups: true,
schedule: {
frequency: { minutes: 30 },
diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts
index e47181a1c4..5feabcb9b2 100644
--- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts
+++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts
@@ -78,6 +78,13 @@ export type MicrosoftGraphProviderConfig = {
* E.g. "manager".
*/
userExpand?: string;
+
+ /**
+ * The path to the users endpoint. Defaults to "/users".
+ *
+ * E.g. "/users"
+ */
+ userPath?: string;
/**
* The filter to apply to extract users by groups memberships.
*
@@ -90,6 +97,14 @@ export type MicrosoftGraphProviderConfig = {
* E.g. "\"displayName:-team\"" would only match groups which contain '-team'
*/
userGroupMemberSearch?: string;
+
+ /**
+ * The path to the groups endpoint. Defaults to "/groups".
+ *
+ * E.g. "/groups"
+ */
+ userGroupMemberPath?: string;
+
/**
* The "expand" argument to apply to groups.
*
@@ -116,6 +131,13 @@ export type MicrosoftGraphProviderConfig = {
*/
groupSelect?: string[];
+ /**
+ * The path to the groups endpoint. Defaults to "/groups".
+ *
+ * E.g. "/groups"
+ */
+ groupPath?: string;
+
/**
* Whether to ingest groups that are members of the found/filtered/searched groups.
* Default value is `false`.
@@ -292,12 +314,14 @@ export function readProviderConfig(
const userExpand = config.getOptionalString('user.expand');
const userFilter = config.getOptionalString('user.filter');
const userSelect = config.getOptionalStringArray('user.select');
+ const userPath = config.getOptionalString('user.path') ?? 'users';
const loadUserPhotos = config.getOptionalBoolean('user.loadPhotos');
const groupExpand = config.getOptionalString('group.expand');
const groupFilter = config.getOptionalString('group.filter');
const groupSearch = config.getOptionalString('group.search');
const groupSelect = config.getOptionalStringArray('group.select');
+ const groupPath = config.getOptionalString('group.path') ?? 'groups';
const groupIncludeSubGroups = config.getOptionalBoolean(
'group.includeSubGroups',
);
@@ -317,6 +341,7 @@ export function readProviderConfig(
const userGroupMemberSearch = config.getOptionalString(
'userGroupMember.search',
);
+ const userGroupMemberPath = config.getOptionalString('userGroupMember.path');
if (userFilter && userGroupMemberFilter) {
throw new Error(
@@ -353,15 +378,18 @@ export function readProviderConfig(
userExpand,
userFilter,
userSelect,
+ userPath,
loadUserPhotos,
groupExpand,
groupFilter,
groupSearch,
groupSelect,
+ groupPath,
groupIncludeSubGroups,
queryMode,
userGroupMemberFilter,
userGroupMemberSearch,
+ userGroupMemberPath,
schedule,
};
}
diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts
index b83597653f..33ae04bd76 100644
--- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts
+++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts
@@ -139,6 +139,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1);
expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith(
@@ -186,6 +187,7 @@ describe('read microsoft graph', () => {
top: 999,
},
'advanced',
+ undefined,
);
expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1);
expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith(
@@ -229,6 +231,55 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
+ );
+ expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1);
+ expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith(
+ 'userid',
+ 120,
+ );
+ });
+
+ it('should read users from different endpoints', async () => {
+ client.getUsers.mockImplementation(getExampleUsers);
+ client.getUserPhotoWithSizeLimit.mockResolvedValue(
+ 'data:image/jpeg;base64,...',
+ );
+
+ const { users } = await readMicrosoftGraphUsers(client, {
+ userFilter: 'accountEnabled eq true',
+ userPath: '/users/x/y',
+ logger: mockServices.logger.mock(),
+ });
+
+ expect(users).toEqual([
+ user({
+ metadata: {
+ annotations: {
+ 'graph.microsoft.com/user-id': 'userid',
+ 'microsoft.com/email': 'user.name@example.com',
+ },
+ name: 'user.name_example.com',
+ },
+ spec: {
+ profile: {
+ displayName: 'User Name',
+ email: 'user.name@example.com',
+ picture: 'data:image/jpeg;base64,...',
+ },
+ memberOf: [],
+ },
+ }),
+ ]);
+
+ expect(client.getUsers).toHaveBeenCalledTimes(1);
+ expect(client.getUsers).toHaveBeenCalledWith(
+ {
+ filter: 'accountEnabled eq true',
+ top: 999,
+ },
+ undefined,
+ '/users/x/y',
);
expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1);
expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledWith(
@@ -280,6 +331,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1);
@@ -338,6 +390,7 @@ describe('read microsoft graph', () => {
top: 999,
},
'advanced',
+ undefined,
);
expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1);
@@ -393,6 +446,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
expect(client.getGroupUserMembers).toHaveBeenCalledTimes(1);
@@ -411,6 +465,52 @@ describe('read microsoft graph', () => {
120,
);
});
+
+ it('should read users from different group endpoints', async () => {
+ client.getGroups.mockImplementation(getExampleGroups);
+ client.getGroupUserMembers.mockImplementation(getExampleUsers);
+
+ client.getUserPhotoWithSizeLimit.mockResolvedValue(
+ 'data:image/jpeg;base64,...',
+ );
+
+ const { users } = await readMicrosoftGraphUsersInGroups(client, {
+ userGroupMemberFilter: 'securityEnabled eq true',
+ userGroupMemberPath: '/groups/x/y',
+ logger: mockServices.logger.mock(),
+ });
+
+ expect(users).toEqual([
+ user({
+ metadata: {
+ annotations: {
+ 'graph.microsoft.com/user-id': 'userid',
+ 'microsoft.com/email': 'user.name@example.com',
+ },
+ name: 'user.name_example.com',
+ },
+ spec: {
+ profile: {
+ displayName: 'User Name',
+ email: 'user.name@example.com',
+ picture: 'data:image/jpeg;base64,...',
+ },
+ memberOf: [],
+ },
+ }),
+ ]);
+
+ expect(client.getGroups).toHaveBeenCalledTimes(1);
+ expect(client.getGroups).toHaveBeenCalledWith(
+ {
+ filter: 'securityEnabled eq true',
+ select: ['id', 'displayName'],
+ top: 999,
+ },
+ undefined,
+ '/groups/x/y',
+ );
+ });
});
describe('readMicrosoftGraphOrganization', () => {
@@ -535,6 +635,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
expect(client.getGroupMembers).toHaveBeenCalledTimes(1);
expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', {
@@ -614,6 +715,7 @@ describe('read microsoft graph', () => {
top: 999,
},
'advanced',
+ undefined,
);
expect(client.getGroupMembers).toHaveBeenCalledTimes(1);
expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', {
@@ -694,6 +796,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
expect(client.getGroupMembers).toHaveBeenCalledTimes(1);
expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', {
@@ -767,6 +870,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
expect(client.getGroupMembers).toHaveBeenCalledTimes(1);
expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', {
@@ -871,6 +975,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
expect(client.getGroupMembers).toHaveBeenCalledTimes(2);
expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', {
@@ -884,6 +989,79 @@ describe('read microsoft graph', () => {
// expect(client.getGroupPhotoWithSizeLimit).toBeCalledTimes(1);
// expect(client.getGroupPhotoWithSizeLimit).toBeCalledWith('groupid', 120);
});
+
+ it('should read groups from different endpoints paths', async () => {
+ client.getGroups.mockImplementation(getExampleGroups);
+ client.getGroupMembers.mockImplementation(getExampleGroupMembers);
+ client.getOrganization.mockResolvedValue({
+ id: 'tenantid',
+ displayName: 'Organization Name',
+ });
+ client.getGroupPhotoWithSizeLimit.mockResolvedValue(
+ 'data:image/jpeg;base64,...',
+ );
+
+ const { groups, groupMember, groupMemberOf, rootGroup } =
+ await readMicrosoftGraphGroups(client, 'tenantid', {
+ groupFilter: 'securityEnabled eq false',
+ groupPath: '/groups/x/y',
+ });
+
+ const expectedRootGroup = group({
+ metadata: {
+ annotations: {
+ 'graph.microsoft.com/tenant-id': 'tenantid',
+ },
+ name: 'organization_name',
+ description: 'Organization Name',
+ },
+ spec: {
+ type: 'root',
+ profile: {
+ displayName: 'Organization Name',
+ },
+ children: [],
+ },
+ });
+ expect(groups).toEqual([
+ expectedRootGroup,
+ group({
+ metadata: {
+ annotations: {
+ 'graph.microsoft.com/group-id': 'groupid',
+ },
+ name: 'group_name',
+ description: 'Group Description',
+ },
+ spec: {
+ type: 'team',
+ profile: {
+ displayName: 'Group Name',
+ email: 'group@example.com',
+ },
+ children: [],
+ },
+ }),
+ ]);
+ expect(rootGroup).toEqual(expectedRootGroup);
+ expect(groupMember.get('groupid')).toEqual(new Set(['childgroupid']));
+ expect(groupMemberOf.get('userid')).toEqual(new Set(['groupid']));
+ expect(groupMember.get('organization_name')).toEqual(new Set());
+
+ expect(client.getGroups).toHaveBeenCalledTimes(1);
+ expect(client.getGroups).toHaveBeenCalledWith(
+ {
+ filter: 'securityEnabled eq false',
+ top: 999,
+ },
+ undefined,
+ '/groups/x/y',
+ );
+ expect(client.getGroupMembers).toHaveBeenCalledTimes(1);
+ expect(client.getGroupMembers).toHaveBeenCalledWith('groupid', {
+ top: 999,
+ });
+ });
});
describe('resolveRelations', () => {
@@ -1018,6 +1196,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
expect(client.getGroups).toHaveBeenCalledTimes(1);
expect(client.getGroups).toHaveBeenCalledWith(
@@ -1026,6 +1205,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
});
@@ -1058,6 +1238,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
expect(client.getGroups).toHaveBeenCalledTimes(1);
expect(client.getGroups).toHaveBeenCalledWith(
@@ -1066,6 +1247,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
});
@@ -1096,6 +1278,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
expect(client.getGroups).toHaveBeenCalledTimes(1);
expect(client.getGroups).toHaveBeenCalledWith(
@@ -1103,6 +1286,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
});
@@ -1132,6 +1316,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
});
@@ -1161,20 +1346,24 @@ describe('read microsoft graph', () => {
expect(client.getUsers).toHaveBeenCalledTimes(0);
expect(client.getGroups).toHaveBeenCalledTimes(2);
- expect(client.getGroups).toHaveBeenCalledWith(
+ expect(client.getGroups).toHaveBeenNthCalledWith(
+ 1,
{
filter: 'name eq backstage-group',
select: ['id', 'displayName'],
top: 999,
},
undefined,
+ undefined,
);
- expect(client.getGroups).toHaveBeenCalledWith(
+ expect(client.getGroups).toHaveBeenNthCalledWith(
+ 2,
{
filter: 'securityEnabled eq false',
top: 999,
},
undefined,
+ undefined,
);
expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1);
});
@@ -1215,6 +1404,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
expect(client.getGroups).toHaveBeenCalledTimes(1);
expect(client.getGroups).toHaveBeenCalledWith(
@@ -1222,6 +1412,7 @@ describe('read microsoft graph', () => {
top: 999,
},
undefined,
+ undefined,
);
});
});
diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts
index 4f1c9efbe2..4761c45ac7 100644
--- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts
+++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts
@@ -49,6 +49,7 @@ export async function readMicrosoftGraphUsers(
userExpand?: string;
userFilter?: string;
userSelect?: string[];
+ userPath?: string;
loadUserPhotos?: boolean;
transformer?: UserTransformer;
logger: LoggerService;
@@ -64,6 +65,7 @@ export async function readMicrosoftGraphUsers(
top: PAGE_SIZE,
},
options.queryMode,
+ options.userPath,
);
return {
@@ -87,6 +89,7 @@ export async function readMicrosoftGraphUsersInGroups(
loadUserPhotos?: boolean;
userGroupMemberSearch?: string;
userGroupMemberFilter?: string;
+ userGroupMemberPath?: string;
groupExpand?: string;
transformer?: UserTransformer;
logger: LoggerService;
@@ -108,6 +111,7 @@ export async function readMicrosoftGraphUsersInGroups(
top: PAGE_SIZE,
},
options.queryMode,
+ options.userGroupMemberPath,
)) {
// Process all groups in parallel, otherwise it can take quite some time
userGroupMemberPromises.push(
@@ -178,6 +182,7 @@ export async function readMicrosoftGraphGroups(
groupFilter?: string;
groupSearch?: string;
groupSelect?: string[];
+ groupPath?: string;
groupIncludeSubGroups?: boolean;
groupTransformer?: GroupTransformer;
organizationTransformer?: OrganizationTransformer;
@@ -213,6 +218,7 @@ export async function readMicrosoftGraphGroups(
top: PAGE_SIZE,
},
options?.queryMode,
+ options?.groupPath,
)) {
// Process all groups in parallel, otherwise it can take quite some time
promises.push(
@@ -396,13 +402,16 @@ export async function readMicrosoftGraphOrg(
userExpand?: string;
userFilter?: string;
userSelect?: string[];
+ userPath?: string;
loadUserPhotos?: boolean;
userGroupMemberSearch?: string;
userGroupMemberFilter?: string;
+ userGroupMemberPath?: string;
groupExpand?: string;
groupSearch?: string;
groupFilter?: string;
groupSelect?: string[];
+ groupPath?: string;
groupIncludeSubGroups?: boolean;
queryMode?: 'basic' | 'advanced';
userTransformer?: UserTransformer;
@@ -413,7 +422,11 @@ export async function readMicrosoftGraphOrg(
): Promise<{ users: UserEntity[]; groups: GroupEntity[] }> {
let users: UserEntity[] = [];
- if (options.userGroupMemberFilter || options.userGroupMemberSearch) {
+ if (
+ options.userGroupMemberFilter ||
+ options.userGroupMemberSearch ||
+ options.userGroupMemberPath
+ ) {
const { users: usersInGroups } = await readMicrosoftGraphUsersInGroups(
client,
{
@@ -423,6 +436,7 @@ export async function readMicrosoftGraphOrg(
userSelect: options.userSelect,
userGroupMemberFilter: options.userGroupMemberFilter,
userGroupMemberSearch: options.userGroupMemberSearch,
+ userGroupMemberPath: options.userGroupMemberPath,
loadUserPhotos: options.loadUserPhotos,
transformer: options.userTransformer,
logger: options.logger,
@@ -435,6 +449,7 @@ export async function readMicrosoftGraphOrg(
userExpand: options.userExpand,
userFilter: options.userFilter,
userSelect: options.userSelect,
+ userPath: options.userPath,
loadUserPhotos: options.loadUserPhotos,
transformer: options.userTransformer,
logger: options.logger,
@@ -448,6 +463,7 @@ export async function readMicrosoftGraphOrg(
groupFilter: options.groupFilter,
groupSearch: options.groupSearch,
groupSelect: options.groupSelect,
+ groupPath: options.groupPath,
groupIncludeSubGroups: options.groupIncludeSubGroups,
groupTransformer: options.groupTransformer,
organizationTransformer: options.organizationTransformer,
diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts
index c490c0473d..423b7eb466 100644
--- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts
+++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts
@@ -327,6 +327,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider {
: this.options.provider;
const { markReadComplete } = trackProgress(logger);
const client = MicrosoftGraphClient.create(this.options.provider);
+
const { users, groups } = await readMicrosoftGraphOrg(
client,
provider.tenantId,
@@ -334,13 +335,16 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider {
userExpand: provider.userExpand,
userFilter: provider.userFilter,
userSelect: provider.userSelect,
+ userPath: provider.userPath,
loadUserPhotos: provider.loadUserPhotos,
userGroupMemberFilter: provider.userGroupMemberFilter,
userGroupMemberSearch: provider.userGroupMemberSearch,
+ userGroupMemberPath: provider.userGroupMemberPath,
groupExpand: provider.groupExpand,
groupFilter: provider.groupFilter,
groupSearch: provider.groupSearch,
groupSelect: provider.groupSelect,
+ groupPath: provider.groupPath,
groupIncludeSubGroups: provider.groupIncludeSubGroups,
queryMode: provider.queryMode,
groupTransformer: this.options.groupTransformer,
diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md
index 2fd522685b..b403e55dd1 100644
--- a/plugins/catalog-backend-module-openapi/CHANGELOG.md
+++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/plugin-catalog-backend-module-openapi
+## 0.2.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## 0.2.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.2.9
### Patch Changes
diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json
index b36760716a..75ea03d23d 100644
--- a/plugins/catalog-backend-module-openapi/package.json
+++ b/plugins/catalog-backend-module-openapi/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-openapi",
- "version": "0.2.9",
+ "version": "0.2.10-next.1",
"description": "A Backstage catalog backend module that helps with OpenAPI specifications",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md
index f6387b058d..324a091dc4 100644
--- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md
+++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md
@@ -1,5 +1,29 @@
# @backstage/plugin-catalog-backend-module-puppetdb
+## 0.2.10-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
+## 0.2.10-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 0.2.9
### Patch Changes
diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json
index fdde9f9ed5..fc830eb66e 100644
--- a/plugins/catalog-backend-module-puppetdb/package.json
+++ b/plugins/catalog-backend-module-puppetdb/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-puppetdb",
- "version": "0.2.9",
+ "version": "0.2.10-next.1",
"description": "A Backstage catalog backend module that helps integrate towards PuppetDB",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md
index 86e7326d8e..af44065886 100644
--- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md
+++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md
@@ -1,5 +1,27 @@
# @backstage/plugin-catalog-backend-module-scaffolder-entity-model
+## 0.2.8-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-scaffolder-common@1.5.11-next.0
+
+## 0.2.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-scaffolder-common@1.5.10
+
## 0.2.7
### Patch Changes
diff --git a/plugins/catalog-backend-module-scaffolder-entity-model/package.json b/plugins/catalog-backend-module-scaffolder-entity-model/package.json
index c35566629f..dc32af8cef 100644
--- a/plugins/catalog-backend-module-scaffolder-entity-model/package.json
+++ b/plugins/catalog-backend-module-scaffolder-entity-model/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-scaffolder-entity-model",
- "version": "0.2.7",
+ "version": "0.2.8-next.1",
"description": "Adds support for the scaffolder specific entity model (e.g. the Template kind) to the catalog backend plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md
index bb019c2fc0..a2341a7e40 100644
--- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md
+++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md
@@ -1,5 +1,35 @@
# @backstage/plugin-catalog-backend-module-unprocessed
+## 0.6.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/plugin-auth-node@0.6.3-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.8-next.0
+
+## 0.6.0-next.0
+
+### Minor Changes
+
+- f453d5c: **BREAKING** Removed support for the legacy backend and removed references to `@backstage/backend-common`, please [migrate to the new backend system](https://backstage.io/docs/backend-system/building-plugins-and-modules/migrating)
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-auth-node@0.6.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-unprocessed-entities-common@0.0.7
+ - @backstage/plugin-permission-common@0.8.4
+
## 0.5.7
### Patch Changes
diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json
index 88d4d6c3ae..98e2ba8f08 100644
--- a/plugins/catalog-backend-module-unprocessed/package.json
+++ b/plugins/catalog-backend-module-unprocessed/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-unprocessed",
- "version": "0.5.7",
+ "version": "0.6.0-next.1",
"description": "Backstage Catalog module to view unprocessed entities",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md
index 5b700182cc..f5fd6342f9 100644
--- a/plugins/catalog-backend/CHANGELOG.md
+++ b/plugins/catalog-backend/CHANGELOG.md
@@ -1,5 +1,117 @@
# @backstage/plugin-catalog-backend
+## 2.0.0-next.1
+
+### Major Changes
+
+- 90ab044: **BREAKING**: Removed all deprecated exports, and removed support for the old backend system.
+
+ It also removes the `CodeOwnersProcessor` from the default set of processors, because it is expensive to run and has vague semantics. You need to update your backend to add it to the `catalogProcessingExtensionPoint` if you wish to continue using it.
+
+ The following removed exports are available from `@backstage/plugin-catalog-node`:
+
+ - `locationSpecToMetadataName`
+ - `locationSpecToLocationEntity`
+ - `processingResult`
+ - `EntitiesSearchFilter`
+ - `EntityFilter`
+ - `DeferredEntity`
+ - `EntityRelationSpec`
+ - `CatalogProcessor`
+ - `CatalogProcessorParser`
+ - `CatalogProcessorCache`
+ - `CatalogProcessorEmit`
+ - `CatalogProcessorLocationResult`
+ - `CatalogProcessorEntityResult`
+ - `CatalogProcessorRelationResult`
+ - `CatalogProcessorErrorResult`
+ - `CatalogProcessorRefreshKeysResult`
+ - `CatalogProcessorResult`
+ - `EntityProvider`
+ - `EntityProviderConnection`
+ - `EntityProviderMutation`
+ - `AnalyzeOptions`
+ - `LocationAnalyzer`
+ - `ScmLocationAnalyzer`
+ - `PlaceholderResolver`
+ - `PlaceholderResolverParams`
+ - `PlaceholderResolverRead`
+ - `PlaceholderResolverResolveUrl`
+ - `parseEntityYaml`
+
+ The following removed exports are available from `@backstage/plugin-catalog-common`:
+
+ - `LocationSpec`
+ - `AnalyzeLocationRequest`
+ - `AnalyzeLocationResponse`
+ - `AnalyzeLocationExistingEntity`
+ - `AnalyzeLocationGenerateEntity`
+ - `AnalyzeLocationEntityField`
+
+ The following removed exports are instead implemented in the new backend system by `@backstage/plugin-search-backend-module-catalog`:
+
+ - `defaultCatalogCollatorEntityTransformer`
+ - `CatalogCollatorEntityTransformer`
+ - `DefaultCatalogCollator`
+
+ The following exports are removed without a direct replacement:
+
+ - `DefaultCatalogCollatorFactory`
+ - `DefaultCatalogCollatorFactoryOptions`
+ - `LocationEntityProcessor`
+ - `LocationEntityProcessorOptions`
+ - `CatalogBuilder`
+ - `CatalogEnvironment`
+ - `CatalogPermissionRuleInput`
+ - `CatalogProcessingEngine`
+ - `createRandomProcessingInterval`
+ - `ProcessingIntervalFunction`
+
+### Minor Changes
+
+- 6c9b88e: **BREAKING ALPHA**: You can no longer import the catalog plugin from the `/alpha` export; please use the regular root default export instead.
+- d88b922: Adds the ability to disable the default entity processors using a new boolean app config item `catalog.disableDefaultProcessors`.
+
+### Patch Changes
+
+- 0e710fc: This patch addresses an issue identified in Backstage when configured with a MySQL database. If an entity of type location
+ (e..all.yaml) has more than 70 referenced entities, clicking "Refresh" does not update the referenced entities as expected. This occurs because the TEXT type in MySQL has a limit of 65,535 bytes, which is insufficient to store all the referenced entities, causing the refresh operation to fail.
+- Updated dependencies
+ - @backstage/plugin-catalog-node@1.17.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/backend-openapi-utils@0.5.3-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 1.32.2-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/plugin-catalog-node@1.17.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/plugin-search-backend-module-catalog@0.3.4-next.0
+ - @backstage/backend-openapi-utils@0.5.3-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-search-common@1.2.17
+
## 1.32.1
### Patch Changes
diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts
index d7816fa856..7e68c4bea9 100644
--- a/plugins/catalog-backend/config.d.ts
+++ b/plugins/catalog-backend/config.d.ts
@@ -148,6 +148,15 @@ export interface Config {
*/
disableRelationsCompatibility?: boolean;
+ /**
+ * Disables the default backstage processors.
+ *
+ * Enabling this option allows more complete control of which processors are included
+ * in the backstage processing loop.
+ *
+ */
+ disableDefaultProcessors?: boolean;
+
/**
* The strategy to use for entities that are orphaned, i.e. no longer have
* any other entities or providers referencing them. The default value is
diff --git a/plugins/catalog-backend/dev/index.ts b/plugins/catalog-backend/dev/index.ts
index 43c75d24df..dc287d46cc 100644
--- a/plugins/catalog-backend/dev/index.ts
+++ b/plugins/catalog-backend/dev/index.ts
@@ -17,5 +17,5 @@
import { createBackend } from '@backstage/backend-defaults';
const backend = createBackend();
-backend.add(import('../src/alpha'));
+backend.add(import('../src'));
backend.start();
diff --git a/plugins/catalog-backend/migrations/20250401200503_update_refresh_state_columns.js b/plugins/catalog-backend/migrations/20250401200503_update_refresh_state_columns.js
new file mode 100644
index 0000000000..091dc896af
--- /dev/null
+++ b/plugins/catalog-backend/migrations/20250401200503_update_refresh_state_columns.js
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2024 The 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.
+ */
+/**
+ * @param {import('knex').Knex} knex
+ * @returns {Promise}
+ */
+exports.up = async function up(knex) {
+ const isMySQL = knex.client.config.client.includes('mysql');
+ // update the columns to longtext for MySQL
+ if (isMySQL) {
+ await knex.schema.alterTable('refresh_state', table => {
+ table.text('unprocessed_entity', 'longtext').alter();
+ table.text('cache', 'longtext').alter();
+ });
+ }
+};
+
+/**
+ * @param {import('knex').Knex} knex
+ * @returns {Promise}
+ */
+exports.down = async function down(knex) {
+ const isMySQL = knex.client.config.client.includes('mysql');
+ if (isMySQL) {
+ await knex.schema.alterTable('refresh_state', table => {
+ table.text('unprocessed_entity').alter();
+ table.text('cache').alter();
+ });
+ }
+};
diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json
index b211aa51b9..8f2f8afe94 100644
--- a/plugins/catalog-backend/package.json
+++ b/plugins/catalog-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend",
- "version": "1.32.1",
+ "version": "2.0.0-next.1",
"description": "The Backstage backend plugin that provides the Backstage catalog",
"backstage": {
"role": "backend-plugin",
@@ -61,7 +61,6 @@
"test": "backstage-cli package test"
},
"dependencies": {
- "@backstage/backend-common": "^0.25.0",
"@backstage/backend-openapi-utils": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/catalog-client": "workspace:^",
@@ -74,11 +73,8 @@
"@backstage/plugin-events-node": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-permission-node": "workspace:^",
- "@backstage/plugin-search-backend-module-catalog": "workspace:^",
- "@backstage/plugin-search-common": "workspace:^",
"@backstage/types": "workspace:^",
"@opentelemetry/api": "^1.9.0",
- "@types/express": "^4.17.6",
"codeowners-utils": "^1.0.2",
"core-js": "^3.6.5",
"express": "^4.17.1",
@@ -105,6 +101,7 @@
"@backstage/repo-tools": "workspace:^",
"@backstage/test-utils": "workspace:^",
"@types/core-js": "^2.5.4",
+ "@types/express": "^4.17.6",
"@types/git-url-parse": "^9.0.0",
"@types/glob": "^8.0.0",
"@types/lodash": "^4.14.151",
diff --git a/plugins/catalog-backend/report-alpha.api.md b/plugins/catalog-backend/report-alpha.api.md
index d32ef323c9..ca521a568d 100644
--- a/plugins/catalog-backend/report-alpha.api.md
+++ b/plugins/catalog-backend/report-alpha.api.md
@@ -3,7 +3,6 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
-import { BackendFeature } from '@backstage/backend-plugin-api';
import { ConditionalPolicyDecision } from '@backstage/plugin-permission-common';
import { Conditions } from '@backstage/plugin-permission-node';
import { EntitiesSearchFilter } from '@backstage/plugin-catalog-node';
@@ -87,10 +86,6 @@ export const createCatalogPermissionRule: <
rule: PermissionRule,
) => PermissionRule;
-// @alpha (undocumented)
-const _feature: BackendFeature;
-export default _feature;
-
// @alpha
export const permissionRules: {
hasAnnotation: PermissionRule<
diff --git a/plugins/catalog-backend/report.api.md b/plugins/catalog-backend/report.api.md
index 3a47cc4354..9bb94b2dd5 100644
--- a/plugins/catalog-backend/report.api.md
+++ b/plugins/catalog-backend/report.api.md
@@ -3,104 +3,36 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
-import { AnalyzeLocationEntityField as AnalyzeLocationEntityField_2 } from '@backstage/plugin-catalog-common';
-import { AnalyzeLocationExistingEntity as AnalyzeLocationExistingEntity_2 } from '@backstage/plugin-catalog-common';
-import { AnalyzeLocationGenerateEntity as AnalyzeLocationGenerateEntity_2 } from '@backstage/plugin-catalog-common';
-import { AnalyzeLocationRequest as AnalyzeLocationRequest_2 } from '@backstage/plugin-catalog-common';
-import { AnalyzeLocationResponse as AnalyzeLocationResponse_2 } from '@backstage/plugin-catalog-common';
-import { AnalyzeOptions as AnalyzeOptions_2 } from '@backstage/plugin-catalog-node';
-import { AuditorService } from '@backstage/backend-plugin-api';
-import { AuthService } from '@backstage/backend-plugin-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
-import { CatalogApi } from '@backstage/catalog-client';
-import { CatalogCollatorEntityTransformer as CatalogCollatorEntityTransformer_2 } from '@backstage/plugin-search-backend-module-catalog';
-import { CatalogEntityDocument } from '@backstage/plugin-catalog-common';
-import { CatalogProcessor as CatalogProcessor_2 } from '@backstage/plugin-catalog-node';
-import { CatalogProcessorCache as CatalogProcessorCache_2 } from '@backstage/plugin-catalog-node';
-import { CatalogProcessorEmit as CatalogProcessorEmit_2 } from '@backstage/plugin-catalog-node';
-import { CatalogProcessorEntityResult as CatalogProcessorEntityResult_2 } from '@backstage/plugin-catalog-node';
-import { CatalogProcessorErrorResult as CatalogProcessorErrorResult_2 } from '@backstage/plugin-catalog-node';
-import { CatalogProcessorLocationResult as CatalogProcessorLocationResult_2 } from '@backstage/plugin-catalog-node';
-import { CatalogProcessorParser as CatalogProcessorParser_2 } from '@backstage/plugin-catalog-node';
-import { CatalogProcessorRefreshKeysResult as CatalogProcessorRefreshKeysResult_2 } from '@backstage/plugin-catalog-node';
-import { CatalogProcessorRelationResult as CatalogProcessorRelationResult_2 } from '@backstage/plugin-catalog-node';
-import { CatalogProcessorResult as CatalogProcessorResult_2 } from '@backstage/plugin-catalog-node';
+import { CatalogProcessor } from '@backstage/plugin-catalog-node';
+import { CatalogProcessorCache } from '@backstage/plugin-catalog-node';
+import { CatalogProcessorEmit } from '@backstage/plugin-catalog-node';
+import { CatalogProcessorParser } from '@backstage/plugin-catalog-node';
import { Config } from '@backstage/config';
-import { DatabaseService } from '@backstage/backend-plugin-api';
-import { DeferredEntity as DeferredEntity_2 } from '@backstage/plugin-catalog-node';
-import { DiscoveryService } from '@backstage/backend-plugin-api';
-import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
-import { EntitiesSearchFilter as EntitiesSearchFilter_2 } from '@backstage/plugin-catalog-node';
import { Entity } from '@backstage/catalog-model';
-import { EntityFilter as EntityFilter_2 } from '@backstage/plugin-catalog-node';
import { EntityPolicy } from '@backstage/catalog-model';
-import { EntityProvider as EntityProvider_2 } from '@backstage/plugin-catalog-node';
-import { EntityProviderConnection as EntityProviderConnection_2 } from '@backstage/plugin-catalog-node';
-import { EntityProviderMutation as EntityProviderMutation_2 } from '@backstage/plugin-catalog-node';
-import { EntityRelationSpec as EntityRelationSpec_2 } from '@backstage/plugin-catalog-node';
-import { EventBroker } from '@backstage/plugin-events-node';
-import { EventsService } from '@backstage/plugin-events-node';
-import { GetEntitiesRequest } from '@backstage/catalog-client';
-import { HttpAuthService } from '@backstage/backend-plugin-api';
-import { LocationAnalyzer as LocationAnalyzer_2 } from '@backstage/plugin-catalog-node';
-import { LocationSpec as LocationSpec_2 } from '@backstage/plugin-catalog-common';
-import { locationSpecToLocationEntity as locationSpecToLocationEntity_2 } from '@backstage/plugin-catalog-node';
-import { locationSpecToMetadataName as locationSpecToMetadataName_2 } from '@backstage/plugin-catalog-node';
+import { LocationSpec } from '@backstage/plugin-catalog-common';
import { LoggerService } from '@backstage/backend-plugin-api';
-import { Permission } from '@backstage/plugin-permission-common';
-import { PermissionAuthorizer } from '@backstage/plugin-permission-common';
-import { PermissionRule } from '@backstage/plugin-permission-node';
-import { PermissionRuleParams } from '@backstage/plugin-permission-common';
-import { PermissionsRegistryService } from '@backstage/backend-plugin-api';
-import { PermissionsService } from '@backstage/backend-plugin-api';
-import { PlaceholderResolver as PlaceholderResolver_2 } from '@backstage/plugin-catalog-node';
-import { PlaceholderResolverParams as PlaceholderResolverParams_2 } from '@backstage/plugin-catalog-node';
-import { PlaceholderResolverRead as PlaceholderResolverRead_2 } from '@backstage/plugin-catalog-node';
-import { PlaceholderResolverResolveUrl as PlaceholderResolverResolveUrl_2 } from '@backstage/plugin-catalog-node';
-import { Readable } from 'stream';
-import { RootConfigService } from '@backstage/backend-plugin-api';
-import { Router } from 'express';
-import { SchedulerService } from '@backstage/backend-plugin-api';
+import { PlaceholderResolver } from '@backstage/plugin-catalog-node';
import { ScmIntegrationRegistry } from '@backstage/integration';
-import { ScmLocationAnalyzer as ScmLocationAnalyzer_2 } from '@backstage/plugin-catalog-node';
-import { TokenManager } from '@backstage/backend-common';
import { UrlReaderService } from '@backstage/backend-plugin-api';
-import { Validators } from '@backstage/catalog-model';
-
-// @public @deprecated
-export type AnalyzeLocationEntityField = AnalyzeLocationEntityField_2;
-
-// @public @deprecated
-export type AnalyzeLocationExistingEntity = AnalyzeLocationExistingEntity_2;
-
-// @public @deprecated
-export type AnalyzeLocationGenerateEntity = AnalyzeLocationGenerateEntity_2;
-
-// @public @deprecated (undocumented)
-export type AnalyzeLocationRequest = AnalyzeLocationRequest_2;
-
-// @public @deprecated (undocumented)
-export type AnalyzeLocationResponse = AnalyzeLocationResponse_2;
-
-// @public @deprecated (undocumented)
-export type AnalyzeOptions = AnalyzeOptions_2;
// @public (undocumented)
-export class AnnotateLocationEntityProcessor implements CatalogProcessor_2 {
+export class AnnotateLocationEntityProcessor implements CatalogProcessor {
constructor(options: { integrations: ScmIntegrationRegistry });
// (undocumented)
getProcessorName(): string;
// (undocumented)
preProcessEntity(
entity: Entity,
- location: LocationSpec_2,
- _: CatalogProcessorEmit_2,
- originLocation: LocationSpec_2,
+ location: LocationSpec,
+ _: CatalogProcessorEmit,
+ originLocation: LocationSpec,
): Promise;
}
// @public (undocumented)
-export class AnnotateScmSlugEntityProcessor implements CatalogProcessor_2 {
+export class AnnotateScmSlugEntityProcessor implements CatalogProcessor {
constructor(opts: {
scmIntegrationRegistry: ScmIntegrationRegistry;
kinds?: string[];
@@ -115,18 +47,18 @@ export class AnnotateScmSlugEntityProcessor implements CatalogProcessor_2 {
// (undocumented)
getProcessorName(): string;
// (undocumented)
- preProcessEntity(entity: Entity, location: LocationSpec_2): Promise;
+ preProcessEntity(entity: Entity, location: LocationSpec): Promise;
}
// @public (undocumented)
-export class BuiltinKindsEntityProcessor implements CatalogProcessor_2 {
+export class BuiltinKindsEntityProcessor implements CatalogProcessor {
// (undocumented)
getProcessorName(): string;
// (undocumented)
postProcessEntity(
entity: Entity,
- _location: LocationSpec_2,
- emit: CatalogProcessorEmit_2,
+ _location: LocationSpec,
+ emit: CatalogProcessorEmit,
): Promise;
// (undocumented)
validateEntityKind(entity: Entity): Promise;
@@ -138,126 +70,12 @@ export const CATALOG_CONFLICTS_TOPIC = 'experimental.catalog.conflict';
// @public (undocumented)
export const CATALOG_ERRORS_TOPIC = 'experimental.catalog.errors';
-// @public @deprecated
-export class CatalogBuilder {
- addEntityPolicy(
- ...policies: Array>
- ): CatalogBuilder;
- addEntityProvider(
- ...providers: Array>
- ): CatalogBuilder;
- addLocationAnalyzers(
- ...analyzers: Array>
- ): CatalogBuilder;
- addPermissionRules(
- ...permissionRules: Array<
- CatalogPermissionRuleInput | Array
- >
- ): this;
- addPermissions(...permissions: Array>): this;
- addProcessor(
- ...processors: Array>
- ): CatalogBuilder;
- build(): Promise<{
- processingEngine: CatalogProcessingEngine;
- router: Router;
- }>;
- static create(env: CatalogEnvironment): CatalogBuilder;
- getDefaultProcessors(): CatalogProcessor_2[];
- replaceEntityPolicies(policies: EntityPolicy[]): CatalogBuilder;
- replaceProcessors(processors: CatalogProcessor_2[]): CatalogBuilder;
- setAllowedLocationTypes(allowedLocationTypes: string[]): CatalogBuilder;
- setEntityDataParser(parser: CatalogProcessorParser_2): CatalogBuilder;
- setEventBroker(broker: EventBroker | EventsService): CatalogBuilder;
- setFieldFormatValidators(validators: Partial): CatalogBuilder;
- setLocationAnalyzer(locationAnalyzer: LocationAnalyzer_2): CatalogBuilder;
- setPlaceholderResolver(
- key: string,
- resolver: PlaceholderResolver_2,
- ): CatalogBuilder;
- setProcessingInterval(
- processingInterval: ProcessingIntervalFunction,
- ): CatalogBuilder;
- setProcessingIntervalSeconds(seconds: number): CatalogBuilder;
- // (undocumented)
- subscribe(options: {
- onProcessingError: (event: {
- unprocessedEntity: Entity;
- errors: Error[];
- }) => Promise | void;
- }): void;
- useLegacySingleProcessorValidation(): this;
-}
-
-// @public @deprecated (undocumented)
-export type CatalogCollatorEntityTransformer =
- CatalogCollatorEntityTransformer_2;
-
-// @public @deprecated (undocumented)
-export type CatalogEnvironment = {
- logger: LoggerService;
- database: DatabaseService;
- config: RootConfigService;
- reader: UrlReaderService;
- permissions: PermissionsService | PermissionAuthorizer;
- permissionsRegistry?: PermissionsRegistryService;
- scheduler?: SchedulerService;
- discovery?: DiscoveryService;
- auth?: AuthService;
- httpAuth?: HttpAuthService;
- auditor?: AuditorService;
-};
-
-// @public
-export type CatalogPermissionRuleInput<
- TParams extends PermissionRuleParams = PermissionRuleParams,
-> = PermissionRule;
-
// @public
const catalogPlugin: BackendFeature;
export default catalogPlugin;
-// @public
-export interface CatalogProcessingEngine {
- // (undocumented)
- start(): Promise;
- // (undocumented)
- stop(): Promise;
-}
-
-// @public @deprecated (undocumented)
-export type CatalogProcessor = CatalogProcessor_2;
-
-// @public @deprecated (undocumented)
-export type CatalogProcessorCache = CatalogProcessorCache_2;
-
-// @public @deprecated (undocumented)
-export type CatalogProcessorEmit = CatalogProcessorEmit_2;
-
-// @public @deprecated (undocumented)
-export type CatalogProcessorEntityResult = CatalogProcessorEntityResult_2;
-
-// @public @deprecated (undocumented)
-export type CatalogProcessorErrorResult = CatalogProcessorErrorResult_2;
-
-// @public @deprecated (undocumented)
-export type CatalogProcessorLocationResult = CatalogProcessorLocationResult_2;
-
-// @public @deprecated (undocumented)
-export type CatalogProcessorParser = CatalogProcessorParser_2;
-
-// @public @deprecated (undocumented)
-export type CatalogProcessorRefreshKeysResult =
- CatalogProcessorRefreshKeysResult_2;
-
-// @public @deprecated (undocumented)
-export type CatalogProcessorRelationResult = CatalogProcessorRelationResult_2;
-
-// @public @deprecated (undocumented)
-export type CatalogProcessorResult = CatalogProcessorResult_2;
-
// @public (undocumented)
-export class CodeOwnersProcessor implements CatalogProcessor_2 {
+export class CodeOwnersProcessor implements CatalogProcessor {
constructor(options: {
integrations: ScmIntegrationRegistry;
logger: LoggerService;
@@ -274,227 +92,49 @@ export class CodeOwnersProcessor implements CatalogProcessor_2 {
// (undocumented)
getProcessorName(): string;
// (undocumented)
- preProcessEntity(entity: Entity, location: LocationSpec_2): Promise;
+ preProcessEntity(entity: Entity, location: LocationSpec): Promise;
}
-// @public
-export function createRandomProcessingInterval(options: {
- minSeconds: number;
- maxSeconds: number;
-}): ProcessingIntervalFunction;
-
-// @public @deprecated (undocumented)
-export class DefaultCatalogCollator {
- constructor(options: {
- discovery: DiscoveryService;
- tokenManager: TokenManager;
- locationTemplate?: string;
- filter?: GetEntitiesRequest['filter'];
- catalogClient?: CatalogApi;
- });
- // (undocumented)
- protected applyArgsToFormat(
- format: string,
- args: Record,
- ): string;
- // (undocumented)
- protected readonly catalogClient: CatalogApi;
- // (undocumented)
- protected discovery: DiscoveryService;
- // (undocumented)
- execute(): Promise;
- // (undocumented)
- protected filter?: GetEntitiesRequest['filter'];
- // (undocumented)
- static fromConfig(
- _config: Config,
- options: {
- discovery: DiscoveryService;
- tokenManager: TokenManager;
- filter?: GetEntitiesRequest['filter'];
- },
- ): DefaultCatalogCollator;
- // (undocumented)
- protected locationTemplate: string;
- // (undocumented)
- protected tokenManager: TokenManager;
- // (undocumented)
- readonly type: string;
- // (undocumented)
- readonly visibilityPermission: Permission;
-}
-
-// @public @deprecated (undocumented)
-export const defaultCatalogCollatorEntityTransformer: CatalogCollatorEntityTransformer_2;
-
-// @public @deprecated (undocumented)
-export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory {
- // (undocumented)
- static fromConfig(
- configRoot: Config,
- options: DefaultCatalogCollatorFactoryOptions,
- ): DefaultCatalogCollatorFactory;
- // (undocumented)
- getCollator(): Promise;
- // (undocumented)
- readonly type = 'software-catalog';
- // (undocumented)
- readonly visibilityPermission: Permission;
-}
-
-// @public @deprecated (undocumented)
-export type DefaultCatalogCollatorFactoryOptions = {
- auth?: AuthService;
- discovery: DiscoveryService;
- tokenManager?: TokenManager;
- locationTemplate?: string;
- filter?: GetEntitiesRequest['filter'];
- batchSize?: number;
- catalogClient?: CatalogApi;
- entityTransformer?: CatalogCollatorEntityTransformer;
-};
-
-// @public @deprecated (undocumented)
-export type DeferredEntity = DeferredEntity_2;
-
-// @public @deprecated (undocumented)
-export type EntitiesSearchFilter = EntitiesSearchFilter_2;
-
-// @public @deprecated (undocumented)
-export type EntityFilter = EntityFilter_2;
-
-// @public @deprecated (undocumented)
-export type EntityProvider = EntityProvider_2;
-
-// @public @deprecated (undocumented)
-export type EntityProviderConnection = EntityProviderConnection_2;
-
-// @public @deprecated (undocumented)
-export type EntityProviderMutation = EntityProviderMutation_2;
-
-// @public @deprecated (undocumented)
-export type EntityRelationSpec = EntityRelationSpec_2;
-
// @public (undocumented)
-export class FileReaderProcessor implements CatalogProcessor_2 {
+export class FileReaderProcessor implements CatalogProcessor {
// (undocumented)
getProcessorName(): string;
// (undocumented)
readLocation(
- location: LocationSpec_2,
+ location: LocationSpec,
optional: boolean,
- emit: CatalogProcessorEmit_2,
- parser: CatalogProcessorParser_2,
+ emit: CatalogProcessorEmit,
+ parser: CatalogProcessorParser,
): Promise;
}
-// @public @deprecated (undocumented)
-export type LocationAnalyzer = LocationAnalyzer_2;
-
-// @public @deprecated
-export class LocationEntityProcessor implements CatalogProcessor_2 {
- constructor(options: LocationEntityProcessorOptions);
- // (undocumented)
- getProcessorName(): string;
- // (undocumented)
- postProcessEntity(
- entity: Entity,
- location: LocationSpec_2,
- emit: CatalogProcessorEmit_2,
- ): Promise;
-}
-
-// @public @deprecated (undocumented)
-export type LocationEntityProcessorOptions = {
- integrations: ScmIntegrationRegistry;
-};
-
-// @public @deprecated
-export type LocationSpec = LocationSpec_2;
-
-// @public @deprecated (undocumented)
-export const locationSpecToLocationEntity: typeof locationSpecToLocationEntity_2;
-
-// @public @deprecated (undocumented)
-export const locationSpecToMetadataName: typeof locationSpecToMetadataName_2;
-
-// @public (undocumented)
-export function parseEntityYaml(
- data: Buffer,
- location: LocationSpec_2,
-): Iterable;
-
// @public
-export class PlaceholderProcessor implements CatalogProcessor_2 {
+export class PlaceholderProcessor implements CatalogProcessor {
constructor(options: PlaceholderProcessorOptions);
// (undocumented)
getProcessorName(): string;
// (undocumented)
preProcessEntity(
entity: Entity,
- location: LocationSpec_2,
- emit: CatalogProcessorEmit_2,
+ location: LocationSpec,
+ emit: CatalogProcessorEmit,
): Promise;
}
// @public (undocumented)
export type PlaceholderProcessorOptions = {
- resolvers: Record;
+ resolvers: Record;
reader: UrlReaderService;
integrations: ScmIntegrationRegistry;
};
-// @public @deprecated (undocumented)
-export type PlaceholderResolver = PlaceholderResolver_2;
-
-// @public @deprecated (undocumented)
-export type PlaceholderResolverParams = PlaceholderResolverParams_2;
-
-// @public @deprecated (undocumented)
-export type PlaceholderResolverRead = PlaceholderResolverRead_2;
-
-// @public @deprecated (undocumented)
-export type PlaceholderResolverResolveUrl = PlaceholderResolverResolveUrl_2;
-
-// @public
-export type ProcessingIntervalFunction = () => number;
-
-// @public @deprecated (undocumented)
-export const processingResult: Readonly<{
- readonly notFoundError: (
- atLocation: LocationSpec_2,
- message: string,
- ) => CatalogProcessorResult_2;
- readonly inputError: (
- atLocation: LocationSpec_2,
- message: string,
- ) => CatalogProcessorResult_2;
- readonly generalError: (
- atLocation: LocationSpec_2,
- message: string,
- ) => CatalogProcessorResult_2;
- readonly location: (newLocation: LocationSpec_2) => CatalogProcessorResult_2;
- readonly entity: (
- atLocation: LocationSpec_2,
- newEntity: Entity,
- options?: {
- locationKey?: string | null;
- },
- ) => CatalogProcessorResult_2;
- readonly relation: (spec: EntityRelationSpec_2) => CatalogProcessorResult_2;
- readonly refresh: (key: string) => CatalogProcessorResult_2;
-}>;
-
-// @public @deprecated (undocumented)
-export type ScmLocationAnalyzer = ScmLocationAnalyzer_2;
-
// @public
export function transformLegacyPolicyToProcessor(
policy: EntityPolicy,
-): CatalogProcessor_2;
+): CatalogProcessor;
// @public (undocumented)
-export class UrlReaderProcessor implements CatalogProcessor_2 {
+export class UrlReaderProcessor implements CatalogProcessor {
constructor(options: {
reader: UrlReaderService;
logger: LoggerService;
@@ -504,11 +144,11 @@ export class UrlReaderProcessor implements CatalogProcessor_2 {
getProcessorName(): string;
// (undocumented)
readLocation(
- location: LocationSpec_2,
+ location: LocationSpec,
optional: boolean,
- emit: CatalogProcessorEmit_2,
- parser: CatalogProcessorParser_2,
- cache: CatalogProcessorCache_2,
+ emit: CatalogProcessorEmit,
+ parser: CatalogProcessorParser,
+ cache: CatalogProcessorCache,
): Promise;
}
```
diff --git a/plugins/catalog-backend/src/alpha.ts b/plugins/catalog-backend/src/alpha.ts
index 4d40b8733e..82585fcf1d 100644
--- a/plugins/catalog-backend/src/alpha.ts
+++ b/plugins/catalog-backend/src/alpha.ts
@@ -14,10 +14,4 @@
* limitations under the License.
*/
-import { catalogPlugin } from './service/CatalogPlugin';
-
-/** @alpha */
-const _feature = catalogPlugin;
-export default _feature;
-
export * from './permissions';
diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts
index 750eebbadb..a09e938575 100644
--- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts
+++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.test.ts
@@ -32,7 +32,7 @@ import {
DbRefreshStateRow,
DbRelationsRow,
} from './tables';
-import { createRandomProcessingInterval } from '../processing';
+import { createRandomProcessingInterval } from '../processing/refresh';
import { timestampToDateTime } from './conversion';
import { generateStableHash } from './util';
import { LoggerService } from '@backstage/backend-plugin-api';
diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts
index 0aa31dd36d..81c26c66ab 100644
--- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts
+++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts
@@ -19,7 +19,7 @@ import { ConflictError } from '@backstage/errors';
import { DeferredEntity } from '@backstage/plugin-catalog-node';
import { Knex } from 'knex';
import lodash from 'lodash';
-import { ProcessingIntervalFunction } from '../processing';
+import { ProcessingIntervalFunction } from '../processing/refresh';
import { rethrowError, timestampToDateTime } from './conversion';
import { initDatabaseMetrics } from './metrics';
import {
diff --git a/plugins/catalog-backend/src/deprecated.ts b/plugins/catalog-backend/src/deprecated.ts
deleted file mode 100644
index 023e93e664..0000000000
--- a/plugins/catalog-backend/src/deprecated.ts
+++ /dev/null
@@ -1,502 +0,0 @@
-/*
- * Copyright 2023 The 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 {
- TokenManager,
- createLegacyAuthAdapters,
-} from '@backstage/backend-common';
-import { AuthService, DiscoveryService } from '@backstage/backend-plugin-api';
-import {
- CatalogApi,
- CatalogClient,
- EntityFilterQuery,
- GetEntitiesRequest,
-} from '@backstage/catalog-client';
-import {
- Entity,
- isGroupEntity,
- isUserEntity,
- stringifyEntityRef,
-} from '@backstage/catalog-model';
-import { Config } from '@backstage/config';
-import {
- CatalogEntityDocument,
- type AnalyzeLocationEntityField as _AnalyzeLocationEntityField,
- type AnalyzeLocationExistingEntity as _AnalyzeLocationExistingEntity,
- type AnalyzeLocationGenerateEntity as _AnalyzeLocationGenerateEntity,
- type AnalyzeLocationRequest as _AnalyzeLocationRequest,
- type AnalyzeLocationResponse as _AnalyzeLocationResponse,
- type LocationSpec as _LocationSpec,
-} from '@backstage/plugin-catalog-common';
-import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';
-import {
- locationSpecToMetadataName as _locationSpecToMetadataName,
- locationSpecToLocationEntity as _locationSpecToLocationEntity,
- processingResult as _processingResult,
- type EntitiesSearchFilter as _EntitiesSearchFilter,
- type EntityFilter as _EntityFilter,
- type DeferredEntity as _DeferredEntity,
- type EntityRelationSpec as _EntityRelationSpec,
- type CatalogProcessor as _CatalogProcessor,
- type CatalogProcessorParser as _CatalogProcessorParser,
- type CatalogProcessorCache as _CatalogProcessorCache,
- type CatalogProcessorEmit as _CatalogProcessorEmit,
- type CatalogProcessorLocationResult as _CatalogProcessorLocationResult,
- type CatalogProcessorEntityResult as _CatalogProcessorEntityResult,
- type CatalogProcessorRelationResult as _CatalogProcessorRelationResult,
- type CatalogProcessorErrorResult as _CatalogProcessorErrorResult,
- type CatalogProcessorRefreshKeysResult as _CatalogProcessorRefreshKeysResult,
- type CatalogProcessorResult as _CatalogProcessorResult,
- type EntityProvider as _EntityProvider,
- type EntityProviderConnection as _EntityProviderConnection,
- type EntityProviderMutation as _EntityProviderMutation,
- type AnalyzeOptions as _AnalyzeOptions,
- type PlaceholderResolver as _PlaceholderResolver,
- type PlaceholderResolverParams as _PlaceholderResolverParams,
- type PlaceholderResolverRead as _PlaceholderResolverRead,
- type PlaceholderResolverResolveUrl as _PlaceholderResolverResolveUrl,
- type LocationAnalyzer as _LocationAnalyzer,
- type ScmLocationAnalyzer as _ScmLocationAnalyzer,
-} from '@backstage/plugin-catalog-node';
-import { Permission } from '@backstage/plugin-permission-common';
-import {
- defaultCatalogCollatorEntityTransformer as _defaultCatalogCollatorEntityTransformer,
- type CatalogCollatorEntityTransformer as _CatalogCollatorEntityTransformer,
-} from '@backstage/plugin-search-backend-module-catalog';
-import { DocumentCollatorFactory } from '@backstage/plugin-search-common';
-import { Readable } from 'stream';
-
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export const locationSpecToMetadataName = _locationSpecToMetadataName;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export const locationSpecToLocationEntity = _locationSpecToLocationEntity;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export const processingResult = _processingResult;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type EntitiesSearchFilter = _EntitiesSearchFilter;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type EntityFilter = _EntityFilter;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type DeferredEntity = _DeferredEntity;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type EntityRelationSpec = _EntityRelationSpec;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type CatalogProcessor = _CatalogProcessor;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type CatalogProcessorParser = _CatalogProcessorParser;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type CatalogProcessorCache = _CatalogProcessorCache;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type CatalogProcessorEmit = _CatalogProcessorEmit;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type CatalogProcessorLocationResult = _CatalogProcessorLocationResult;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type CatalogProcessorEntityResult = _CatalogProcessorEntityResult;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type CatalogProcessorRelationResult = _CatalogProcessorRelationResult;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type CatalogProcessorErrorResult = _CatalogProcessorErrorResult;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type CatalogProcessorRefreshKeysResult =
- _CatalogProcessorRefreshKeysResult;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type CatalogProcessorResult = _CatalogProcessorResult;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type EntityProvider = _EntityProvider;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type EntityProviderConnection = _EntityProviderConnection;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type EntityProviderMutation = _EntityProviderMutation;
-
-/**
- * Holds the entity location information.
- *
- * @remarks
- *
- * `presence` flag: when using repo importer plugin, location is being created before the component yaml file is merged to the main branch.
- * This flag is then set to indicate that the file can be not present.
- * default value: 'required'.
- *
- * @public
- * @deprecated use the same type from `@backstage/plugin-catalog-common` instead
- */
-export type LocationSpec = _LocationSpec;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type AnalyzeOptions = _AnalyzeOptions;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type LocationAnalyzer = _LocationAnalyzer;
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type ScmLocationAnalyzer = _ScmLocationAnalyzer;
-
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type PlaceholderResolver = _PlaceholderResolver;
-
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type PlaceholderResolverParams = _PlaceholderResolverParams;
-
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type PlaceholderResolverRead = _PlaceholderResolverRead;
-
-/**
- * @public
- * @deprecated import from `@backstage/plugin-catalog-node` instead
- */
-export type PlaceholderResolverResolveUrl = _PlaceholderResolverResolveUrl;
-
-/**
- * @public
- * @deprecated use the same type from `@backstage/plugin-catalog-common` instead
- */
-export type AnalyzeLocationRequest = _AnalyzeLocationRequest;
-/**
- * @public
- * @deprecated use the same type from `@backstage/plugin-catalog-common` instead
- */
-export type AnalyzeLocationResponse = _AnalyzeLocationResponse;
-
-/**
- * If the folder pointed to already contained catalog info yaml files, they are
- * read and emitted like this so that the frontend can inform the user that it
- * located them and can make sure to register them as well if they weren't
- * already
- * @public
- * @deprecated use the same type from `@backstage/plugin-catalog-common` instead
- */
-export type AnalyzeLocationExistingEntity = _AnalyzeLocationExistingEntity;
-/**
- * This is some form of representation of what the analyzer could deduce.
- * We should probably have a chat about how this can best be conveyed to
- * the frontend. It'll probably contain a (possibly incomplete) entity, plus
- * enough info for the frontend to know what form data to show to the user
- * for overriding/completing the info.
- * @public
- * @deprecated use the same type from `@backstage/plugin-catalog-common` instead
- */
-export type AnalyzeLocationGenerateEntity = _AnalyzeLocationGenerateEntity;
-
-/**
- *
- * This is where I get really vague. Something like this perhaps? Or it could be
- * something like a json-schema that contains enough info for the frontend to
- * be able to present a form and explanations
- * @public
- * @deprecated use the same type from `@backstage/plugin-catalog-common` instead
- */
-export type AnalyzeLocationEntityField = _AnalyzeLocationEntityField;
-
-namespace search {
- const configKey = 'search.collators.catalog';
-
- const defaults = {
- schedule: {
- frequency: { minutes: 10 },
- timeout: { minutes: 15 },
- initialDelay: { seconds: 3 },
- },
- collatorOptions: {
- locationTemplate: '/catalog/:namespace/:kind/:name',
- filter: undefined,
- batchSize: 500,
- },
- };
-
- export const readCollatorConfigOptions = (
- configRoot: Config,
- ): {
- locationTemplate: string;
- filter: EntityFilterQuery | undefined;
- batchSize: number;
- } => {
- const config = configRoot.getOptionalConfig(configKey);
- if (!config) {
- return defaults.collatorOptions;
- }
-
- return {
- locationTemplate:
- config.getOptionalString('locationTemplate') ??
- defaults.collatorOptions.locationTemplate,
- filter:
- config.getOptional('filter') ??
- defaults.collatorOptions.filter,
- batchSize:
- config.getOptionalNumber('batchSize') ??
- defaults.collatorOptions.batchSize,
- };
- };
-
- export const getDocumentText = (entity: Entity): string => {
- const documentTexts: string[] = [];
- if (entity.metadata.description) {
- documentTexts.push(entity.metadata.description);
- }
-
- if (isUserEntity(entity) || isGroupEntity(entity)) {
- if (entity.spec?.profile?.displayName) {
- documentTexts.push(entity.spec.profile.displayName);
- }
- }
-
- if (isUserEntity(entity)) {
- if (entity.spec?.profile?.email) {
- documentTexts.push(entity.spec.profile.email);
- }
- }
-
- return documentTexts.join(' : ');
- };
-}
-
-/**
- * @public
- * @deprecated import from `@backstage/plugin-search-backend-module-catalog` instead
- */
-export const defaultCatalogCollatorEntityTransformer =
- _defaultCatalogCollatorEntityTransformer;
-
-/**
- * @public
- * @deprecated This is no longer supported since the new backend system migration
- */
-export class DefaultCatalogCollatorFactory implements DocumentCollatorFactory {
- public readonly type = 'software-catalog';
- public readonly visibilityPermission: Permission =
- catalogEntityReadPermission;
-
- private locationTemplate: string;
- private filter?: GetEntitiesRequest['filter'];
- private batchSize: number;
- private readonly catalogClient: CatalogApi;
- private entityTransformer: CatalogCollatorEntityTransformer;
- private auth: AuthService;
-
- static fromConfig(
- configRoot: Config,
- options: DefaultCatalogCollatorFactoryOptions,
- ) {
- const configOptions = search.readCollatorConfigOptions(configRoot);
- const { auth: adaptedAuth } = createLegacyAuthAdapters({
- auth: options.auth,
- discovery: options.discovery,
- tokenManager: options.tokenManager,
- });
- return new DefaultCatalogCollatorFactory({
- locationTemplate:
- options.locationTemplate ?? configOptions.locationTemplate,
- filter: options.filter ?? configOptions.filter,
- batchSize: options.batchSize ?? configOptions.batchSize,
- entityTransformer: options.entityTransformer,
- auth: adaptedAuth,
- discovery: options.discovery,
- catalogClient: options.catalogClient,
- });
- }
-
- private constructor(options: {
- locationTemplate: string;
- filter: GetEntitiesRequest['filter'];
- batchSize: number;
- entityTransformer?: CatalogCollatorEntityTransformer;
- auth: AuthService;
- discovery: DiscoveryService;
- catalogClient?: CatalogApi;
- }) {
- const {
- auth,
- batchSize,
- discovery,
- locationTemplate,
- filter,
- catalogClient,
- entityTransformer,
- } = options;
-
- this.locationTemplate = locationTemplate;
- this.filter = filter;
- this.batchSize = batchSize;
- this.catalogClient =
- catalogClient || new CatalogClient({ discoveryApi: discovery });
- this.entityTransformer =
- entityTransformer ?? defaultCatalogCollatorEntityTransformer;
- this.auth = auth;
- }
-
- async getCollator(): Promise {
- return Readable.from(this.execute());
- }
-
- private async *execute(): AsyncGenerator {
- let entitiesRetrieved = 0;
- let cursor: string | undefined = undefined;
-
- do {
- const { token } = await this.auth.getPluginRequestToken({
- onBehalfOf: await this.auth.getOwnServiceCredentials(),
- targetPluginId: 'catalog',
- });
- const response = await this.catalogClient.queryEntities(
- {
- filter: this.filter,
- limit: this.batchSize,
- ...(cursor ? { cursor } : {}),
- },
- { token },
- );
- cursor = response.pageInfo.nextCursor;
- entitiesRetrieved += response.items.length;
-
- for (const entity of response.items) {
- yield {
- ...this.entityTransformer(entity),
- authorization: {
- resourceRef: stringifyEntityRef(entity),
- },
- location: this.applyArgsToFormat(this.locationTemplate, {
- namespace: entity.metadata.namespace || 'default',
- kind: entity.kind,
- name: entity.metadata.name,
- }),
- };
- }
- } while (cursor);
- }
-
- private applyArgsToFormat(
- format: string,
- args: Record,
- ): string {
- let formatted = format;
-
- for (const [key, value] of Object.entries(args)) {
- formatted = formatted.replace(`:${key}`, value);
- }
-
- return formatted.toLowerCase();
- }
-}
-
-/**
- * @public
- * @deprecated This is no longer supported since the new backend system migration
- */
-export type DefaultCatalogCollatorFactoryOptions = {
- auth?: AuthService;
- discovery: DiscoveryService;
- tokenManager?: TokenManager;
- /**
- * @deprecated Use the config key `search.collators.catalog.locationTemplate` instead.
- */
- locationTemplate?: string;
- /**
- * @deprecated Use the config key `search.collators.catalog.filter` instead.
- */
- filter?: GetEntitiesRequest['filter'];
- /**
- * @deprecated Use the config key `search.collators.catalog.batchSize` instead.
- */
- batchSize?: number;
- // TODO(freben): Change to required CatalogService instead when fully migrated to the new backend system.
- catalogClient?: CatalogApi;
- /**
- * Allows you to customize how entities are shaped into documents.
- */
- entityTransformer?: CatalogCollatorEntityTransformer;
-};
-
-/**
- * @public
- * @deprecated import from `@backstage/plugin-search-backend-module-catalog` instead
- */
-export type CatalogCollatorEntityTransformer =
- _CatalogCollatorEntityTransformer;
diff --git a/plugins/catalog-backend/src/index.ts b/plugins/catalog-backend/src/index.ts
index 3de6cea22e..72bfde4cdf 100644
--- a/plugins/catalog-backend/src/index.ts
+++ b/plugins/catalog-backend/src/index.ts
@@ -22,9 +22,4 @@
export { catalogPlugin as default } from './service/CatalogPlugin';
export * from './processors';
-export * from './processing';
-export * from './search';
-export * from './service';
-export * from './deprecated';
export * from './constants';
-export * from './util';
diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts
index 5456371e15..b1fcba5d40 100644
--- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts
+++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts
@@ -84,7 +84,6 @@ function addProcessorAttributes(
);
}
-/** @public */
export class DefaultCatalogProcessingOrchestrator
implements CatalogProcessingOrchestrator
{
diff --git a/plugins/catalog-backend/src/processing/refresh.ts b/plugins/catalog-backend/src/processing/refresh.ts
index f9f596cdb9..9efc1dfcdc 100644
--- a/plugins/catalog-backend/src/processing/refresh.ts
+++ b/plugins/catalog-backend/src/processing/refresh.ts
@@ -16,14 +16,12 @@
/**
* Function that returns the catalog processing interval in seconds.
- * @public
*/
export type ProcessingIntervalFunction = () => number;
/**
* Creates a function that returns a random processing interval between minSeconds and maxSeconds.
* @returns A {@link ProcessingIntervalFunction} that provides the next processing interval
- * @public
*/
export function createRandomProcessingInterval(options: {
minSeconds: number;
diff --git a/plugins/catalog-backend/src/processors/LocationEntityProcessor.test.ts b/plugins/catalog-backend/src/processors/LocationEntityProcessor.test.ts
deleted file mode 100644
index 5e000f87d6..0000000000
--- a/plugins/catalog-backend/src/processors/LocationEntityProcessor.test.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { ConfigReader } from '@backstage/config';
-import {
- ScmIntegrations,
- ScmIntegrationRegistry,
-} from '@backstage/integration';
-import path from 'path';
-import { toAbsoluteUrl } from './LocationEntityProcessor';
-import { LocationSpec } from '@backstage/plugin-catalog-common';
-
-describe('LocationEntityProcessor', () => {
- describe('toAbsoluteUrl', () => {
- it('handles files', () => {
- const integrations = {} as unknown as ScmIntegrationRegistry;
- const base: LocationSpec = {
- type: 'file',
- target: `some${path.sep}path${path.sep}catalog-info.yaml`,
- };
- expect(toAbsoluteUrl(integrations, base, `.${path.sep}c`)).toBe(
- `some${path.sep}path${path.sep}c`,
- );
- expect(toAbsoluteUrl(integrations, base, `${path.sep}c`)).toBe(
- `${path.sep}c`,
- );
- });
-
- it('handles urls', () => {
- const integrations = ScmIntegrations.fromConfig(new ConfigReader({}));
- const base: LocationSpec = {
- type: 'url',
- target: 'http://a.com/b/catalog-info.yaml',
- };
- jest.spyOn(integrations, 'resolveUrl');
-
- expect(toAbsoluteUrl(integrations, base, './c/d')).toBe(
- 'http://a.com/b/c/d',
- );
- expect(toAbsoluteUrl(integrations, base, 'c/d')).toBe(
- 'http://a.com/b/c/d',
- );
- expect(toAbsoluteUrl(integrations, base, 'http://b.com/z')).toBe(
- 'http://b.com/z',
- );
-
- expect(integrations.resolveUrl).toHaveBeenCalledTimes(3);
- });
-
- it('handles azure urls specifically', () => {
- const integrations = ScmIntegrations.fromConfig(
- new ConfigReader({
- integrations: {
- azure: [{ host: 'dev.azure.com' }],
- },
- }),
- );
-
- expect(
- toAbsoluteUrl(
- integrations,
- {
- type: 'url',
- target:
- 'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml',
- },
- './a.yaml',
- ),
- ).toBe(
- 'https://dev.azure.com/organization/project/_git/repository?path=%2Fa.yaml',
- );
- });
- });
-});
diff --git a/plugins/catalog-backend/src/processors/LocationEntityProcessor.ts b/plugins/catalog-backend/src/processors/LocationEntityProcessor.ts
deleted file mode 100644
index 7bfd7f33cd..0000000000
--- a/plugins/catalog-backend/src/processors/LocationEntityProcessor.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { Entity, LocationEntity } from '@backstage/catalog-model';
-import { ScmIntegrationRegistry } from '@backstage/integration';
-import path from 'path';
-import { LocationSpec } from '@backstage/plugin-catalog-common';
-import {
- processingResult,
- CatalogProcessor,
- CatalogProcessorEmit,
-} from '@backstage/plugin-catalog-node';
-
-export function toAbsoluteUrl(
- integrations: ScmIntegrationRegistry,
- base: LocationSpec,
- target: string,
-): string {
- try {
- if (base.type === 'file') {
- if (target.startsWith('.')) {
- return path.join(path.dirname(base.target), target);
- }
- return target;
- }
- return integrations.resolveUrl({ url: target, base: base.target });
- } catch (e) {
- return target;
- }
-}
-
-/**
- * @public
- * @deprecated This processor should no longer be used
- */
-export type LocationEntityProcessorOptions = {
- integrations: ScmIntegrationRegistry;
-};
-
-/**
- * Legacy processor, should not be used.
- *
- * @remarks
- *
- * In the old catalog architecture, this processor translated Location entities
- * into URLs that should be fetched. This is no longer needed since the engine
- * handles this internally.
- *
- * @public
- * @deprecated This processor should no longer be used
- */
-export class LocationEntityProcessor implements CatalogProcessor {
- constructor(private readonly options: LocationEntityProcessorOptions) {}
-
- getProcessorName(): string {
- return 'LocationEntityProcessor';
- }
-
- async postProcessEntity(
- entity: Entity,
- location: LocationSpec,
- emit: CatalogProcessorEmit,
- ): Promise {
- if (entity.kind === 'Location') {
- const locationEntity = entity as LocationEntity;
-
- const type = locationEntity.spec.type || location.type;
- if (type === 'file' && location.target.endsWith(path.sep)) {
- emit(
- processingResult.inputError(
- location,
- `LocationEntityProcessor cannot handle ${type} type location with target ${location.target} that ends with a path separator`,
- ),
- );
- }
-
- const targets = new Array();
- if (locationEntity.spec.target) {
- targets.push(locationEntity.spec.target);
- }
- if (locationEntity.spec.targets) {
- targets.push(...locationEntity.spec.targets);
- }
-
- for (const maybeRelativeTarget of targets) {
- const target = toAbsoluteUrl(
- this.options.integrations,
- location,
- maybeRelativeTarget,
- );
- emit(processingResult.location({ type, target }));
- }
- }
-
- return entity;
- }
-}
diff --git a/plugins/catalog-backend/src/processors/index.ts b/plugins/catalog-backend/src/processors/index.ts
index ee2204f20f..1a43444b1a 100644
--- a/plugins/catalog-backend/src/processors/index.ts
+++ b/plugins/catalog-backend/src/processors/index.ts
@@ -19,8 +19,6 @@ export { AnnotateScmSlugEntityProcessor } from './AnnotateScmSlugEntityProcessor
export { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor';
export { CodeOwnersProcessor } from './CodeOwnersProcessor';
export { FileReaderProcessor } from './FileReaderProcessor';
-export { LocationEntityProcessor } from './LocationEntityProcessor';
-export type { LocationEntityProcessorOptions } from './LocationEntityProcessor';
export { PlaceholderProcessor } from './PlaceholderProcessor';
export type { PlaceholderProcessorOptions } from './PlaceholderProcessor';
export { UrlReaderProcessor } from './UrlReaderProcessor';
diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts
deleted file mode 100644
index 9473d45bb6..0000000000
--- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.test.ts
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 2021 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { TokenManager } from '@backstage/backend-common';
-import {
- mockServices,
- registerMswTestHooks,
-} from '@backstage/backend-test-utils';
-import { Entity } from '@backstage/catalog-model';
-import { DefaultCatalogCollator } from './DefaultCatalogCollator';
-import { setupServer } from 'msw/node';
-import { rest } from 'msw';
-import { ConfigReader } from '@backstage/config';
-
-const server = setupServer();
-
-const expectedEntities: Entity[] = [
- {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Component',
- metadata: {
- name: 'test-entity',
- description: 'The expected description',
- },
- spec: {
- type: 'some-type',
- lifecycle: 'experimental',
- owner: 'someone',
- },
- },
- {
- apiVersion: 'backstage.io/v1alpha1',
- kind: 'Component',
- metadata: {
- title: 'Test Entity',
- name: 'test-entity-2',
- description: 'The expected description 2',
- },
- spec: {
- type: 'some-type',
- lifecycle: 'experimental',
- owner: 'someone',
- },
- },
-];
-
-describe('DefaultCatalogCollator', () => {
- const mockDiscovery = mockServices.discovery.mock({
- getBaseUrl: async () => 'http://localhost:7007',
- });
- let mockTokenManager: jest.Mocked;
- let collator: DefaultCatalogCollator;
-
- registerMswTestHooks(server);
- beforeAll(() => {
- mockTokenManager = {
- getToken: jest.fn().mockResolvedValue({ token: '' }),
- authenticate: jest.fn(),
- };
- collator = new DefaultCatalogCollator({
- discovery: mockDiscovery,
- tokenManager: mockTokenManager,
- });
- });
- beforeEach(() => {
- server.use(
- rest.get('http://localhost:7007/entities', (req, res, ctx) => {
- if (req.url.searchParams.has('filter')) {
- const filter = req.url.searchParams.get('filter');
- if (filter === 'kind=Foo,kind=Bar') {
- // When filtering on the 'Foo,Bar' kinds we simply return no items, to simulate a filter
- return res(ctx.json([]));
- }
- throw new Error('Unexpected filter parameter');
- }
- return res(ctx.json(expectedEntities));
- }),
- );
- });
- afterAll(() => {
- jest.useRealTimers();
- });
-
- it('fetches from the configured catalog service', async () => {
- const documents = await collator.execute();
- expect(mockDiscovery.getBaseUrl).toHaveBeenCalledWith('catalog');
- expect(documents).toHaveLength(expectedEntities.length);
- });
-
- it('maps a returned entity to an expected CatalogEntityDocument', async () => {
- const documents = await collator.execute();
- expect(documents[0]).toMatchObject({
- title: expectedEntities[0].metadata.name,
- location: '/catalog/default/component/test-entity',
- text: expectedEntities[0].metadata.description,
- namespace: 'default',
- componentType: expectedEntities[0]!.spec!.type,
- lifecycle: expectedEntities[0]!.spec!.lifecycle,
- owner: expectedEntities[0]!.spec!.owner,
- authorization: {
- resourceRef: 'component:default/test-entity',
- },
- });
- expect(documents[1]).toMatchObject({
- title: `${expectedEntities[1].metadata.title} (${expectedEntities[1].metadata.name})`,
- location: '/catalog/default/component/test-entity-2',
- text: expectedEntities[1].metadata.description,
- namespace: 'default',
- componentType: expectedEntities[1]!.spec!.type,
- lifecycle: expectedEntities[1]!.spec!.lifecycle,
- owner: expectedEntities[1]!.spec!.owner,
- authorization: {
- resourceRef: 'component:default/test-entity-2',
- },
- });
- });
-
- it('maps a returned entity with a custom locationTemplate', async () => {
- // Provide an alternate location template.
- collator = new DefaultCatalogCollator({
- discovery: mockDiscovery,
- tokenManager: mockTokenManager,
- locationTemplate: '/software/:name',
- });
-
- const documents = await collator.execute();
- expect(documents[0]).toMatchObject({
- location: '/software/test-entity',
- });
- });
-
- it('allows filtering of the retrieved catalog entities', async () => {
- // Provide an alternate location template.
- collator = DefaultCatalogCollator.fromConfig(new ConfigReader({}), {
- discovery: mockDiscovery,
- tokenManager: mockTokenManager,
- filter: {
- kind: ['Foo', 'Bar'],
- },
- });
-
- const documents = await collator.execute();
- // The simulated 'Foo,Bar' filter should return in an empty list
- expect(documents).toHaveLength(0);
- });
-});
diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts
deleted file mode 100644
index 11f6030ce6..0000000000
--- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2021 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { TokenManager } from '@backstage/backend-common';
-import {
- Entity,
- isUserEntity,
- stringifyEntityRef,
-} from '@backstage/catalog-model';
-import { Config } from '@backstage/config';
-import {
- CatalogApi,
- CatalogClient,
- GetEntitiesRequest,
-} from '@backstage/catalog-client';
-import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common/alpha';
-import { CatalogEntityDocument } from '@backstage/plugin-catalog-common';
-import { Permission } from '@backstage/plugin-permission-common';
-import { DiscoveryService } from '@backstage/backend-plugin-api';
-
-/**
- * @public
- * @deprecated Upgrade to a more recent `@backstage/plugin-search-backend-node` and
- * use `DefaultCatalogCollatorFactory` instead.
- */
-export class DefaultCatalogCollator {
- protected discovery: DiscoveryService;
- protected locationTemplate: string;
- protected filter?: GetEntitiesRequest['filter'];
- protected readonly catalogClient: CatalogApi;
- public readonly type: string = 'software-catalog';
- public readonly visibilityPermission: Permission =
- catalogEntityReadPermission;
- protected tokenManager: TokenManager;
-
- static fromConfig(
- _config: Config,
- options: {
- discovery: DiscoveryService;
- tokenManager: TokenManager;
- filter?: GetEntitiesRequest['filter'];
- },
- ) {
- return new DefaultCatalogCollator({
- ...options,
- });
- }
-
- constructor(options: {
- discovery: DiscoveryService;
- tokenManager: TokenManager;
- locationTemplate?: string;
- filter?: GetEntitiesRequest['filter'];
- catalogClient?: CatalogApi;
- }) {
- const { discovery, locationTemplate, filter, catalogClient, tokenManager } =
- options;
-
- this.discovery = discovery;
- this.locationTemplate =
- locationTemplate || '/catalog/:namespace/:kind/:name';
- this.filter = filter;
- this.catalogClient =
- catalogClient || new CatalogClient({ discoveryApi: discovery });
- this.tokenManager = tokenManager;
- }
-
- protected applyArgsToFormat(
- format: string,
- args: Record,
- ): string {
- let formatted = format;
- for (const [key, value] of Object.entries(args)) {
- formatted = formatted.replace(`:${key}`, value);
- }
- return formatted.toLowerCase();
- }
-
- private getDocumentText(entity: Entity): string {
- let documentText = entity.metadata.description || '';
- if (isUserEntity(entity)) {
- if (entity.spec?.profile?.displayName && documentText) {
- // combine displayName and description
- const displayName = entity.spec?.profile?.displayName;
- documentText = displayName.concat(' : ', documentText);
- } else {
- documentText = entity.spec?.profile?.displayName || documentText;
- }
- }
- return documentText;
- }
-
- async execute() {
- const { token } = await this.tokenManager.getToken();
- const response = await this.catalogClient.getEntities(
- {
- filter: this.filter,
- },
- { token },
- );
- return response.items.map((entity: Entity): CatalogEntityDocument => {
- return {
- title: entity.metadata.title
- ? `${entity.metadata.title} (${entity.metadata.name})`
- : entity.metadata.name,
- location: this.applyArgsToFormat(this.locationTemplate, {
- namespace: entity.metadata.namespace || 'default',
- kind: entity.kind,
- name: entity.metadata.name,
- }),
- text: this.getDocumentText(entity),
- componentType: entity.spec?.type?.toString() || 'other',
- type: entity.spec?.type?.toString() || 'other',
- namespace: entity.metadata.namespace || 'default',
- kind: entity.kind,
- lifecycle: (entity.spec?.lifecycle as string) || '',
- owner: (entity.spec?.owner as string) || '',
- authorization: {
- resourceRef: stringifyEntityRef(entity),
- },
- };
- });
- }
-}
diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts
index 99752ca5cf..c9d75298c8 100644
--- a/plugins/catalog-backend/src/service/CatalogBuilder.ts
+++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-import {
- createLegacyAuthAdapters,
- HostDiscovery,
-} from '@backstage/backend-common';
import {
DefaultNamespaceEntityPolicy,
Entity,
@@ -38,7 +34,6 @@ import {
AuditorService,
AuthService,
DatabaseService,
- DiscoveryService,
HttpAuthService,
LoggerService,
PermissionsRegistryService,
@@ -55,7 +50,6 @@ import {
import {
CatalogProcessor,
CatalogProcessorParser,
- EntitiesSearchFilter,
EntityProvider,
LocationAnalyzer,
PlaceholderResolver,
@@ -65,13 +59,11 @@ import { EventBroker, EventsService } from '@backstage/plugin-events-node';
import {
Permission,
PermissionAuthorizer,
- PermissionRuleParams,
toPermissionEvaluator,
} from '@backstage/plugin-permission-common';
import {
createConditionTransformer,
createPermissionIntegrationRouter,
- PermissionRule,
} from '@backstage/plugin-permission-node';
import { durationToMilliseconds } from '@backstage/types';
import { DefaultCatalogDatabase } from '../database/DefaultCatalogDatabase';
@@ -81,11 +73,11 @@ import { applyDatabaseMigrations } from '../database/migrations';
import { DefaultCatalogRulesEnforcer } from '../ingestion/CatalogRules';
import { RepoLocationAnalyzer } from '../ingestion/LocationAnalyzer';
import { permissionRules as catalogPermissionRules } from '../permissions/rules';
+import { CatalogProcessingEngine } from '../processing/types';
import {
- CatalogProcessingEngine,
createRandomProcessingInterval,
ProcessingIntervalFunction,
-} from '../processing';
+} from '../processing/refresh';
import { connectEntityProviders } from '../processing/connectEntityProviders';
import { evictEntitiesFromOrphanedProviders } from '../processing/evictEntitiesFromOrphanedProviders';
import { DefaultCatalogProcessingEngine } from '../processing/DefaultCatalogProcessingEngine';
@@ -93,7 +85,6 @@ import { DefaultCatalogProcessingOrchestrator } from '../processing/DefaultCatal
import {
AnnotateLocationEntityProcessor,
BuiltinKindsEntityProcessor,
- CodeOwnersProcessor,
FileReaderProcessor,
PlaceholderProcessor,
UrlReaderProcessor,
@@ -116,21 +107,11 @@ import { DefaultEntitiesCatalog } from './DefaultEntitiesCatalog';
import { DefaultLocationService } from './DefaultLocationService';
import { DefaultRefreshService } from './DefaultRefreshService';
import { entitiesResponseToObjects } from './response';
-import { catalogEntityPermissionResourceRef } from '@backstage/plugin-catalog-node/alpha';
+import {
+ catalogEntityPermissionResourceRef,
+ CatalogPermissionRuleInput,
+} from '@backstage/plugin-catalog-node/alpha';
-/**
- * This is a duplicate of the alpha `CatalogPermissionRule` type, for use in the stable API.
- *
- * @public
- */
-export type CatalogPermissionRuleInput<
- TParams extends PermissionRuleParams = PermissionRuleParams,
-> = PermissionRule;
-
-/**
- * @deprecated Please migrate to the new backend system as this will be removed in the future.
- * @public
- */
export type CatalogEnvironment = {
logger: LoggerService;
database: DatabaseService;
@@ -139,9 +120,8 @@ export type CatalogEnvironment = {
permissions: PermissionsService | PermissionAuthorizer;
permissionsRegistry?: PermissionsRegistryService;
scheduler?: SchedulerService;
- discovery?: DiscoveryService;
- auth?: AuthService;
- httpAuth?: HttpAuthService;
+ auth: AuthService;
+ httpAuth: HttpAuthService;
auditor?: AuditorService;
};
@@ -167,9 +147,6 @@ export type CatalogEnvironment = {
* - Processors can be added or replaced. These implement the functionality of
* reading, parsing, validating, and processing the entity data before it is
* persisted in the catalog.
- *
- * @public
- * @deprecated Please migrate to the new backend system as this will be removed in the future.
*/
export class CatalogBuilder {
private readonly env: CatalogEnvironment;
@@ -380,7 +357,6 @@ export class CatalogBuilder {
return [
new FileReaderProcessor(),
new UrlReaderProcessor({ reader, logger, config }),
- CodeOwnersProcessor.fromConfig(config, { logger, reader }),
new AnnotateLocationEntityProcessor({ integrations }),
];
}
@@ -482,15 +458,11 @@ export class CatalogBuilder {
permissions,
scheduler,
permissionsRegistry,
- discovery = HostDiscovery.fromConfig(config),
auditor,
+ auth,
+ httpAuth,
} = this.env;
- const { auth, httpAuth } = createLegacyAuthAdapters({
- ...this.env,
- discovery,
- });
-
const disableRelationsCompatibility = config.getOptionalBoolean(
'catalog.disableRelationsCompatibility',
);
@@ -736,8 +708,14 @@ export class CatalogBuilder {
processors.push(builtinKindsEntityProcessor);
}
- // These are only added unless the user replaced them all
- if (!this.processorsReplace) {
+ const disableDefaultProcessors = config.getOptionalBoolean(
+ 'catalog.disableDefaultProcessors',
+ );
+
+ // Add default processors if:
+ // - processors have NOT been explicitly replaced
+ // - and default processors are NOT disabled via config
+ if (!this.processorsReplace && !disableDefaultProcessors) {
processors.push(...this.getDefaultProcessors());
}
diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts
index 13be6eea31..9925f11c7b 100644
--- a/plugins/catalog-backend/src/service/CatalogPlugin.ts
+++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts
@@ -35,13 +35,14 @@ import {
catalogModelExtensionPoint,
CatalogPermissionExtensionPoint,
catalogPermissionExtensionPoint,
+ CatalogPermissionRuleInput,
CatalogProcessingExtensionPoint,
catalogProcessingExtensionPoint,
} from '@backstage/plugin-catalog-node/alpha';
import { eventsServiceRef } from '@backstage/plugin-events-node';
import { Permission } from '@backstage/plugin-permission-common';
import { merge } from 'lodash';
-import { CatalogBuilder, CatalogPermissionRuleInput } from './CatalogBuilder';
+import { CatalogBuilder } from './CatalogBuilder';
class CatalogLocationsExtensionPointImpl
implements CatalogLocationsExtensionPoint
@@ -232,7 +233,6 @@ export const catalogPlugin = createBackendPlugin({
httpRouter: coreServices.httpRouter,
lifecycle: coreServices.rootLifecycle,
scheduler: coreServices.scheduler,
- discovery: coreServices.discovery,
auth: coreServices.auth,
httpAuth: coreServices.httpAuth,
auditor: coreServices.auditor,
@@ -248,7 +248,6 @@ export const catalogPlugin = createBackendPlugin({
httpRouter,
lifecycle,
scheduler,
- discovery,
auth,
httpAuth,
auditor,
@@ -262,7 +261,6 @@ export const catalogPlugin = createBackendPlugin({
database,
scheduler,
logger,
- discovery,
auth,
httpAuth,
auditor,
diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts
index 08e98d0548..f4ce26b66b 100644
--- a/plugins/catalog-backend/src/service/createRouter.ts
+++ b/plugins/catalog-backend/src/service/createRouter.ts
@@ -65,9 +65,6 @@ import {
/**
* Options used by {@link createRouter}.
- *
- * @public
- * @deprecated Please migrate to the new backend system as this will be removed in the future.
*/
export interface RouterOptions {
entitiesCatalog?: EntitiesCatalog;
diff --git a/plugins/catalog-backend/src/service/index.ts b/plugins/catalog-backend/src/service/index.ts
deleted file mode 100644
index 44645d39fe..0000000000
--- a/plugins/catalog-backend/src/service/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * Copyright 2021 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-export type {
- CatalogEnvironment,
- CatalogPermissionRuleInput,
-} from './CatalogBuilder';
-export { CatalogBuilder } from './CatalogBuilder';
diff --git a/plugins/catalog-backend/src/service/types.ts b/plugins/catalog-backend/src/service/types.ts
index 30470e5af0..f9ec39c7d3 100644
--- a/plugins/catalog-backend/src/service/types.ts
+++ b/plugins/catalog-backend/src/service/types.ts
@@ -20,8 +20,6 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api';
/**
* Holds the information required to create a new location in the catalog location store.
- *
- * @public
*/
export interface LocationInput {
type: string;
@@ -30,7 +28,6 @@ export interface LocationInput {
/**
* The location service manages entity locations.
- * @public
*/
export interface LocationService {
createLocation(
@@ -59,8 +56,6 @@ export interface LocationService {
/**
* Options for requesting a refresh of entities in the catalog.
- *
- * @public
*/
export type RefreshOptions = {
/** The reference to a single entity that should be refreshed */
@@ -70,8 +65,6 @@ export type RefreshOptions = {
/**
* A service that manages refreshes of entities in the catalog.
- *
- * @public
*/
export interface RefreshService {
/**
@@ -82,7 +75,6 @@ export interface RefreshService {
/**
* Interacts with the database to manage locations.
- * @public
*/
export interface LocationStore {
createLocation(location: LocationInput): Promise;
diff --git a/plugins/catalog-backend/src/tests/integration.test.ts b/plugins/catalog-backend/src/tests/integration.test.ts
index aa6d241d23..400bbc7538 100644
--- a/plugins/catalog-backend/src/tests/integration.test.ts
+++ b/plugins/catalog-backend/src/tests/integration.test.ts
@@ -47,14 +47,13 @@ import {
} from '../processing/DefaultCatalogProcessingEngine';
import { DefaultCatalogProcessingOrchestrator } from '../processing/DefaultCatalogProcessingOrchestrator';
import { connectEntityProviders } from '../processing/connectEntityProviders';
-import { CatalogProcessingEngine } from '../processing';
+import { CatalogProcessingEngine } from '../processing/types';
import { DefaultEntitiesCatalog } from '../service/DefaultEntitiesCatalog';
import { DefaultRefreshService } from '../service/DefaultRefreshService';
import { RefreshOptions, RefreshService } from '../service/types';
import { DefaultStitcher } from '../stitching/DefaultStitcher';
-import { mockServices } from '@backstage/backend-test-utils';
+import { mockServices, TestDatabases } from '@backstage/backend-test-utils';
import { LoggerService } from '@backstage/backend-plugin-api';
-import { DatabaseManager } from '@backstage/backend-common';
import { entitiesResponseToObjects } from '../service/response';
import { deleteOrphanedEntities } from '../database/operations/util/deleteOrphanedEntities';
@@ -205,10 +204,10 @@ class TestHarness {
readonly #proxyProgressTracker: ProxyProgressTracker;
readonly #db: Knex;
- static async create(options?: {
+ static async create(options: {
disableRelationsCompatibility?: boolean;
logger?: LoggerService;
- db?: Knex;
+ db: Knex;
permissions?: PermissionEvaluator;
additionalProviders?: EntityProvider[];
processEntity?(
@@ -231,24 +230,19 @@ class TestHarness {
},
});
const logger = options?.logger ?? mockServices.logger.mock();
- const db =
- options?.db ??
- (await DatabaseManager.fromConfig(config, { logger })
- .forPlugin('catalog')
- .getClient());
- await applyDatabaseMigrations(db);
+ await applyDatabaseMigrations(options.db);
const catalogDatabase = new DefaultCatalogDatabase({
- database: db,
+ database: options.db,
logger,
});
const providerDatabase = new DefaultProviderDatabase({
- database: db,
+ database: options.db,
logger,
});
const processingDatabase = new DefaultProcessingDatabase({
- database: db,
+ database: options.db,
logger,
refreshInterval: () => 0.05,
});
@@ -281,9 +275,12 @@ class TestHarness {
policy: EntityPolicies.allOf([]),
legacySingleProcessorValidation: false,
});
- const stitcher = DefaultStitcher.fromConfig(config, { knex: db, logger });
+ const stitcher = DefaultStitcher.fromConfig(config, {
+ knex: options.db,
+ logger,
+ });
const catalog = new DefaultEntitiesCatalog({
- database: db,
+ database: options.db,
logger,
stitcher,
disableRelationsCompatibility: options?.disableRelationsCompatibility,
@@ -296,7 +293,7 @@ class TestHarness {
config: new ConfigReader({}),
logger,
processingDatabase,
- knex: db,
+ knex: options.db,
orchestrator,
stitcher,
createHash: () => createHash('sha1'),
@@ -333,7 +330,7 @@ class TestHarness {
refresh,
provider,
proxyProgressTracker,
- db,
+ options.db,
);
}
@@ -444,10 +441,13 @@ class TestHarness {
}
describe('Catalog Backend Integration', () => {
+ const databases = TestDatabases.create({ ids: ['SQLITE_3'] });
+
it('should add entities and update errors', async () => {
let triggerError = false;
const harness = await TestHarness.create({
+ db: await databases.init('SQLITE_3'),
async processEntity(entity: Entity) {
if (triggerError) {
throw new Error('NOPE');
@@ -525,6 +525,7 @@ describe('Catalog Backend Integration', () => {
const generatedApis = ['api-1', 'api-2'];
const harness = await TestHarness.create({
+ db: await databases.init('SQLITE_3'),
async processEntity(
entity: Entity,
location: LocationSpec,
@@ -609,7 +610,9 @@ describe('Catalog Backend Integration', () => {
});
it('should not replace matching provided entities', async () => {
- const harness = await TestHarness.create();
+ const harness = await TestHarness.create({
+ db: await databases.init('SQLITE_3'),
+ });
const entityA = {
apiVersion: 'backstage.io/v1alpha1',
@@ -669,6 +672,7 @@ describe('Catalog Backend Integration', () => {
}
const harness = await TestHarness.create({
+ db: await databases.init('SQLITE_3'),
async processEntity(
entity: Entity,
location: LocationSpec,
@@ -788,7 +792,9 @@ describe('Catalog Backend Integration', () => {
});
it('should reject insecure URLs', async () => {
- const harness = await TestHarness.create();
+ const harness = await TestHarness.create({
+ db: await databases.init('SQLITE_3'),
+ });
await harness.setInputEntities([
{
@@ -827,7 +833,9 @@ describe('Catalog Backend Integration', () => {
});
it('should reject insecure location URLs', async () => {
- const harness = await TestHarness.create();
+ const harness = await TestHarness.create({
+ db: await databases.init('SQLITE_3'),
+ });
await harness.setInputEntities([
{
@@ -854,6 +862,7 @@ describe('Catalog Backend Integration', () => {
it('should return valid responses in raw JSON mode', async () => {
const harness = await TestHarness.create({
+ db: await databases.init('SQLITE_3'),
disableRelationsCompatibility: true,
});
@@ -910,6 +919,7 @@ describe('Catalog Backend Integration', () => {
const secondProvider = new TestProvider('second');
const harness = await TestHarness.create({
+ db: await databases.init('SQLITE_3'),
additionalProviders: [firstProvider, secondProvider],
});
@@ -1066,7 +1076,10 @@ describe('Catalog Backend Integration', () => {
_emit: CatalogProcessorEmit,
) => entity,
);
- const harness = await TestHarness.create({ processEntity });
+ const harness = await TestHarness.create({
+ db: await databases.init('SQLITE_3'),
+ processEntity,
+ });
processEntity.mockImplementation(async (entity, location, emit) => {
if (entity.metadata.name === entityA.metadata.name) {
@@ -1189,7 +1202,10 @@ describe('Catalog Backend Integration', () => {
_emit: CatalogProcessorEmit,
) => entity,
);
- const harness = await TestHarness.create({ processEntity });
+ const harness = await TestHarness.create({
+ db: await databases.init('SQLITE_3'),
+ processEntity,
+ });
processEntity.mockImplementation(async (entity, location, emit) => {
if (entity.metadata.name === entityA.metadata.name) {
@@ -1246,7 +1262,10 @@ describe('Catalog Backend Integration', () => {
_emit: CatalogProcessorEmit,
) => entity,
);
- const harness = await TestHarness.create({ processEntity });
+ const harness = await TestHarness.create({
+ db: await databases.init('SQLITE_3'),
+ processEntity,
+ });
processEntity.mockImplementation(async (entity, location, emit) => {
if (entity.metadata.name === entityA.metadata.name) {
diff --git a/plugins/catalog-backend/src/tests/migrations.test.ts b/plugins/catalog-backend/src/tests/migrations.test.ts
index bb840f7a21..f58b174f93 100644
--- a/plugins/catalog-backend/src/tests/migrations.test.ts
+++ b/plugins/catalog-backend/src/tests/migrations.test.ts
@@ -521,4 +521,87 @@ describe('migrations', () => {
await knex.destroy();
},
);
+ it.each(databases.eachSupportedId())(
+ '20250401200503_update_refresh_state_columns.js, %p',
+ async databaseId => {
+ const knex = await databases.init(databaseId);
+
+ // Run migrations up to just before the target migration
+ await migrateUntilBefore(
+ knex,
+ '20250401200503_update_refresh_state_columns.js',
+ );
+
+ // Insert a row with data into the refresh_state table
+ await knex('refresh_state').insert({
+ entity_id: 'test-id',
+ entity_ref: 'k:ns/test',
+ unprocessed_entity: JSON.stringify({ key: 'value' }),
+ cache: JSON.stringify({ cacheKey: 'cacheValue' }),
+ errors: '[]',
+ next_update_at: knex.fn.now(),
+ last_discovery_at: knex.fn.now(),
+ });
+
+ // Verify the data before the migration
+ const preMigrationData = await knex('refresh_state')
+ .where({ entity_id: 'test-id' })
+ .first();
+ expect(preMigrationData).toEqual(
+ expect.objectContaining({
+ entity_id: 'test-id',
+ entity_ref: 'k:ns/test',
+ unprocessed_entity: JSON.stringify({ key: 'value' }),
+ cache: JSON.stringify({ cacheKey: 'cacheValue' }),
+ }),
+ );
+
+ // Run the migration
+ await migrateUpOnce(knex);
+
+ // Verify the schema after the migration
+ const columnInfo = await knex('refresh_state').columnInfo();
+ const expectedType = knex.client.config.client.includes('mysql')
+ ? 'longtext'
+ : 'text';
+ expect(columnInfo.unprocessed_entity.type).toBe(expectedType);
+ expect(columnInfo.cache.type).toBe(expectedType);
+
+ // Verify the data after the migration
+ const postMigrationData = await knex('refresh_state')
+ .where({ entity_id: 'test-id' })
+ .first();
+ expect(postMigrationData).toEqual(
+ expect.objectContaining({
+ entity_id: 'test-id',
+ entity_ref: 'k:ns/test',
+ unprocessed_entity: JSON.stringify({ key: 'value' }),
+ cache: JSON.stringify({ cacheKey: 'cacheValue' }),
+ }),
+ );
+
+ // Roll back the migration
+ await migrateDownOnce(knex);
+
+ // Verify the schema after rolling back
+ const revertedColumnInfo = await knex('refresh_state').columnInfo();
+ expect(revertedColumnInfo.unprocessed_entity.type).toBe('text');
+ expect(revertedColumnInfo.cache.type).toBe('text');
+
+ // Verify the data after rolling back
+ const postRollbackData = await knex('refresh_state')
+ .where({ entity_id: 'test-id' })
+ .first();
+ expect(postRollbackData).toEqual(
+ expect.objectContaining({
+ entity_id: 'test-id',
+ entity_ref: 'k:ns/test',
+ unprocessed_entity: JSON.stringify({ key: 'value' }),
+ cache: JSON.stringify({ cacheKey: 'cacheValue' }),
+ }),
+ );
+
+ await knex.destroy();
+ },
+ );
});
diff --git a/plugins/catalog-backend/src/util/conversion.ts b/plugins/catalog-backend/src/util/conversion.ts
index 0b92b88d90..7d542e0ce1 100644
--- a/plugins/catalog-backend/src/util/conversion.ts
+++ b/plugins/catalog-backend/src/util/conversion.ts
@@ -32,7 +32,6 @@ export function locationSpecToMetadataName(location: LocationSpec) {
return `generated-${hash}`;
}
-/** @public */
export function locationSpecToLocationEntity(opts: {
location: LocationSpec;
parentEntity?: Entity;
diff --git a/plugins/catalog-backend/src/util/parse.ts b/plugins/catalog-backend/src/util/parse.ts
index 778c176323..d87a98ef75 100644
--- a/plugins/catalog-backend/src/util/parse.ts
+++ b/plugins/catalog-backend/src/util/parse.ts
@@ -14,51 +14,11 @@
* limitations under the License.
*/
-import { Entity, stringifyLocationRef } from '@backstage/catalog-model';
-import lodash from 'lodash';
-import yaml from 'yaml';
-import { LocationSpec } from '@backstage/plugin-catalog-common';
import {
CatalogProcessorParser,
- CatalogProcessorResult,
- processingResult,
+ parseEntityYaml,
} from '@backstage/plugin-catalog-node';
-/** @public */
-export function* parseEntityYaml(
- data: Buffer,
- location: LocationSpec,
-): Iterable {
- let documents: yaml.Document.Parsed[];
- try {
- documents = yaml.parseAllDocuments(data.toString('utf8')).filter(d => d);
- } catch (e) {
- const loc = stringifyLocationRef(location);
- const message = `Failed to parse YAML at ${loc}, ${e}`;
- yield processingResult.generalError(location, message);
- return;
- }
-
- for (const document of documents) {
- if (document.errors?.length) {
- const loc = stringifyLocationRef(location);
- const message = `YAML error at ${loc}, ${document.errors[0]}`;
- yield processingResult.generalError(location, message);
- } else {
- const json = document.toJSON();
- if (lodash.isPlainObject(json)) {
- yield processingResult.entity(location, json as Entity);
- } else if (json === null) {
- // Ignore null values, these happen if there is an empty document in the
- // YAML file, for example if --- is added to the end of the file.
- } else {
- const message = `Expected object at root, got ${typeof json}`;
- yield processingResult.generalError(location, message);
- }
- }
- }
-}
-
export const defaultEntityDataParser: CatalogProcessorParser =
async function* defaultEntityDataParser({ data, location }) {
for (const e of parseEntityYaml(data, location)) {
diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md
index 746aba7037..f859e1d0f9 100644
--- a/plugins/catalog-common/CHANGELOG.md
+++ b/plugins/catalog-common/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-catalog-common
+## 1.1.4-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-search-common@1.2.18-next.0
+
## 1.1.3
### Patch Changes
diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json
index 87587cc262..3cf4c885a9 100644
--- a/plugins/catalog-common/package.json
+++ b/plugins/catalog-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-common",
- "version": "1.1.3",
+ "version": "1.1.4-next.0",
"description": "Common functionalities for the catalog plugin",
"backstage": {
"role": "common-library",
diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md
index 72eba5bfe4..269dcf2a4f 100644
--- a/plugins/catalog-graph/CHANGELOG.md
+++ b/plugins/catalog-graph/CHANGELOG.md
@@ -1,5 +1,35 @@
# @backstage/plugin-catalog-graph
+## 0.4.19-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- 72d019d: Removed various typos
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/types@1.2.1
+
+## 0.4.19-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/types@1.2.1
+
## 0.4.18
### Patch Changes
diff --git a/plugins/catalog-graph/README-alpha.md b/plugins/catalog-graph/README-alpha.md
index 06de696dcc..bab9a26fd1 100644
--- a/plugins/catalog-graph/README-alpha.md
+++ b/plugins/catalog-graph/README-alpha.md
@@ -184,7 +184,7 @@ app:
extensions:
# this is the extension id and it follows the naming pattern bellow:
# /:
- # example disbaling the graph card extension
+ # example disabling the graph card extension
- entity-card:catalog-graph/relations: false
```
@@ -212,7 +212,7 @@ export default createFrontendModule({
configSchema: createSchemaFromZod(z =>
z.object({
filter: z.string().optional(),
- // Ommitting the rest of default configs for simplicity in this example
+ // Omitting the rest of default configs for simplicity in this example
}),
),
loader: () =>
@@ -283,7 +283,7 @@ app:
extensions:
# this is the extension id and it follows the naming pattern bellow:
# /:
- # example disbaling the graph page extension
+ # example disabling the graph page extension
- page:catalog-graph: false
```
@@ -305,12 +305,12 @@ export default createFrontendModule({
pluginId: 'catalog-graph',
extensions: [
createPageExtension({
- // Ommiting name since it is an index page
+ // Omitting name since it is an index page
defaultPath: '/catalog-graph',
routeRef: convertLegacyRouteRef(catalogGraphRouteRef),
createSchemaFromZod(z => z.object({
path: z.string().default('/catalog-graph')
- // Ommitting the rest of default configs for simplicity in this example
+ // Omitting the rest of default configs for simplicity in this example
})),
loader: () => import('./components').then(m => )
})
diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json
index e58cde8967..08d88ceca7 100644
--- a/plugins/catalog-graph/package.json
+++ b/plugins/catalog-graph/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-graph",
- "version": "0.4.18",
+ "version": "0.4.19-next.1",
"backstage": {
"role": "frontend-plugin",
"pluginId": "catalog-graph",
diff --git a/plugins/catalog-graph/src/alpha.tsx b/plugins/catalog-graph/src/alpha.tsx
index 096dcca2b8..d70232e92a 100644
--- a/plugins/catalog-graph/src/alpha.tsx
+++ b/plugins/catalog-graph/src/alpha.tsx
@@ -86,7 +86,7 @@ const CatalogGraphPage = PageBlueprint.makeWithOverrides({
});
export default createFrontendPlugin({
- id: 'catalog-graph',
+ pluginId: 'catalog-graph',
routes: {
catalogGraph: convertLegacyRouteRef(catalogGraphRouteRef),
},
diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md
index f1f0bc3fa2..8ede217379 100644
--- a/plugins/catalog-import/CHANGELOG.md
+++ b/plugins/catalog-import/CHANGELOG.md
@@ -1,5 +1,42 @@
# @backstage/plugin-catalog-import
+## 0.12.14-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## 0.12.14-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.4-next.0
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.12.13
### Patch Changes
diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json
index 29f28c2fef..b6730e7fd6 100644
--- a/plugins/catalog-import/package.json
+++ b/plugins/catalog-import/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-import",
- "version": "0.12.13",
+ "version": "0.12.14-next.1",
"description": "A Backstage plugin the helps you import entities into your catalog",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx
index e70d964ce2..0d1599ee5c 100644
--- a/plugins/catalog-import/src/alpha.tsx
+++ b/plugins/catalog-import/src/alpha.tsx
@@ -86,7 +86,7 @@ const catalogImportApi = ApiBlueprint.make({
/** @alpha */
export default createFrontendPlugin({
- id: 'catalog-import',
+ pluginId: 'catalog-import',
extensions: [catalogImportApi, catalogImportPage],
routes: {
importPage: convertLegacyRouteRef(rootRouteRef),
diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md
index ea71b6d310..ebffdcd296 100644
--- a/plugins/catalog-node/CHANGELOG.md
+++ b/plugins/catalog-node/CHANGELOG.md
@@ -1,5 +1,41 @@
# @backstage/plugin-catalog-node
+## 1.17.0-next.1
+
+### Minor Changes
+
+- a459f17: Added `parseEntityYaml` from `@backstage/plugin-catalog-backend`, to make it more easily usable by custom plugins and modules
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+
+## 1.17.0-next.0
+
+### Minor Changes
+
+- 1a003ff: Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-common@0.8.4
+
## 1.16.3
### Patch Changes
diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json
index 2982ff7d42..a222e0093d 100644
--- a/plugins/catalog-node/package.json
+++ b/plugins/catalog-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-node",
- "version": "1.16.3",
+ "version": "1.17.0-next.1",
"description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend",
"backstage": {
"role": "node-library",
@@ -67,7 +67,9 @@
"@backstage/plugin-catalog-common": "workspace:^",
"@backstage/plugin-permission-common": "workspace:^",
"@backstage/plugin-permission-node": "workspace:^",
- "@backstage/types": "workspace:^"
+ "@backstage/types": "workspace:^",
+ "lodash": "^4.17.21",
+ "yaml": "^2.0.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
diff --git a/plugins/catalog-node/report.api.md b/plugins/catalog-node/report.api.md
index f2fe86a38c..807bcaa8d5 100644
--- a/plugins/catalog-node/report.api.md
+++ b/plugins/catalog-node/report.api.md
@@ -297,6 +297,12 @@ export function locationSpecToLocationEntity(opts: {
// @public
export function locationSpecToMetadataName(location: LocationSpec_2): string;
+// @public
+export function parseEntityYaml(
+ data: string | Buffer,
+ location: LocationSpec_2,
+): Iterable;
+
// @public (undocumented)
export type PlaceholderResolver = (
params: PlaceholderResolverParams,
diff --git a/plugins/catalog-node/src/processing/index.ts b/plugins/catalog-node/src/processing/index.ts
index 4e83e6077d..1610e0cb89 100644
--- a/plugins/catalog-node/src/processing/index.ts
+++ b/plugins/catalog-node/src/processing/index.ts
@@ -24,3 +24,4 @@ export type {
LocationAnalyzer,
ScmLocationAnalyzer,
} from './types';
+export { parseEntityYaml } from './parse';
diff --git a/plugins/catalog-backend/src/util/parse.test.ts b/plugins/catalog-node/src/processing/parse.test.ts
similarity index 100%
rename from plugins/catalog-backend/src/util/parse.test.ts
rename to plugins/catalog-node/src/processing/parse.test.ts
diff --git a/plugins/catalog-node/src/processing/parse.ts b/plugins/catalog-node/src/processing/parse.ts
new file mode 100644
index 0000000000..2ef276d06d
--- /dev/null
+++ b/plugins/catalog-node/src/processing/parse.ts
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2020 The 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, stringifyLocationRef } from '@backstage/catalog-model';
+import lodash from 'lodash';
+import yaml from 'yaml';
+import { LocationSpec } from '@backstage/plugin-catalog-common';
+import { CatalogProcessorResult } from '../api/processor';
+import { processingResult } from '../api/processingResult';
+
+/**
+ * A helper function that parses a YAML file, properly handling multiple
+ * documents in a single file.
+ *
+ * @public
+ * @remarks
+ *
+ * Each document is expected to be a valid Backstage entity. Each item in the
+ * iterable is in the form of an entity result if the document seemed like a
+ * valid object, or in the form of an error result if it seemed incorrect. This
+ * way, you can choose to retain a partial result if you want.
+ *
+ * Note that this function does NOT perform any validation of the entities. No
+ * assumptions whatsoever can be made on the emnitted entities except that they
+ * are valid JSON objects.
+ */
+export function* parseEntityYaml(
+ data: string | Buffer,
+ location: LocationSpec,
+): Iterable {
+ let documents: yaml.Document.Parsed[];
+ try {
+ documents = yaml
+ .parseAllDocuments(
+ typeof data === 'string' ? data : data.toString('utf8'),
+ )
+ .filter(d => d);
+ } catch (e) {
+ const loc = stringifyLocationRef(location);
+ const message = `Failed to parse YAML at ${loc}, ${e}`;
+ yield processingResult.generalError(location, message);
+ return;
+ }
+
+ for (const document of documents) {
+ if (document.errors?.length) {
+ const loc = stringifyLocationRef(location);
+ const message = `YAML error at ${loc}, ${document.errors[0]}`;
+ yield processingResult.generalError(location, message);
+ } else {
+ const json = document.toJSON();
+ if (lodash.isPlainObject(json)) {
+ yield processingResult.entity(location, json as Entity);
+ } else if (json === null) {
+ // Ignore null values, these happen if there is an empty document in the
+ // YAML file, for example if --- is added to the end of the file.
+ } else {
+ const message = `Expected object at root, got ${typeof json}`;
+ yield processingResult.generalError(location, message);
+ }
+ }
+ }
+}
diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md
index 3fafae3a8b..4a261ad2aa 100644
--- a/plugins/catalog-react/CHANGELOG.md
+++ b/plugins/catalog-react/CHANGELOG.md
@@ -1,5 +1,73 @@
# @backstage/plugin-catalog-react
+## 1.18.0-next.1
+
+### Patch Changes
+
+- 6d7f0d5: Fixed an issue causing entities of kind user and group to be empty when an owner was selected
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/frontend-test-utils@0.3.2-next.1
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## 1.18.0-next.0
+
+### Minor Changes
+
+- d47aaa3: Added EntityOrderFilter to sort entities by different fields/columns. This new filter allows users to specify the order in which entities are displayed in the catalog.
+
+ Example usage:
+
+ ```ts
+ import {
+ EntityOrderFilter,
+ useEntityList,
+ } from '@backstage/plugin-catalog-react';
+ // ...
+ const { updateFilters } = useEntityList();
+
+ // ...
+ updateFilters({
+ order: new EntityOrderFilter([
+ {
+ field: 'metadata.name',
+ order: 'desc',
+ },
+ ]),
+ });
+ ```
+
+- 1a003ff: Add `getLocations` method to `CatalogApi` and `CatalogClient`. This method calls the [`GET /locations`](https://backstage.io/docs/features/software-catalog/software-catalog-api/#get-locations) endpoint from the catalog backend.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/frontend-test-utils@0.3.2-next.0
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-permission-react@0.4.33
+
## 1.17.0
### Minor Changes
diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json
index 48159e55ef..aeeb398cad 100644
--- a/plugins/catalog-react/package.json
+++ b/plugins/catalog-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-react",
- "version": "1.17.0",
+ "version": "1.18.0-next.1",
"description": "A frontend library that helps other Backstage plugins interact with the catalog",
"backstage": {
"role": "web-library",
diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
index 45ed6079cc..de85fa1042 100644
--- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
+++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
@@ -32,6 +32,7 @@ import { catalogApiRef } from '../api';
import { MockStarredEntitiesApi, starredEntitiesApiRef } from '../apis';
import {
EntityKindFilter,
+ EntityOwnerFilter,
EntityTextFilter,
EntityTypeFilter,
EntityUserFilter,
@@ -298,6 +299,48 @@ describe(' ', () => {
expect(result.current.pageInfo).toBeUndefined();
});
+
+ it('should omit owners filter when kind is "user"', async () => {
+ const { result } = renderHook(() => useEntityList(), {
+ wrapper: createWrapper({ pagination }),
+ });
+
+ act(() => {
+ result.current.updateFilters({
+ kind: new EntityKindFilter('user', 'User'),
+ owners: new EntityOwnerFilter(['user:default/guest']),
+ });
+ });
+
+ await waitFor(() => {
+ expect(mockCatalogApi.getEntities).toHaveBeenCalled();
+ });
+
+ expect(mockCatalogApi.getEntities).toHaveBeenCalledWith({
+ filter: { kind: 'user' },
+ });
+ });
+
+ it('should omit owners filter when kind is "group"', async () => {
+ const { result } = renderHook(() => useEntityList(), {
+ wrapper: createWrapper({ pagination }),
+ });
+
+ act(() => {
+ result.current.updateFilters({
+ kind: new EntityKindFilter('group', 'Group'),
+ owners: new EntityOwnerFilter(['group:default/team-a']),
+ });
+ });
+
+ await waitFor(() => {
+ expect(mockCatalogApi.getEntities).toHaveBeenCalled();
+ });
+
+ expect(mockCatalogApi.getEntities).toHaveBeenCalledWith({
+ filter: { kind: 'group' },
+ });
+ });
});
describe(' ', () => {
@@ -552,6 +595,52 @@ describe(' ', () => {
});
});
});
+
+ it('should omit owners filter when kind is "user"', async () => {
+ const { result } = renderHook(() => useEntityList(), {
+ wrapper: createWrapper({ pagination }),
+ });
+
+ act(() => {
+ result.current.updateFilters({
+ kind: new EntityKindFilter('user', 'User'),
+ owners: new EntityOwnerFilter(['user:default/guest']),
+ });
+ });
+
+ await waitFor(() => {
+ expect(mockCatalogApi.queryEntities).toHaveBeenCalled();
+ });
+
+ expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith(
+ expect.objectContaining({
+ filter: { kind: 'user' },
+ }),
+ );
+ });
+
+ it('should omit owners filter when kind is "group"', async () => {
+ const { result } = renderHook(() => useEntityList(), {
+ wrapper: createWrapper({ pagination }),
+ });
+
+ act(() => {
+ result.current.updateFilters({
+ kind: new EntityKindFilter('group', 'Group'),
+ owners: new EntityOwnerFilter(['group:default/team-a']),
+ });
+ });
+
+ await waitFor(() => {
+ expect(mockCatalogApi.queryEntities).toHaveBeenCalled();
+ });
+
+ expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith(
+ expect.objectContaining({
+ filter: { kind: 'group' },
+ }),
+ );
+ });
});
});
@@ -801,4 +890,50 @@ describe(` `, () => {
expect(result.current.error).toBeDefined();
});
});
+
+ it('should omit owners filter when kind is "user"', async () => {
+ const { result } = renderHook(() => useEntityList(), {
+ wrapper: createWrapper({ pagination }),
+ });
+
+ act(() => {
+ result.current.updateFilters({
+ kind: new EntityKindFilter('user', 'User'),
+ owners: new EntityOwnerFilter(['user:default/guest']),
+ });
+ });
+
+ await waitFor(() => {
+ expect(mockCatalogApi.queryEntities).toHaveBeenCalled();
+ });
+
+ expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith(
+ expect.objectContaining({
+ filter: { kind: 'user' },
+ }),
+ );
+ });
+
+ it('should omit owners filter when kind is "group"', async () => {
+ const { result } = renderHook(() => useEntityList(), {
+ wrapper: createWrapper({ pagination }),
+ });
+
+ act(() => {
+ result.current.updateFilters({
+ kind: new EntityKindFilter('group', 'Group'),
+ owners: new EntityOwnerFilter(['group:default/team-a']),
+ });
+ });
+
+ await waitFor(() => {
+ expect(mockCatalogApi.queryEntities).toHaveBeenCalled();
+ });
+
+ expect(mockCatalogApi.queryEntities).toHaveBeenCalledWith(
+ expect.objectContaining({
+ filter: { kind: 'group' },
+ }),
+ );
+ });
});
diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx
index 1d3afd8d9b..560e2ba987 100644
--- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx
+++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx
@@ -241,7 +241,13 @@ export const EntityListProvider = (
// based on the requested filters changing.
const [{ loading, error }, refresh] = useAsyncFn(
async () => {
- const compacted = compact(Object.values(requestedFilters));
+ const kindValue =
+ requestedFilters.kind?.value?.toLocaleLowerCase('en-US');
+ const adjustedFilters =
+ kindValue === 'user' || kindValue === 'group'
+ ? { ...requestedFilters, owners: undefined }
+ : requestedFilters;
+ const compacted = compact(Object.values(adjustedFilters));
const queryParams = Object.keys(requestedFilters).reduce(
(params, key) => {
diff --git a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md
index 0313241aca..63bf77a4af 100644
--- a/plugins/catalog-unprocessed-entities-common/CHANGELOG.md
+++ b/plugins/catalog-unprocessed-entities-common/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/plugin-catalog-unprocessed-entities-common
+## 0.0.8-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.0-next.0
+
## 0.0.7
### Patch Changes
diff --git a/plugins/catalog-unprocessed-entities-common/package.json b/plugins/catalog-unprocessed-entities-common/package.json
index f1c7d38cc1..d96e349347 100644
--- a/plugins/catalog-unprocessed-entities-common/package.json
+++ b/plugins/catalog-unprocessed-entities-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-unprocessed-entities-common",
- "version": "0.0.7",
+ "version": "0.0.8-next.0",
"description": "Common functionalities for the catalog-unprocessed-entities plugin",
"backstage": {
"role": "common-library",
diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md
index e5f7e33096..d85e0919cd 100644
--- a/plugins/catalog-unprocessed-entities/CHANGELOG.md
+++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md
@@ -1,5 +1,30 @@
# @backstage/plugin-catalog-unprocessed-entities
+## 0.2.17-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+
+## 0.2.17-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+
## 0.2.16
### Patch Changes
diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json
index c8bfbaf73b..e985ba2d9c 100644
--- a/plugins/catalog-unprocessed-entities/package.json
+++ b/plugins/catalog-unprocessed-entities/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-unprocessed-entities",
- "version": "0.2.16",
+ "version": "0.2.17-next.1",
"backstage": {
"role": "frontend-plugin",
"pluginId": "catalog-unprocessed-entities",
diff --git a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx
index df1935f89a..8ff83605c7 100644
--- a/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx
+++ b/plugins/catalog-unprocessed-entities/src/alpha/plugin.tsx
@@ -73,7 +73,7 @@ export const catalogUnprocessedEntitiesNavItem = NavItemBlueprint.make({
/** @alpha */
export default createFrontendPlugin({
- id: 'catalog-unprocessed-entities',
+ pluginId: 'catalog-unprocessed-entities',
routes: {
root: convertLegacyRouteRef(rootRouteRef),
},
diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md
index 951a73edf6..9cfa2876ba 100644
--- a/plugins/catalog/CHANGELOG.md
+++ b/plugins/catalog/CHANGELOG.md
@@ -1,5 +1,50 @@
# @backstage/plugin-catalog
+## 1.29.1-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/plugin-search-react@1.9.0-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.1
+ - @backstage/integration-react@1.2.7-next.1
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-catalog-common@1.1.4-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+ - @backstage/plugin-scaffolder-common@1.5.11-next.0
+ - @backstage/plugin-search-common@1.2.18-next.0
+
+## 1.29.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/plugin-catalog-react@1.18.0-next.0
+ - @backstage/catalog-client@1.10.0-next.0
+ - @backstage/integration-react@1.2.7-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-react@0.4.33
+ - @backstage/plugin-scaffolder-common@1.5.10
+ - @backstage/plugin-search-common@1.2.17
+ - @backstage/plugin-search-react@1.8.8
+
## 1.29.0
### Minor Changes
diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json
index 3354277536..87f0ce2052 100644
--- a/plugins/catalog/package.json
+++ b/plugins/catalog/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog",
- "version": "1.29.0",
+ "version": "1.29.1-next.1",
"description": "The Backstage plugin for browsing the Backstage catalog",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/catalog/src/alpha/plugin.tsx b/plugins/catalog/src/alpha/plugin.tsx
index 04034e2156..6fd171ed54 100644
--- a/plugins/catalog/src/alpha/plugin.tsx
+++ b/plugins/catalog/src/alpha/plugin.tsx
@@ -38,7 +38,7 @@ import contextMenuItems from './contextMenuItems';
/** @alpha */
export default createFrontendPlugin({
- id: 'catalog',
+ pluginId: 'catalog',
routes: convertLegacyRouteRefs({
catalogIndex: rootRouteRef,
catalogEntity: entityRouteRef,
diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md
index 74a0d75376..34168e5c87 100644
--- a/plugins/config-schema/CHANGELOG.md
+++ b/plugins/config-schema/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-config-schema
+## 0.1.68-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 0.1.67
### Patch Changes
diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json
index 8b26d7b0c1..f2ede3c05e 100644
--- a/plugins/config-schema/package.json
+++ b/plugins/config-schema/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-config-schema",
- "version": "0.1.67",
+ "version": "0.1.68-next.0",
"description": "A Backstage plugin that lets you browse the configuration schema of your app",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md
index fa81ef58dc..b1875099e6 100644
--- a/plugins/devtools-backend/CHANGELOG.md
+++ b/plugins/devtools-backend/CHANGELOG.md
@@ -1,5 +1,37 @@
# @backstage/plugin-devtools-backend
+## 0.5.5-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.10.0-next.1
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/plugin-permission-node@0.10.0-next.1
+ - @backstage/config-loader@1.10.1-next.0
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-devtools-common@0.1.16-next.0
+
+## 0.5.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.9.1-next.0
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/config-loader@1.10.0
+ - @backstage/plugin-permission-node@0.9.2-next.0
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-devtools-common@0.1.15
+ - @backstage/plugin-permission-common@0.8.4
+
## 0.5.4
### Patch Changes
diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json
index c45844548c..abf589f322 100644
--- a/plugins/devtools-backend/package.json
+++ b/plugins/devtools-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-devtools-backend",
- "version": "0.5.4",
+ "version": "0.5.5-next.1",
"backstage": {
"role": "backend-plugin",
"pluginId": "devtools",
diff --git a/plugins/devtools-common/CHANGELOG.md b/plugins/devtools-common/CHANGELOG.md
index a8d1484afc..ef20660a07 100644
--- a/plugins/devtools-common/CHANGELOG.md
+++ b/plugins/devtools-common/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-devtools-common
+## 0.1.16-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-permission-common@0.9.0-next.0
+ - @backstage/types@1.2.1
+
## 0.1.15
### Patch Changes
diff --git a/plugins/devtools-common/package.json b/plugins/devtools-common/package.json
index 5bcc409523..7f5ec4e499 100644
--- a/plugins/devtools-common/package.json
+++ b/plugins/devtools-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-devtools-common",
- "version": "0.1.15",
+ "version": "0.1.16-next.0",
"description": "Common functionalities for the devtools plugin",
"backstage": {
"role": "common-library",
diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md
index 9d66835e94..7d3bfebced 100644
--- a/plugins/devtools/CHANGELOG.md
+++ b/plugins/devtools/CHANGELOG.md
@@ -1,5 +1,32 @@
# @backstage/plugin-devtools
+## 0.1.27-next.1
+
+### Patch Changes
+
+- fb58f20: Internal update to use the new `pluginId` option of `createFrontendPlugin`.
+- Updated dependencies
+ - @backstage/core-components@0.17.2-next.0
+ - @backstage/frontend-plugin-api@0.10.2-next.0
+ - @backstage/core-compat-api@0.4.2-next.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-devtools-common@0.1.16-next.0
+ - @backstage/plugin-permission-react@0.4.34-next.0
+
+## 0.1.27-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-compat-api@0.4.2-next.0
+ - @backstage/core-components@0.17.1
+ - @backstage/core-plugin-api@1.10.6
+ - @backstage/errors@1.2.7
+ - @backstage/frontend-plugin-api@0.10.1
+ - @backstage/plugin-devtools-common@0.1.15
+ - @backstage/plugin-permission-react@0.4.33
+
## 0.1.26
### Patch Changes
diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json
index f653eac509..2c6cac0cf4 100644
--- a/plugins/devtools/package.json
+++ b/plugins/devtools/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-devtools",
- "version": "0.1.26",
+ "version": "0.1.27-next.1",
"backstage": {
"role": "frontend-plugin",
"pluginId": "devtools",
diff --git a/plugins/devtools/src/alpha/plugin.tsx b/plugins/devtools/src/alpha/plugin.tsx
index bb2142ab9b..959670f3a5 100644
--- a/plugins/devtools/src/alpha/plugin.tsx
+++ b/plugins/devtools/src/alpha/plugin.tsx
@@ -70,7 +70,7 @@ export const devToolsNavItem = NavItemBlueprint.make({
/** @alpha */
export default createFrontendPlugin({
- id: 'devtools',
+ pluginId: 'devtools',
routes: {
root: convertLegacyRouteRef(rootRouteRef),
},
diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md
index f05f5b104d..7af253f205 100644
--- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md
+++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-events-backend-module-aws-sqs
+## 0.4.11-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.4.11-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
## 0.4.10
### Patch Changes
diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json
index aec9c6707e..c006f80ba5 100644
--- a/plugins/events-backend-module-aws-sqs/package.json
+++ b/plugins/events-backend-module-aws-sqs/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-aws-sqs",
- "version": "0.4.10",
+ "version": "0.4.11-next.1",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md
index 91566ec0d6..0beac8787c 100644
--- a/plugins/events-backend-module-azure/CHANGELOG.md
+++ b/plugins/events-backend-module-azure/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-events-backend-module-azure
+## 0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
## 0.2.19
### Patch Changes
diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json
index d74d994e8d..79af151214 100644
--- a/plugins/events-backend-module-azure/package.json
+++ b/plugins/events-backend-module-azure/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-azure",
- "version": "0.2.19",
+ "version": "0.2.20-next.1",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
index bdce46935a..8681c0540c 100644
--- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
+++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-events-backend-module-bitbucket-cloud
+## 0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
## 0.2.19
### Patch Changes
diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json
index 025c3d5ede..62d39f6f0e 100644
--- a/plugins/events-backend-module-bitbucket-cloud/package.json
+++ b/plugins/events-backend-module-bitbucket-cloud/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-bitbucket-cloud",
- "version": "0.2.19",
+ "version": "0.2.20-next.1",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md
index e1a2207a70..dea752d4d7 100644
--- a/plugins/events-backend-module-bitbucket-server/CHANGELOG.md
+++ b/plugins/events-backend-module-bitbucket-server/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-events-backend-module-bitbucket-server
+## 0.1.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.1.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
## 0.1.0
### Minor Changes
diff --git a/plugins/events-backend-module-bitbucket-server/package.json b/plugins/events-backend-module-bitbucket-server/package.json
index 6d4c84ea34..bda209ef5a 100644
--- a/plugins/events-backend-module-bitbucket-server/package.json
+++ b/plugins/events-backend-module-bitbucket-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-bitbucket-server",
- "version": "0.1.0",
+ "version": "0.1.1-next.1",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md
index 19be246ec3..d19438a8c5 100644
--- a/plugins/events-backend-module-gerrit/CHANGELOG.md
+++ b/plugins/events-backend-module-gerrit/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-events-backend-module-gerrit
+## 0.2.20-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.2.20-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+
## 0.2.19
### Patch Changes
diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json
index feb607ccad..102e118552 100644
--- a/plugins/events-backend-module-gerrit/package.json
+++ b/plugins/events-backend-module-gerrit/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-gerrit",
- "version": "0.2.19",
+ "version": "0.2.20-next.1",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md
index e00d28c193..72262b82c9 100644
--- a/plugins/events-backend-module-github/CHANGELOG.md
+++ b/plugins/events-backend-module-github/CHANGELOG.md
@@ -1,5 +1,32 @@
# @backstage/plugin-events-backend-module-github
+## 0.4.0-next.1
+
+### Minor Changes
+
+- ae249fc: **BREAKING**: Removed the `createGithubSignatureValidator` export.
+
+ Added support webhook validation based on `integrations.github.[].apps.[].webhookSecret`.
+
+### Patch Changes
+
+- c7ef81c: Correct README installation instructions.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/integration@1.16.4-next.1
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.3.1-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+
## 0.3.0
### Minor Changes
diff --git a/plugins/events-backend-module-github/README.md b/plugins/events-backend-module-github/README.md
index adc91da4ec..b52592c82d 100644
--- a/plugins/events-backend-module-github/README.md
+++ b/plugins/events-backend-module-github/README.md
@@ -27,16 +27,14 @@ Please find all possible webhook event types at the
yarn --cwd packages/backend add @backstage/plugin-events-backend-module-github
```
-### Event Router
-
```ts
// packages/backend/src/index.ts
-import { eventsModuleGithubEventRouter } from '@backstage/plugin-events-backend-module-github/alpha';
-// ...
-backend.add(eventsModuleGithubEventRouter);
+backend.add(import('@backstage/plugin-events-backend-module-github'));
```
-#### Legacy Backend System
+### Legacy Backend System
+
+#### Event Router
```ts
// packages/backend/src/plugins/events.ts
@@ -44,16 +42,7 @@ const eventRouter = new GithubEventRouter({ events: env.events });
await eventRouter.subscribe();
```
-### Signature Validator
-
-```ts
-// packages/backend/src/index.ts
-import { eventsModuleGithubWebhook } from '@backstage/plugin-events-backend-module-github/alpha';
-// ...
-backend.add(eventsModuleGithubWebhook);
-```
-
-#### Legacy Backend System
+#### Signature Validator
Add the signature validator for the topic `github`:
diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json
index d562cd9a73..b83a52e308 100644
--- a/plugins/events-backend-module-github/package.json
+++ b/plugins/events-backend-module-github/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-github",
- "version": "0.3.0",
+ "version": "0.4.0-next.1",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
@@ -45,8 +45,13 @@
"dependencies": {
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/config": "workspace:^",
+ "@backstage/integration": "workspace:^",
"@backstage/plugin-events-node": "workspace:^",
- "@octokit/webhooks-methods": "^3.0.0"
+ "@backstage/types": "workspace:^",
+ "@octokit/auth-callback": "^5.0.0",
+ "@octokit/webhooks-methods": "^3.0.0",
+ "lodash": "^4.17.21",
+ "octokit": "^3.0.0"
},
"devDependencies": {
"@backstage/backend-test-utils": "workspace:^",
diff --git a/plugins/events-backend-module-github/report.api.md b/plugins/events-backend-module-github/report.api.md
index 8d1f4d939f..afecb7590b 100644
--- a/plugins/events-backend-module-github/report.api.md
+++ b/plugins/events-backend-module-github/report.api.md
@@ -4,17 +4,10 @@
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
-import { Config } from '@backstage/config';
import { EventParams } from '@backstage/plugin-events-node';
import { EventsService } from '@backstage/plugin-events-node';
-import { RequestValidator } from '@backstage/plugin-events-node';
import { SubTopicEventRouter } from '@backstage/plugin-events-node';
-// @public
-export function createGithubSignatureValidator(
- config: Config,
-): RequestValidator | undefined;
-
// @public (undocumented)
const _default: BackendFeature;
export default _default;
diff --git a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts
index bb604e88d8..9cb2ed3c4c 100644
--- a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts
+++ b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.test.ts
@@ -22,6 +22,7 @@ import {
} from '@backstage/plugin-events-node';
import { sign } from '@octokit/webhooks-methods';
import { createGithubSignatureValidator } from './createGithubSignatureValidator';
+import { OctokitProviderService } from '../util/octokitProviderService';
class TestContext implements RequestValidationContext {
#details?: Partial;
@@ -35,6 +36,10 @@ class TestContext implements RequestValidationContext {
}
}
+const octokitProvider = {
+ getOctokit: jest.fn(),
+} satisfies OctokitProviderService;
+
describe('createGithubSignatureValidator', () => {
const secret = 'valid-secret';
const configWithoutSecret = new ConfigReader({});
@@ -47,8 +52,31 @@ describe('createGithubSignatureValidator', () => {
},
},
});
- const payloadString = '{"test": "payload", "score": 5.0}';
- const payload = JSON.parse(payloadString);
+ const configWithAppSecret = new ConfigReader({
+ integrations: {
+ github: [
+ {
+ host: 'github.com',
+ apps: [
+ {
+ appId: 7,
+ privateKey: 'a',
+ clientId: 'b',
+ clientSecret: 'c',
+ webhookSecret: secret,
+ },
+ ],
+ },
+ ],
+ },
+ });
+ const payload = {
+ test: 'payload',
+ score: 5.0,
+ repository: { html_url: 'https://github.com/backstage/backstage' },
+ installation: { id: 70 },
+ };
+ const payloadString = JSON.stringify(payload);
const payloadBuffer = Buffer.from(payloadString);
const validSignature = sign({ secret, algorithm: 'sha256' }, payloadString);
@@ -66,16 +94,19 @@ describe('createGithubSignatureValidator', () => {
};
it('should return undefined if no secret is configured', async () => {
- expect(createGithubSignatureValidator(configWithoutSecret)).toEqual(
- undefined,
- );
+ expect(
+ createGithubSignatureValidator(configWithoutSecret, octokitProvider),
+ ).toEqual(undefined);
});
it('secret configured, reject request without signature', async () => {
const request = await requestWithSignature(undefined);
const context = new TestContext();
- const validator = createGithubSignatureValidator(configWithSecret);
+ const validator = createGithubSignatureValidator(
+ configWithSecret,
+ octokitProvider,
+ );
await validator!(request, context);
expect(context.details).not.toBeUndefined();
@@ -87,7 +118,10 @@ describe('createGithubSignatureValidator', () => {
const request = await requestWithSignature('invalid signature');
const context = new TestContext();
- const validator = createGithubSignatureValidator(configWithSecret);
+ const validator = createGithubSignatureValidator(
+ configWithSecret,
+ octokitProvider,
+ );
await validator!(request, context);
expect(context.details).not.toBeUndefined();
@@ -99,7 +133,32 @@ describe('createGithubSignatureValidator', () => {
const request = await requestWithSignature(await validSignature);
const context = new TestContext();
- const validator = createGithubSignatureValidator(configWithSecret);
+ const validator = createGithubSignatureValidator(
+ configWithSecret,
+ octokitProvider,
+ );
+ await validator!(request, context);
+
+ expect(context.details).toBeUndefined();
+ });
+
+ it('secret configured, accept request with valid signature defined in integrations', async () => {
+ const request = await requestWithSignature(await validSignature);
+ const context = new TestContext();
+ octokitProvider.getOctokit.mockResolvedValue({
+ rest: {
+ apps: {
+ getInstallation: async () => ({
+ data: { app_id: 7 },
+ }),
+ },
+ },
+ });
+
+ const validator = createGithubSignatureValidator(
+ configWithAppSecret,
+ octokitProvider,
+ );
await validator!(request, context);
expect(context.details).toBeUndefined();
diff --git a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts
index f54c00b58a..6eb203b79b 100644
--- a/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts
+++ b/plugins/events-backend-module-github/src/http/createGithubSignatureValidator.ts
@@ -15,12 +15,15 @@
*/
import { Config } from '@backstage/config';
+import { ScmIntegrations } from '@backstage/integration';
import {
RequestDetails,
RequestValidationContext,
RequestValidator,
} from '@backstage/plugin-events-node';
import { verify } from '@octokit/webhooks-methods';
+import { createAppIdResolver } from '../util/createAppIdResolver';
+import { OctokitProviderService } from '../util/octokitProviderService';
/**
* Validates that the request received is the expected GitHub request
@@ -35,14 +38,31 @@ import { verify } from '@octokit/webhooks-methods';
*/
export function createGithubSignatureValidator(
config: Config,
+ octokitProvider: OctokitProviderService,
): RequestValidator | undefined {
- const secret = config.getOptionalString(
+ const integrations = ScmIntegrations.fromConfig(config);
+
+ // GitHub App installation ID to secret
+ const githubAppSecrets = new Map();
+ for (const integration of integrations.github.list()) {
+ for (const { appId, webhookSecret } of integration.config.apps ?? []) {
+ if (appId && webhookSecret) {
+ githubAppSecrets.set(appId, webhookSecret);
+ }
+ }
+ }
+
+ // A single optional secret for all GitHub events
+ const genericSecret = config.getOptionalString(
'events.modules.github.webhookSecret',
);
- if (!secret) {
+
+ if (!genericSecret && githubAppSecrets.size === 0) {
return undefined;
}
+ const appIdResolver = createAppIdResolver(octokitProvider);
+
return async (
request: RequestDetails,
context: RequestValidationContext,
@@ -51,18 +71,28 @@ export function createGithubSignatureValidator(
| string
| undefined;
- if (
- !signature ||
- !(await verify(
- secret,
- request.raw.body.toString(request.raw.encoding),
- signature,
- ))
- ) {
- context.reject({
- status: 403,
- payload: { message: 'invalid signature' },
- });
+ if (signature) {
+ const body = request.raw.body.toString(request.raw.encoding);
+
+ if (githubAppSecrets.size) {
+ const appId = await appIdResolver(request);
+ if (appId && githubAppSecrets.has(appId)) {
+ if (await verify(githubAppSecrets.get(appId)!, body, signature)) {
+ return;
+ }
+ }
+ }
+
+ if (genericSecret) {
+ if (await verify(genericSecret, body, signature)) {
+ return;
+ }
+ }
}
+
+ context.reject({
+ status: 403,
+ payload: { message: 'invalid signature' },
+ });
};
}
diff --git a/plugins/events-backend-module-github/src/index.ts b/plugins/events-backend-module-github/src/index.ts
index 72b55f892b..285d74bae4 100644
--- a/plugins/events-backend-module-github/src/index.ts
+++ b/plugins/events-backend-module-github/src/index.ts
@@ -20,6 +20,7 @@
*
* @packageDocumentation
*/
+
import { createBackendFeatureLoader } from '@backstage/backend-plugin-api';
export default createBackendFeatureLoader({
@@ -31,5 +32,8 @@ export default createBackendFeatureLoader({
},
});
-export { createGithubSignatureValidator } from './http/createGithubSignatureValidator';
+// TODO(freben): This is not exported at the moment since it depends on the octokit provider.
+// Until we have made that a core thing in integrations, we can't export it
+// export { createGithubSignatureValidator } from './http/createGithubSignatureValidator';
+
export { GithubEventRouter } from './router/GithubEventRouter';
diff --git a/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts b/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts
index 5cb9c88679..78def1f30a 100644
--- a/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts
+++ b/plugins/events-backend-module-github/src/service/eventsModuleGithubWebhook.ts
@@ -20,6 +20,7 @@ import {
} from '@backstage/backend-plugin-api';
import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
import { createGithubSignatureValidator } from '../http/createGithubSignatureValidator';
+import { octokitProviderServiceRef } from '../util/octokitProviderService';
/**
* Module for the events-backend plugin,
@@ -36,9 +37,13 @@ export default createBackendModule({
deps: {
config: coreServices.rootConfig,
events: eventsExtensionPoint,
+ octokitProvider: octokitProviderServiceRef,
},
- async init({ config, events }) {
- const validator = createGithubSignatureValidator(config);
+ async init({ config, events, octokitProvider }) {
+ const validator = createGithubSignatureValidator(
+ config,
+ octokitProvider,
+ );
if (validator) {
events.addHttpPostIngress({
topic: 'github',
diff --git a/plugins/events-backend-module-github/src/util/createAppIdResolver.ts b/plugins/events-backend-module-github/src/util/createAppIdResolver.ts
new file mode 100644
index 0000000000..d5347ffd29
--- /dev/null
+++ b/plugins/events-backend-module-github/src/util/createAppIdResolver.ts
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2025 The 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 { RequestDetails } from '@backstage/plugin-events-node';
+import { OctokitProviderService } from './octokitProviderService';
+import lodash from 'lodash';
+
+export type AppIdResolver = (
+ request: RequestDetails,
+) => Promise;
+
+/**
+ * Helps with resolving what app ID (if any) that sent a webhook event.
+ */
+export function createAppIdResolver(
+ octokitProvider: OctokitProviderService,
+): AppIdResolver {
+ const installationIdToAppId = new Map>();
+
+ return async (request: RequestDetails) => {
+ const installationId = lodash.get(
+ request.body,
+ 'installation.id',
+ ) as unknown;
+
+ if (!installationId || typeof installationId !== 'number') {
+ return undefined;
+ }
+
+ let appIdPromsie = installationIdToAppId.get(installationId);
+ if (appIdPromsie) {
+ return await appIdPromsie;
+ }
+
+ const repositoryUrl = lodash.get(
+ request.body,
+ 'repository.html_url',
+ ) as unknown;
+
+ if (!repositoryUrl || typeof repositoryUrl !== 'string') {
+ return undefined;
+ }
+
+ const octokit = await octokitProvider.getOctokit(repositoryUrl);
+ appIdPromsie = octokit.rest.apps
+ .getInstallation({ installation_id: installationId })
+ .then(response => Number(response.data.app_id))
+ .catch(() => undefined);
+
+ installationIdToAppId.set(installationId, appIdPromsie);
+ return await appIdPromsie;
+ };
+}
diff --git a/plugins/events-backend-module-github/src/util/octokitProviderService.ts b/plugins/events-backend-module-github/src/util/octokitProviderService.ts
new file mode 100644
index 0000000000..9993b91000
--- /dev/null
+++ b/plugins/events-backend-module-github/src/util/octokitProviderService.ts
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2025 The 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 {
+ coreServices,
+ createServiceFactory,
+ createServiceRef,
+ RootConfigService,
+} from '@backstage/backend-plugin-api';
+import {
+ DefaultGithubCredentialsProvider,
+ GithubCredentialsProvider,
+ ScmIntegrationRegistry,
+ ScmIntegrations,
+} from '@backstage/integration';
+import { durationToMilliseconds, HumanDuration } from '@backstage/types';
+import { Octokit } from 'octokit';
+
+export interface OctokitProviderService {
+ getOctokit: (url: string) => Promise;
+}
+
+class OctokitProviderImpl implements OctokitProviderService {
+ readonly #integrations: ScmIntegrationRegistry;
+ readonly #githubCredentials: GithubCredentialsProvider;
+ readonly #octokitCache: Map;
+ readonly #octokitCacheTtl: HumanDuration;
+
+ constructor(config: RootConfigService) {
+ this.#integrations = ScmIntegrations.fromConfig(config);
+ this.#githubCredentials = DefaultGithubCredentialsProvider.fromIntegrations(
+ this.#integrations,
+ );
+ this.#octokitCache = new Map();
+ this.#octokitCacheTtl = { hours: 1 };
+ }
+
+ async getOctokit(url: string): Promise {
+ // TODO(freben): Be smart and cache these more granularly, e.g. by
+ // organization or even repo.
+ const integration = this.#integrations.github.byUrl(url);
+ if (!integration) {
+ throw new Error(`No integration found for url: ${url}`);
+ }
+ const key = integration.config.host;
+
+ if (this.#octokitCache.has(key)) {
+ return this.#octokitCache.get(key)!;
+ }
+
+ const { createCallbackAuth } = await import('@octokit/auth-callback');
+
+ const octokit = new Octokit({
+ baseUrl: integration.config.apiBaseUrl,
+ authStrategy: createCallbackAuth,
+ auth: {
+ callback: async () => {
+ try {
+ const credentials = await this.#githubCredentials.getCredentials({
+ url,
+ });
+ return credentials.token;
+ } catch {
+ return undefined;
+ }
+ },
+ },
+ });
+
+ this.#octokitCache.set(key, octokit);
+ setTimeout(() => {
+ this.#octokitCache.delete(key);
+ }, durationToMilliseconds(this.#octokitCacheTtl));
+
+ return octokit;
+ }
+}
+
+export const octokitProviderServiceRef =
+ createServiceRef({
+ id: 'octokitProvider',
+ scope: 'root',
+ defaultFactory: async service =>
+ createServiceFactory({
+ service,
+ deps: { config: coreServices.rootConfig },
+ async factory({ config }) {
+ return new OctokitProviderImpl(config);
+ },
+ }),
+ });
diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md
index 36fc393021..2f509717be 100644
--- a/plugins/events-backend-module-gitlab/CHANGELOG.md
+++ b/plugins/events-backend-module-gitlab/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/plugin-events-backend-module-gitlab
+## 0.3.1-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.3.1-next.0
+
+### Patch Changes
+
+- a820df1: Adds support for `object_kind` field with priority over `event_name` on Gitlab webhook event types
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+
## 0.3.0
### Minor Changes
diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json
index f720e6e5af..6af20dccdc 100644
--- a/plugins/events-backend-module-gitlab/package.json
+++ b/plugins/events-backend-module-gitlab/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-gitlab",
- "version": "0.3.0",
+ "version": "0.3.1-next.1",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-google-pubsub/.eslintrc.js b/plugins/events-backend-module-google-pubsub/.eslintrc.js
new file mode 100644
index 0000000000..e2a53a6ad2
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/.eslintrc.js
@@ -0,0 +1 @@
+module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
diff --git a/plugins/events-backend-module-google-pubsub/CHANGELOG.md b/plugins/events-backend-module-google-pubsub/CHANGELOG.md
new file mode 100644
index 0000000000..c24077f931
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/CHANGELOG.md
@@ -0,0 +1,27 @@
+# @backstage/plugin-events-backend-module-google-pubsub
+
+## 0.1.0-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-events-node@0.4.11-next.1
+
+## 0.1.0-next.0
+
+### Minor Changes
+
+- af853ef: Added a module that is able to transfer messages from Google Pub/Sub subscriptions into the Backstage events system.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.3.1-next.0
+ - @backstage/plugin-events-node@0.4.11-next.0
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
diff --git a/plugins/events-backend-module-google-pubsub/README.md b/plugins/events-backend-module-google-pubsub/README.md
new file mode 100644
index 0000000000..67ddcfacb4
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/README.md
@@ -0,0 +1,42 @@
+# @backstage/plugin-events-backend-module-google-pubsub
+
+This package is a module for the `events-backend` backend plugin
+and extends the events system with Google Pub/Sub related capabilities.
+
+## Configuration
+
+The following configuration enables the transfer of messages from a Google
+Pub/Sub subscription into a topic on the Backstage events system.
+
+```yaml
+events:
+ modules:
+ googlePubSub:
+ googlePubSubConsumingEventPublisher:
+ subscriptions:
+ # A unique key for your subscription, to be used in logging and metrics
+ mySubscription:
+ # The fully qualified name of the subscription
+ subscriptionName: 'projects/my-google-project/subscriptions/github-enterprise-events'
+ # The event system topic to transfer to. This can also be just a plain string
+ targetTopic:
+ # This example picks the topic name from a message attribute + a prefix
+ fromMessageAttribute:
+ attributeName: 'x-github-event'
+ withPrefix: 'github.'
+```
+
+## Installation
+
+1. Install this module
+2. Add your configuration.
+
+```bash
+# From your Backstage root directory
+yarn --cwd packages/backend add @backstage/plugin-events-backend-module-google-pubsub
+```
+
+```ts
+// packages/backend/src/index.ts
+backend.add(import('@backstage/plugin-events-backend-module-google-pubsub'));
+```
diff --git a/plugins/events-backend-module-google-pubsub/catalog-info.yaml b/plugins/events-backend-module-google-pubsub/catalog-info.yaml
new file mode 100644
index 0000000000..2cf0459a30
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/catalog-info.yaml
@@ -0,0 +1,10 @@
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ name: backstage-plugin-events-backend-module-google-pubsub
+ title: '@backstage/plugin-events-backend-module-google-pubsub'
+ description: The google-pubsub backend module for the events plugin.
+spec:
+ lifecycle: experimental
+ type: backstage-backend-plugin-module
+ owner: maintainers
diff --git a/plugins/events-backend-module-google-pubsub/config.d.ts b/plugins/events-backend-module-google-pubsub/config.d.ts
new file mode 100644
index 0000000000..3e70489b9d
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/config.d.ts
@@ -0,0 +1,96 @@
+/*
+ * 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 {
+ events?: {
+ modules?: {
+ /**
+ * events-backend-module-google-pubsub configuration.
+ */
+ googlePubSub?: {
+ /**
+ * Configuration for `GooglePubSubConsumingEventPublisher`, which
+ * consumes messages from a Google Pub/Sub subscription and forwards
+ * them into the Backstage events system.
+ */
+ googlePubSubConsumingEventPublisher?: {
+ /**
+ * Generally contains a record per subscription to consume.
+ */
+ subscriptions: {
+ /**
+ * The name can be anything, but it is recommended to use only
+ * letters, numbers, and hyphens for this identifier since it will
+ * appear in logs and metric names etc.
+ */
+ [name: string]: {
+ /**
+ * The complete name of the Pub/Sub subscription to be used, on the
+ * form
+ * `projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID`.
+ */
+ subscriptionName: string;
+
+ /**
+ * The name of the events backend topic to which the messages are
+ * to be forwarded.
+ *
+ * @remarks
+ *
+ * The value can contain placeholders on the form `{{ message.attributes.foo }}`,
+ * to mirror attribute `foo` as the whole or part of the topic name.
+ *
+ * @example
+ *
+ * This example expects the Pub/Sub topic to contain GitHub
+ * webhook events where the HTTP headers were mapped into
+ * message attributes. The outcome should be that messages
+ * end up on event topics such as `github.push`,
+ * `github.repository` etc which matches the [`@backstage/plugin-events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github) structure.
+ *
+ * ```yaml
+ * targetTopic: 'github.{{ message.attributes.x-github-event }}'
+ * ```
+ */
+ targetTopic: string;
+
+ /**
+ * Pub/Sub message attributes are by default copied to the event
+ * metadata field. This setting allows you to override or amend
+ * that metadata.
+ *
+ * @remarks
+ *
+ * The values can contain placeholders on the form `{{ message.attributes.foo }}`,
+ * to mirror attribute `foo` as the whole or part of a metadata value.
+ *
+ * @example
+ *
+ * ```yaml
+ * eventMetadata:
+ * x-gitHub-event: '{{ message.attributes.event }}'
+ * ```
+ */
+ eventMetadata?: {
+ [key: string]: string;
+ };
+ };
+ };
+ };
+ };
+ };
+ };
+}
diff --git a/plugins/events-backend-module-google-pubsub/dev/index.ts b/plugins/events-backend-module-google-pubsub/dev/index.ts
new file mode 100644
index 0000000000..752217e6db
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/dev/index.ts
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2025 The 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 { createBackend } from '@backstage/backend-defaults';
+import {
+ coreServices,
+ createBackendPlugin,
+} from '@backstage/backend-plugin-api';
+import { eventsServiceRef } from '@backstage/plugin-events-node';
+
+const backend = createBackend();
+
+backend.add(import('@backstage/plugin-events-backend'));
+backend.add(import('../src'));
+
+backend.add(
+ createBackendPlugin({
+ pluginId: 'example-event-consumer',
+ register(reg) {
+ reg.registerInit({
+ deps: {
+ events: eventsServiceRef,
+ logger: coreServices.logger,
+ },
+ async init({ events, logger }) {
+ events.subscribe({
+ id: 'example-subscription',
+ topics: ['example-topic'],
+ async onEvent(event) {
+ logger.info(
+ `Received event: ${JSON.stringify(
+ {
+ topic: event.topic,
+ payload: event.eventPayload,
+ metadata: event.metadata,
+ },
+ null,
+ 2,
+ )}`,
+ );
+ },
+ });
+ },
+ });
+ },
+ }),
+);
+
+backend.start();
diff --git a/plugins/events-backend-module-google-pubsub/package.json b/plugins/events-backend-module-google-pubsub/package.json
new file mode 100644
index 0000000000..6d8418eb59
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/package.json
@@ -0,0 +1,53 @@
+{
+ "name": "@backstage/plugin-events-backend-module-google-pubsub",
+ "version": "0.1.0-next.1",
+ "description": "The google-pubsub backend module for the events plugin.",
+ "backstage": {
+ "role": "backend-plugin-module",
+ "pluginId": "events",
+ "pluginPackage": "@backstage/plugin-events-backend"
+ },
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.cjs.js",
+ "types": "dist/index.d.ts"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/events-backend-module-google-pubsub"
+ },
+ "license": "Apache-2.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "files": [
+ "config.d.ts",
+ "dist"
+ ],
+ "scripts": {
+ "build": "backstage-cli package build",
+ "clean": "backstage-cli package clean",
+ "lint": "backstage-cli package lint",
+ "prepack": "backstage-cli package prepack",
+ "postpack": "backstage-cli package postpack",
+ "start": "backstage-cli package start",
+ "test": "backstage-cli package test"
+ },
+ "dependencies": {
+ "@backstage/backend-plugin-api": "workspace:^",
+ "@backstage/config": "workspace:^",
+ "@backstage/errors": "workspace:^",
+ "@backstage/plugin-events-node": "workspace:^",
+ "@backstage/types": "workspace:^",
+ "@google-cloud/pubsub": "^4.10.0",
+ "@opentelemetry/api": "^1.9.0"
+ },
+ "devDependencies": {
+ "@backstage/backend-defaults": "workspace:^",
+ "@backstage/backend-test-utils": "workspace:^",
+ "@backstage/cli": "workspace:^",
+ "@backstage/plugin-events-backend": "workspace:^",
+ "wait-for-expect": "^3.0.2"
+ },
+ "configSchema": "config.d.ts"
+}
diff --git a/plugins/events-backend-module-google-pubsub/report.api.md b/plugins/events-backend-module-google-pubsub/report.api.md
new file mode 100644
index 0000000000..bdb057d7fc
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/report.api.md
@@ -0,0 +1,11 @@
+## API Report File for "@backstage/plugin-events-backend-module-google-pubsub"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { BackendFeature } from '@backstage/backend-plugin-api';
+
+// @public
+const eventsModuleGooglePubsubConsumingEventPublisher: BackendFeature;
+export default eventsModuleGooglePubsubConsumingEventPublisher;
+```
diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.test.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.test.ts
new file mode 100644
index 0000000000..60c6816729
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.test.ts
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2025 The 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 { mockServices } from '@backstage/backend-test-utils';
+import { Message, PubSub } from '@google-cloud/pubsub';
+import { GooglePubSubConsumingEventPublisher } from './GooglePubSubConsumingEventPublisher';
+import { JsonObject } from '@backstage/types';
+import waitFor from 'wait-for-expect';
+
+function makeMessage(
+ data: JsonObject,
+ attributes: Record,
+): Message {
+ const message: Partial = {
+ attributes,
+ data: Buffer.from(JSON.stringify(data), 'utf-8'),
+ ack: jest.fn(),
+ nack: jest.fn(),
+ };
+ return message as Message;
+}
+
+describe('GooglePubSubConsumingEventPublisher', () => {
+ const logger = mockServices.logger.mock();
+ const events = mockServices.events.mock();
+
+ let onMessage: undefined | ((message: Message) => void);
+ const subscription = {
+ close: jest.fn(),
+ on: jest.fn((event, fn) => {
+ if (event === 'message') {
+ onMessage = fn;
+ }
+ }),
+ };
+
+ const pubSub = {
+ close: jest.fn(),
+ subscription: jest.fn(() => subscription),
+ };
+ const pubSubFactory = jest.fn(() => pubSub as unknown as PubSub);
+
+ beforeEach(() => {
+ onMessage = undefined;
+ jest.clearAllMocks();
+ });
+
+ it('should go though the expected registration and flows', async () => {
+ const publisher = new GooglePubSubConsumingEventPublisher({
+ logger,
+ events,
+ tasks: [
+ {
+ id: 'my-id',
+ project: 'my-project',
+ subscription: 'my-subscription',
+ mapToTopic: () => 'my-topic',
+ mapToMetadata: m => ({ ...m.attributes, more: 'yes' }),
+ },
+ ],
+ pubSubFactory,
+ });
+
+ // Start up
+
+ await publisher.start();
+
+ expect(pubSubFactory).toHaveBeenCalledWith('my-project');
+ expect(pubSub.subscription).toHaveBeenCalledWith(
+ 'my-subscription',
+ expect.anything(),
+ );
+
+ // Publish successfully
+
+ let message = makeMessage({ foo: 'bar' }, { extra: 'data' });
+ onMessage!(message);
+
+ await waitFor(() => {
+ expect(events.publish).toHaveBeenCalledWith({
+ topic: 'my-topic',
+ eventPayload: { foo: 'bar' },
+ metadata: { extra: 'data', more: 'yes' },
+ });
+ expect(message.ack).toHaveBeenCalled();
+ });
+
+ // Simulate a failed publish
+
+ events.publish.mockRejectedValueOnce(new Error('Failed to publish'));
+
+ message = makeMessage({ foo: 'bar' }, { extra: 'data' });
+ onMessage!(message);
+
+ await waitFor(() => {
+ expect(message.nack).toHaveBeenCalled();
+ expect(message.ack).not.toHaveBeenCalled();
+ });
+
+ // Shut down
+
+ await publisher.stop();
+
+ expect(subscription.close).toHaveBeenCalled();
+ expect(pubSub.close).toHaveBeenCalled();
+ });
+});
diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.ts
new file mode 100644
index 0000000000..4844c216fb
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/GooglePubSubConsumingEventPublisher.ts
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2025 The 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 {
+ LoggerService,
+ RootConfigService,
+ RootLifecycleService,
+} from '@backstage/backend-plugin-api';
+import { EventParams, EventsService } from '@backstage/plugin-events-node';
+import { Message, PubSub, Subscription } from '@google-cloud/pubsub';
+import { Counter, metrics } from '@opentelemetry/api';
+import { readSubscriptionTasksFromConfig } from './config';
+import { SubscriptionTask } from './types';
+
+/**
+ * Reads messages off of Google Pub/Sub subscriptions and forwards them into the
+ * Backstage events system.
+ */
+export class GooglePubSubConsumingEventPublisher {
+ readonly #logger: LoggerService;
+ readonly #events: EventsService;
+ readonly #tasks: SubscriptionTask[];
+ readonly #pubSubFactory: (projectId: string) => PubSub;
+ readonly #metrics: { messages: Counter };
+ #activeClientsByProjectId: Map;
+ #activeSubscriptions: Subscription[];
+
+ static create(options: {
+ config: RootConfigService;
+ logger: LoggerService;
+ rootLifecycle: RootLifecycleService;
+ events: EventsService;
+ }) {
+ const publisher = new GooglePubSubConsumingEventPublisher({
+ logger: options.logger,
+ events: options.events,
+ tasks: readSubscriptionTasksFromConfig(options.config),
+ pubSubFactory: projectId => new PubSub({ projectId }),
+ });
+
+ options.rootLifecycle.addStartupHook(async () => {
+ await publisher.start();
+ });
+
+ options.rootLifecycle.addBeforeShutdownHook(async () => {
+ await publisher.stop();
+ });
+
+ return publisher;
+ }
+
+ constructor(options: {
+ logger: LoggerService;
+ events: EventsService;
+ tasks: SubscriptionTask[];
+ pubSubFactory: (projectId: string) => PubSub;
+ }) {
+ this.#logger = options.logger;
+ this.#events = options.events;
+ this.#tasks = options.tasks;
+ this.#pubSubFactory = options.pubSubFactory;
+
+ const meter = metrics.getMeter('default');
+ this.#metrics = {
+ messages: meter.createCounter(
+ 'events.google.pubsub.consumer.messages.total',
+ {
+ description:
+ 'Number of Pub/Sub messages received by GooglePubSubConsumingEventPublisher',
+ unit: 'short',
+ },
+ ),
+ };
+
+ this.#activeClientsByProjectId = new Map();
+ this.#activeSubscriptions = [];
+ }
+
+ async start() {
+ for (const task of this.#tasks) {
+ this.#logger.info(
+ `Starting subscription: id=${task.id} project=${task.project} subscription=${task.subscription}`,
+ );
+
+ let pubsub = this.#activeClientsByProjectId.get(task.project);
+ if (!pubsub) {
+ pubsub = this.#pubSubFactory(task.project);
+ this.#activeClientsByProjectId.set(task.project, pubsub);
+ }
+
+ // You cannot control the actual batch size delivered to the client from
+ // pubsub, so these settings actually instead control the rate at which
+ // messages are released to our event handlers by the pubsub library. This
+ // means that there may be significantly more than maxMessages messages
+ // pending in memory before we see them. Thus, the settings here are rather
+ // chosen so as to limit the concurrency of hammering consumers (the catalog
+ // etc).
+ const subscription = pubsub.subscription(task.subscription, {
+ flowControl: {
+ maxMessages: 5,
+ allowExcessMessages: false,
+ },
+ });
+
+ this.#activeSubscriptions.push(subscription);
+
+ subscription.on('error', error => {
+ this.#logger.error(
+ `Error reading Google Pub/Sub subscription: ${task.id}`,
+ error,
+ );
+ });
+
+ subscription.on('message', async message => {
+ let event: EventParams;
+ try {
+ event = this.#messageToEvent(message, task)!;
+ if (!event) {
+ this.#metrics.messages.add(1, {
+ subscription: task.id,
+ status: 'ignored',
+ });
+ return;
+ }
+ } catch (error) {
+ this.#logger.error('Error processing Google Pub/Sub message', error);
+ this.#metrics.messages.add(1, {
+ subscription: task.id,
+ status: 'failed',
+ });
+ // We unconditionally ACK the message in this case, because if it's
+ // broken, it will still be broken next time around, so there is no
+ // point in re-delivering it.
+ message.ack();
+ return;
+ }
+
+ try {
+ await this.#events.publish(event);
+ this.#metrics.messages.add(1, {
+ subscription: task.id,
+ status: 'success',
+ });
+ message.ack();
+ } catch (error) {
+ this.#logger.error('Error processing Google Pub/Sub message', error);
+ this.#metrics.messages.add(1, {
+ subscription: task.id,
+ status: 'failed',
+ });
+ // We fast-NACK the message in this case because this may be a
+ // transient problem with the events backend.
+ message.nack();
+ }
+ });
+ }
+ }
+
+ async stop() {
+ const subscriptions = this.#activeSubscriptions;
+ const clients = Array.from(this.#activeClientsByProjectId.values());
+
+ this.#activeSubscriptions = [];
+ this.#activeClientsByProjectId = new Map();
+
+ await Promise.allSettled(
+ subscriptions.map(async subscription => {
+ this.#logger.info(
+ `Closing Google Pub/Sub subscription: ${subscription.name}`,
+ );
+ await subscription.close();
+ }),
+ );
+
+ await Promise.allSettled(
+ clients.map(async client => {
+ this.#logger.info(`Closing Google Pub/Sub client: ${client.projectId}`);
+ await client.close();
+ }),
+ );
+ }
+
+ #messageToEvent(
+ message: Message,
+ task: SubscriptionTask,
+ ): EventParams | undefined {
+ const topic = task.mapToTopic(message);
+ if (!topic) {
+ return undefined;
+ }
+ return {
+ topic,
+ eventPayload: JSON.parse(message.data.toString()),
+ metadata: task.mapToMetadata(message),
+ };
+ }
+}
diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts
new file mode 100644
index 0000000000..bfe3c20773
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.test.ts
@@ -0,0 +1,194 @@
+/*
+ * Copyright 2025 The 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 { mockServices } from '@backstage/backend-test-utils';
+import { JsonObject } from '@backstage/types';
+import { Message } from '@google-cloud/pubsub';
+import { readSubscriptionTasksFromConfig } from './config';
+
+function makeMessage(
+ data: JsonObject,
+ attributes: Record,
+): Message {
+ const message: Partial = {
+ attributes,
+ data: Buffer.from(JSON.stringify(data), 'utf-8'),
+ ack: jest.fn(),
+ nack: jest.fn(),
+ };
+ return message as Message;
+}
+
+describe('readSubscriptionTasksFromConfig', () => {
+ it('reads with basic targetTopic', () => {
+ const data = {
+ events: {
+ modules: {
+ googlePubSub: {
+ googlePubSubConsumingEventPublisher: {
+ subscriptions: {
+ subKey: {
+ subscriptionName: 'projects/pid/subscriptions/sid',
+ targetTopic: 'my-topic',
+ },
+ },
+ },
+ },
+ },
+ },
+ };
+
+ const result = readSubscriptionTasksFromConfig(
+ mockServices.rootConfig({ data }),
+ );
+
+ expect(result).toEqual([
+ {
+ id: 'subKey',
+ project: 'pid',
+ subscription: 'sid',
+ mapToTopic: expect.any(Function),
+ mapToMetadata: expect.any(Function),
+ },
+ ]);
+
+ expect(
+ result[0].mapToTopic(makeMessage({ foo: 'bar' }, { attr: 'yes' })),
+ ).toBe('my-topic');
+ expect(
+ result[0].mapToMetadata(makeMessage({ foo: 'bar' }, { attr: 'yes' })),
+ ).toEqual({ attr: 'yes' });
+ });
+
+ it('fills in placeholders', () => {
+ const data = {
+ events: {
+ modules: {
+ googlePubSub: {
+ googlePubSubConsumingEventPublisher: {
+ subscriptions: {
+ sub1: {
+ subscriptionName: 'projects/pid/subscriptions/sid',
+ targetTopic: 't.{{ message.attributes.exists }}',
+ eventMetadata: {
+ meta1: 'updated.{{ message.attributes.exists }}',
+ meta2: 'updated.{{ message.attributes.missing }}',
+ },
+ },
+ sub2: {
+ subscriptionName: 'projects/pid/subscriptions/sid',
+ targetTopic: 't.{{ message.attributes.missing }}',
+ eventMetadata: {
+ meta3: 'new',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ };
+
+ const result = readSubscriptionTasksFromConfig(
+ mockServices.rootConfig({ data }),
+ );
+
+ expect(result).toEqual([
+ {
+ id: 'sub1',
+ project: 'pid',
+ subscription: 'sid',
+ mapToTopic: expect.any(Function),
+ mapToMetadata: expect.any(Function),
+ },
+ {
+ id: 'sub2',
+ project: 'pid',
+ subscription: 'sid',
+ mapToTopic: expect.any(Function),
+ mapToMetadata: expect.any(Function),
+ },
+ ]);
+
+ expect(
+ result[0].mapToTopic(
+ makeMessage(
+ { foo: 'bar' },
+ { exists: 'exists', meta1: 'original1', meta2: 'original2' },
+ ),
+ ),
+ ).toBe('t.exists'); // Message attribute existed, successfully routed
+ expect(
+ result[0].mapToMetadata(
+ makeMessage(
+ { foo: 'bar' },
+ { exists: 'exists', meta1: 'original1', meta2: 'original2' },
+ ),
+ ),
+ ).toEqual({
+ exists: 'exists',
+ meta1: 'updated.exists', // message attribute existed, was replaced
+ meta2: 'original2', // message attribute did not exist, was not replaced
+ });
+
+ expect(
+ result[1].mapToTopic(
+ makeMessage(
+ { foo: 'bar' },
+ { exists: 'exists', meta1: 'original1', meta2: 'original2' },
+ ),
+ ),
+ ).toBeUndefined(); // Message attribute did not exist, could not be routed
+ expect(
+ result[1].mapToMetadata(
+ makeMessage(
+ { foo: 'bar' },
+ { exists: 'exists', meta1: 'original1', meta2: 'original2' },
+ ),
+ ),
+ ).toEqual({
+ exists: 'exists',
+ meta1: 'original1',
+ meta2: 'original2',
+ meta3: 'new',
+ });
+ });
+
+ it('rejects malformed subscription name', () => {
+ const data = {
+ events: {
+ modules: {
+ googlePubSub: {
+ googlePubSubConsumingEventPublisher: {
+ subscriptions: {
+ subKey: {
+ subscriptionName: 'sid',
+ targetTopic: 'foo',
+ },
+ },
+ },
+ },
+ },
+ },
+ };
+
+ expect(() =>
+ readSubscriptionTasksFromConfig(mockServices.rootConfig({ data })),
+ ).toThrowErrorMatchingInlineSnapshot(
+ `"Expected Googoe Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got 'sid'"`,
+ );
+ });
+});
diff --git a/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts
new file mode 100644
index 0000000000..fdcab2e2e5
--- /dev/null
+++ b/plugins/events-backend-module-google-pubsub/src/GooglePubSubConsumingEventPublisher/config.ts
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2025 The 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 { RootConfigService } from '@backstage/backend-plugin-api';
+import { Config } from '@backstage/config';
+import { InputError } from '@backstage/errors';
+import { Message } from '@google-cloud/pubsub';
+import { createPatternResolver } from '../util/createPatternResolver';
+import { SubscriptionTask } from './types';
+
+export function readSubscriptionTasksFromConfig(
+ rootConfig: RootConfigService,
+): SubscriptionTask[] {
+ const subscriptionsConfig = rootConfig.getOptionalConfig(
+ 'events.modules.googlePubSub.googlePubSubConsumingEventPublisher.subscriptions',
+ );
+ if (!subscriptionsConfig) {
+ return [];
+ }
+
+ return subscriptionsConfig.keys().map(subscriptionId => {
+ if (!subscriptionId.match(/^[-_\w]+$/)) {
+ throw new InputError(
+ `Expected Googoe Pub/Sub subscription ID to consist of letters, numbers, dashes and underscores, but got '${subscriptionId}'`,
+ );
+ }
+
+ const config = subscriptionsConfig.getConfig(subscriptionId);
+ const { project, subscription } = readSubscriptionName(config);
+ const mapToTopic = readTopicMapper(config);
+ const mapToMetadata = readMetadataMapper(config);
+
+ return {
+ id: subscriptionId,
+ project,
+ subscription,
+ mapToTopic,
+ mapToMetadata,
+ };
+ });
+}
+
+function readSubscriptionName(config: Config): {
+ project: string;
+ subscription: string;
+} {
+ const subscriptionName = config.getString('subscriptionName');
+ const parts = subscriptionName.match(
+ /^projects\/([^/]+)\/subscriptions\/(.+)$/,
+ );
+ if (!parts) {
+ throw new InputError(
+ `Expected Googoe Pub/Sub 'subscriptionName' to be on the form 'projects/PROJECT_ID/subscriptions/SUBSCRIPTION_ID' but got '${subscriptionName}'`,
+ );
+ }
+ return {
+ project: parts[1],
+ subscription: parts[2],
+ };
+}
+
+/**
+ * Handles the `targetTopic` configuration field.
+ */
+function readTopicMapper(
+ config: Config,
+): (message: Message) => string | undefined {
+ const targetTopicPattern = config.getString('targetTopic');
+ const patternResolver = createPatternResolver(targetTopicPattern);
+ return message => {
+ try {
+ return patternResolver({ message });
+ } catch {
+ // could not map to a topic
+ return undefined;
+ }
+ };
+}
+
+/**
+ * Handles the `eventMetadata` configuration field.
+ */
+function readMetadataMapper(
+ config: Config,
+): (message: Message) => Record {
+ const setters = new Array<
+ (options: { message: Message; metadata: Record }) => void
+ >();
+
+ const eventMetadata = config.getOptionalConfig('eventMetadata');
+ if (eventMetadata) {
+ for (const key of eventMetadata?.keys() ?? []) {
+ const valuePattern = eventMetadata.getString(key);
+ const patternResolver = createPatternResolver(valuePattern);
+ setters.push(({ message, metadata }) => {
+ try {
+ const value = patternResolver({ message });
+ if (value) {
+ metadata[key] = value;
+ }
+ } catch {
+ // ignore silently, keep original
+ }
+ });
+ }
+ }
+
+ return message => {
+ const result: Record