From 00df8d6fcdd6290e73601db619b41c002d30ff5c Mon Sep 17 00:00:00 2001 From: markyjackson-taulia Date: Sun, 18 Feb 2024 06:05:11 -0800 Subject: [PATCH 001/234] Adding Anuclei to Backstage adopters list Signed-off-by: markyjackson-taulia --- ADOPTERS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 8f3f93658b..7d22f72778 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -262,4 +262,5 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Gynzy](https://gynzy.com) | [Stef Louwers](https://github.com/fhp) | We are building an internal developer portal to get an overview of all our software components. | | [Cielo](https://www.cielo.com.br) | [@Alex Silva](https://github.com/narokwq) | We are using as our Internal Developer Portal, it provides developers with the resources, information, and tools they need to build high-quality applications and adhere to organizational standards and security practices. | | [Giant Swarm](https://giantswarm.io) | [Lucas Weatherhog](https://github.com/weatherhog) | At Giant Swarm we are using Backstage as our internal developer portal. Heavily using the service catalog and the TechDocs plugin. Further looking into adopting functionalities of our Web UI into Backstage to have a single pane of glass for our core business, managing all our components, and helping developers to be more productive. | -| [WD Studios](https://wdstudios.tech) | [Mikael Aboagye](https://github.com/JailbreakPapa) | We are using Backstage as our main internal developer portal for our Programmers and Studio Partners. It's really proven its worth at our studio with on-boarding new programmers! +| [WD Studios](https://wdstudios.tech) | [Mikael Aboagye](https://github.com/JailbreakPapa) | We are using Backstage as our main internal developer portal for our Programmers and Studio Partners. It's really proven its worth at our studio with on-boarding new programmers! | +| [Anuclei](https://anuclei.com) | [Mark Jackson](https://github.com/markjacksonfishing) | At Anuclei, we are using Backstage as our internal portal for developers, providing all the tools and guidance needed for full-stack software projects. It simplifies our development methods, making innovation and investment in new ideas consistent and easy to repeat. From ff176942c83b57bcac879214646175737794785a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 28 Feb 2024 10:57:31 +0100 Subject: [PATCH 002/234] docs/frontend-system: starting point for app config docs Signed-off-by: Patrik Oldsberg --- .../02-configuring-extensions.md | 90 ++++++++++++++++++- 1 file changed, 89 insertions(+), 1 deletion(-) diff --git a/docs/frontend-system/building-apps/02-configuring-extensions.md b/docs/frontend-system/building-apps/02-configuring-extensions.md index 2fd4ce69fc..57a2110da6 100644 --- a/docs/frontend-system/building-apps/02-configuring-extensions.md +++ b/docs/frontend-system/building-apps/02-configuring-extensions.md @@ -6,4 +6,92 @@ sidebar_label: Configuring Extensions description: Documentation for how to configure extensions in a Backstage app --- -TODO +All extensions in a Backstage app can be configured through static configuration. This configuration is all done under a the `app.extensions` configuration key. For more general information on how to write configuration for Backstage, see the section on [writing configuration](../../conf/writing.md). + +## Extension Configuration Schema + +This section focuses on the format of the `app.extensions` configuration and the various shorthands that are available. + +The most complete and verbose format for configuring an individual extensions is as follows: + +```yaml +app: + extensions: + - : + attachTo: + id: + input: + disabled: + config: +``` + +All of the top-level fields are optional: `attachTo`, `disabled`, and `config`. Every extension implementation must provide defaults for all of these fields that will be used if they are not provided in the configuration. + +Note that `app.extensions` is always an array rather than an object. For example, the following is invalid: + +```yaml title="INVALID" +app: + extensions: + : # Invalid, this should be an array item, `app.extensions` is now an object + config: ... +``` + +In addition to this schema, there are a number of shorthands available: + +Rather than a full object, you can specify just the ID of the extension as a string. This is equivalent to setting `disabled` to `false`: + +```yaml +app: + extensions: + - ‘’ +``` + +You can enable/disable individual extension by ID, in this case the value is a boolean: + +```yaml +extensions: + - : +``` + +You can override the implementation of an extension by ID, in this case the value is a string: + +```yaml +extensions: + - : ‘’ +``` + +You can **create a new extension instance with a generated ID** by including an input name in the key: + +```yaml +extensions: + - /: + extension: + config: +``` + +This syntax is only for use in the app configuration itself, every extension provided by default from a plugin must have an explicit ID. For example, the following two configurations are equivalent, except that the former does not have an explicit instance ID: + +```yaml +extensions: + # Generated ID + - core.router/routes: + extension: '@backstage/plugin-tech-radar#TechRadarPage' + # Explicit ID + - tech-radar.page: + at: core.router/routes + extension: '@backstage/plugin-tech-radar#TechRadarPage' +``` + +Lastly, if you do not need to provide additional configuration, you can combine the key input format with the implementation value format as a shorthand for creating a new extension instance with a generated ID and no configuration: + +```yaml +extensions: + - /: ‘’ +``` + +For example: + +```yaml +extensions: + - core.router/routes: '@backstage/plugin-tech-radar#TechRadarPage' +``` From faf3fdf3ada5f922976baa0bcc82be7d2f69b00e Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Wed, 28 Feb 2024 15:54:41 +0100 Subject: [PATCH 003/234] docs: add more examples to the frontend app migration guide Signed-off-by: Camila Belo --- .../building-apps/08-migrating.md | 116 +++++++++++++++++- 1 file changed, 113 insertions(+), 3 deletions(-) diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 98d8b3bab8..8bcc1f973f 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -117,7 +117,7 @@ You can then also add any additional extensions that you may need to create as p [Utility API](../utility-apis/01-index.md) factories are now installed as extensions instead. Pass the existing factory to `createApiExtension` and install it in the app. For more information, see the section on [configuring Utility APIs](../utility-apis/04-configuring.md). -For example, the following API configuration: +For example, the following apis configuration: ```ts const app = createApp({ @@ -151,15 +151,75 @@ Icons are currently installed through the usual options to `createApp`, but will Plugins are now passed through the `features` options instead. +For example, the following plugins configuration: + +```tsx +import { homePlugin } from '@backstage/plugin-home'; + +createApp({ + // ... + plugins: [homePlugin], + // ... +}); +``` + +Can be converted to the following features configuration: + +```tsx +// plugins are now default exported via alpha subpath +import homePlugin from '@backstage/plugin-home/alpha'; + +createApp({ + // ... + features: [homePlugin], + // ... +}); +``` + +Plugins don't even have to be imported manually after installing their package if [features discovery](../architecture/02-app.md#feature-discovery) is enabled. + +```yaml title="in app-config.yaml" +app: + # Enabling plugin and override features discovery + experimental: 'all' +``` + ### `featureFlags` Declaring features flags in the app is no longer supported, move these declarations to the appropriate plugins instead. +For example, the following app feature flags configuration: + +```tsx +createApp({ + // ... + featureFlags: [ + { + pluginId: '', + name: 'tech-radar', + description: 'Enables the tech radar plugin', + }, + ], + // ... +}); +``` + +Can be converted to the following plugin configuration: + +```tsx +createPlugin({ + id: 'tech-radar', + // ... + featureFlags: [{ name: 'tech-radar' }], + // ... +}); +``` + ### `components` Many app components are now installed as extensions instead using `createComponentExtension`. See the section on [configuring app components](./01-index.md#configure-your-app) for more information. -The `Router` component is now a built-in extension that you can override using `createRouterExtension`. +The `Router` component is now a built-in extension that you can [override](../architecture/05-extension-overrides.md) using `createRouterExtension`. The Sign-in page is now installed as an extension using the `createSignInPageExtension` instead. @@ -277,6 +337,35 @@ const app = createApp({ Translations are now installed as extensions, using `createTranslationExtension`. +For example, the following translations configuration: + +```tsx +import { catalogTranslationRef } from '@backstage/plugin-catalog/alpha'; +createApp({ + // ... + __experimentalTranslations: { + resources: [ + createTranslationMessages({ + ref: catalogTranslationRef, + catalog_page_create_button_title: 'Create Software', + }), + ], + }, + // ... +}); +``` + +Can be converted to the following extension: + +```tsx +createTranslationExtension({ + resource: createTranslationMessages({ + ref: catalogTranslationRef, + catalog_page_create_button_title: 'Create Software', + }), +}); +``` + ## Gradual Migration After updating all `createApp` options as well as using `convertLegacyApp` to use your existing app structure, you should be able to start up the app and see that it still works. If that is not the case, make sure you read any error messages that you may see in the app as they can provide hints on what you need to fix. If you are still stuck, you can check if anyone else ran into the same issue in our [GitHub issues](https://github.com/backstage/backstage/issues), or ask for help in our [community Discord](https://discord.gg/backstage-687207715902193673). @@ -368,7 +457,7 @@ The entity pages are typically defined in `packages/app/src/components/catalog` New apps feature a built-in sidebar extension (`app/nav`) that will render all nav item extensions provided by plugins. This is a placeholder implementation and not intended as a long-term solution. In the future we will aim to provide a more flexible sidebar extension that allows for more customization out of the box. -Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an extension override: +Because the built-in sidebar is quite limited you may want to override the sidebar with your own custom implementation. To do so, use `createExtension` directly and refer to the [original sidebar implementation](https://github.com/backstage/backstage/blob/master/packages/frontend-app-api/src/extensions/AppNav.tsx). The following is an example of how to take your existing sidebar from the `Root` component that you typically find in `packages/app/src/components/Root.tsx`, and use it in an [extension override](../architecture/05-extension-overrides.md): ```tsx const nav = createExtension({ @@ -435,3 +524,24 @@ export default app.createRoot( ``` Any app root wrapper needs to be migrated to be an extension, using `createAppRootWrapperExtension`. Note that if you have multiple wrappers they must be completely independent of each other, i.e. the order in which they the appear in the React tree should not matter. If that is not the case then you should group them into a single wrapper. + +Here is an example converting the `CustomAppBarrier` into extension: + +```tsx +createApp({ + // ... + features: [ + createExtensionOverrides({ + extensions: [ + createAppRootWrapperExtension({ + name: 'CustomAppBarrier', + // Whenever your component uses legacy core packages, wrap it with "compatWrapper" + // e.g. props => compatWrapper() + Component: CustomAppBarrier, + }), + ], + }), + ], + // ... +}); +``` From e2e39faa710b09ed0fa6cfebd32987d1e1b39129 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 1 Mar 2024 11:37:05 +0100 Subject: [PATCH 004/234] docs/frontend-system: removed outdated content from app config section Signed-off-by: Patrik Oldsberg --- .../02-configuring-extensions.md | 48 ++----------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/docs/frontend-system/building-apps/02-configuring-extensions.md b/docs/frontend-system/building-apps/02-configuring-extensions.md index 57a2110da6..5f090e23cd 100644 --- a/docs/frontend-system/building-apps/02-configuring-extensions.md +++ b/docs/frontend-system/building-apps/02-configuring-extensions.md @@ -49,49 +49,7 @@ app: You can enable/disable individual extension by ID, in this case the value is a boolean: ```yaml -extensions: - - : -``` - -You can override the implementation of an extension by ID, in this case the value is a string: - -```yaml -extensions: - - : ‘’ -``` - -You can **create a new extension instance with a generated ID** by including an input name in the key: - -```yaml -extensions: - - /: - extension: - config: -``` - -This syntax is only for use in the app configuration itself, every extension provided by default from a plugin must have an explicit ID. For example, the following two configurations are equivalent, except that the former does not have an explicit instance ID: - -```yaml -extensions: - # Generated ID - - core.router/routes: - extension: '@backstage/plugin-tech-radar#TechRadarPage' - # Explicit ID - - tech-radar.page: - at: core.router/routes - extension: '@backstage/plugin-tech-radar#TechRadarPage' -``` - -Lastly, if you do not need to provide additional configuration, you can combine the key input format with the implementation value format as a shorthand for creating a new extension instance with a generated ID and no configuration: - -```yaml -extensions: - - /: ‘’ -``` - -For example: - -```yaml -extensions: - - core.router/routes: '@backstage/plugin-tech-radar#TechRadarPage' +app: + extensions: + - : ``` From 665d118422bf3d61e453bf566f0a3052187cf134 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 14:05:23 -0500 Subject: [PATCH 005/234] feat(openapi-tooling): add breaking changes checks to the verify command Signed-off-by: aramissennyeydd --- packages/repo-tools/cli-report.md | 3 +- packages/repo-tools/src/commands/index.ts | 6 ++- .../commands/repo/schema/openapi/verify.ts | 37 +++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index 9d37ce7aff..7c7dd445bc 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -168,7 +168,7 @@ Options: -h, --help Commands: - verify [paths...] + verify [options] [paths...] lint [options] [paths...] test [options] [paths...] fuzz [options] @@ -211,6 +211,7 @@ Options: Usage: backstage-repo-tools repo schema openapi verify [options] [paths...] Options: + --from -h, --help ``` diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index d2fe507941..9f0461e7ea 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -96,7 +96,11 @@ function registerRepoCommand(program: Command) { openApiCommand .command('verify [paths...]') .description( - 'Verify that all OpenAPI schemas are valid and have a matching `schemas/openapi.generated.ts` file.', + 'Verify that all OpenAPI schemas are valid and set up correctly. This also verifies that your API has not changed in a breaking way.', + ) + .option( + '--from ', + 'The base ref to compare against. Defaults to the fork point of the current branch.', ) .action( lazy(() => diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts index 442494610c..78b5a966c3 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts @@ -29,8 +29,10 @@ import { YAML_SCHEMA_PATH, } from '../../../../lib/openapi/constants'; import { getPathToOpenApiSpec } from '../../../../lib/openapi/helpers'; +import { exec } from '../../../../lib/exec'; +import { OptionValues } from 'commander'; -async function verify(directoryPath: string) { +async function verify(directoryPath: string, options: OptionValues) { let openapiPath = ''; try { openapiPath = await getPathToOpenApiSpec(directoryPath); @@ -58,10 +60,39 @@ async function verify(directoryPath: string) { `\`${YAML_SCHEMA_PATH}\` and \`${TS_SCHEMA_PATH}\` do not match. Please run \`yarn backstage-repo-tools package schema openapi generate\` from '${path}' to regenerate \`${TS_SCHEMA_PATH}\`.`, ); } + + let baseRef = options.from ?? process.env.GITHUB_BASE_REF; + if (!baseRef) { + const { stdout: branch } = await exec('git merge-base --fork-point HEAD'); + baseRef = branch.toString().trim(); + } + + try { + const { stdout } = await exec('optic diff', [ + openapiPath, + '--check', + '--base', + baseRef, + ]); + // Log out the results as this still shows API changes that aren't breakages. + console.log( + stdout + .toString() + .split('\n') + .filter(e => !e.startsWith('Rerun') && e.trim()) + .join('\n'), + ); + } catch (err) { + err.message = err.stdout; + throw err; + } } -export async function bulkCommand(paths: string[] = []): Promise { - const resultsList = await runner(paths, dir => verify(dir)); +export async function bulkCommand( + paths: string[] = [], + options: OptionValues, +): Promise { + const resultsList = await runner(paths, dir => verify(dir, options)); let failed = false; for (const { relativeDir, resultText } of resultsList) { From 0b3fac608a9cb8052ad6644380add93c8116b72f Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 14:05:30 -0500 Subject: [PATCH 006/234] example breakage Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- plugins/catalog-backend/src/schema/openapi.generated.ts | 2 +- plugins/catalog-backend/src/schema/openapi.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index 550db58f2e..c8c9c73a84 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -1072,7 +1072,7 @@ export const spec = { 'application/json': { schema: { type: 'object', - required: ['entityRefs'], + required: ['entityRefs', 'fields'], properties: { entityRefs: { type: 'array', diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index 75dc1d9bbc..d3a65490bf 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -841,6 +841,7 @@ paths: type: object required: - entityRefs + - fields properties: entityRefs: type: array From 7cd15860dc065afd44d1d56e4eb498f7093b7693 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:13:03 -0500 Subject: [PATCH 007/234] testing using the uffizzi workflow Signed-off-by: aramissennyeydd --- .../api-breaking-changes-comment.yml | 111 ++++++++ .github/workflows/api-breaking-changes.yml | 62 +++++ NOTICE | 1 + packages/repo-tools/package.json | 1 + packages/repo-tools/src/commands/index.ts | 28 +- .../commands/package/schema/openapi/check.ts | 93 +++++++ .../src/commands/repo/schema/openapi/check.ts | 80 ++++++ .../commands/repo/schema/openapi/verify.ts | 56 ++-- .../src/lib/openapi/optic/helpers.ts | 244 ++++++++++++++++++ plugins/catalog-backend/package.json | 1 + yarn.lock | 36 +++ 11 files changed, 670 insertions(+), 43 deletions(-) create mode 100644 .github/workflows/api-breaking-changes-comment.yml create mode 100644 .github/workflows/api-breaking-changes.yml create mode 100644 packages/repo-tools/src/commands/package/schema/openapi/check.ts create mode 100644 packages/repo-tools/src/commands/repo/schema/openapi/check.ts create mode 100644 packages/repo-tools/src/lib/openapi/optic/helpers.ts diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml new file mode 100644 index 0000000000..91741bba5b --- /dev/null +++ b/.github/workflows/api-breaking-changes-comment.yml @@ -0,0 +1,111 @@ +name: API Breaking Changes (comment) + +on: + workflow_run: + workflows: + - 'API Breaking Changes (Trigger)' + types: + - completed + +jobs: + setup: + name: Add values from previous step + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + permissions: + # "If you specify the access for any of these scopes, all of those that are not specified are set to none." + # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions + actions: read # Access cache + outputs: + git-ref: ${{ steps.event.outputs.GIT_REF }} + pr-number: ${{ steps.event.outputs.PR_NUMBER }} + action: ${{ steps.event.outputs.ACTION }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + disable-sudo: true + egress-policy: block + allowed-endpoints: > + api.github.com:443 + + - name: 'Download artifacts' + # Fetch output (zip archive) from the workflow run that triggered this workflow. + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.payload.workflow_run.id, + }); + let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => { + return artifact.name == "preview-spec" + })[0]; + if (matchArtifact === undefined) { + throw TypeError('Build Artifact not found!'); + } + let download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip', + }); + let fs = require('fs'); + fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/preview-spec.zip`, Buffer.from(download.data)); + + - name: 'Accept event from first stage' + run: unzip preview-spec.zip event.json + + - name: Read Event into ENV + id: event + run: | + echo PR_NUMBER=$(jq '.number | tonumber' < event.json) >> $GITHUB_OUTPUT + echo ACTION=$(jq --raw-output '.action | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT + echo GIT_REF=$(jq --raw-output '.pull_request.head.sha | tostring | [scan("\\w+")][0]' < event.json) >> $GITHUB_OUTPUT + + - name: DEBUG - Print Job Outputs + if: ${{ runner.debug }} + run: | + echo "PR number: ${{ steps.event.outputs.PR_NUMBER }}" + echo "Git Ref: ${{ steps.event.outputs.GIT_REF }}" + echo "Action: ${{ steps.event.outputs.ACTION }}" + cat event.json + + - name: Get Comment + id: get-comment + run: | + unzip preview-spec.zip comment.md + ls + echo "MANIFESTS_FILE_HASH=$(md5sum manifests.rendered.yml | awk '{ print $1 }')" >> $GITHUB_OUTPUT + + add-comment: + name: Write comment about issues + needs: + - setup + if: ${{ github.event.workflow_run.conclusion == 'success' }} + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 + + # Identify comment to be updated + - name: Find comment for Ephemeral Environment + uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3 + id: find-comment + with: + issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }} + comment-author: 'github-actions[bot]' + body-includes: pr-changes-${{ needs.cache-manifests-file.outputs.pr-number }} + direction: last + + - name: Create or Update Comment with Deployment URL + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 + with: + comment-id: ${{ steps.notification.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: comment.md + edit-mode: replace diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml new file mode 100644 index 0000000000..ed2dcd9a0e --- /dev/null +++ b/.github/workflows/api-breaking-changes.yml @@ -0,0 +1,62 @@ +name: API Breaking Changes (Trigger) +on: + pull_request: + types: [opened, synchronize, reopened, closed] + paths-ignore: + - '.changeset/**' + - 'contrib/**' + - 'docs/**' + - 'microsite/**' + - 'beps/**' + - 'scripts/**' + - 'storybook/**' + - '**/*.test.*' + - '**/package.json' + - '*.md' + +jobs: + get-backstage-changes: + env: + NODE_OPTIONS: --max-old-space-size=4096 + name: Build PR image + runs-on: ubuntu-latest + if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} + outputs: + tags: ${{ steps.meta.outputs.tags }} + steps: + - name: Harden Runner + uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 + with: + egress-policy: audit + + - name: checkout + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + - name: setup-node + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 + with: + node-version: 18.x + registry-url: https://registry.npmjs.org/ + + - name: yarn install + uses: backstage/actions/yarn-install@a674369920067381b450d398b27df7039b7ef635 # v0.6.5 + with: + cache-prefix: linux-v18 + + - name: breaking changes check + run: | + yarn backstage-repo-tools repo schema openapi check > comment.md + + - name: Upload Rendered Comment as Artifact + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 + with: + name: preview-spec + path: comment.md + retention-days: 2 + + - name: Upload PR Event as Artifact + uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3 + with: + name: preview-spec + path: ${{ github.event_path }} + retention-days: 2 diff --git a/NOTICE b/NOTICE index fb23d28ebc..1f1dfbccbb 100644 --- a/NOTICE +++ b/NOTICE @@ -5,3 +5,4 @@ Portions of this software were developed by third-party software vendors: - Tech Radar Plugin (https://opensource.zalando.com/tech-radar/), Copyright (c) 2017 Zalando SE - [OpenAPI Generator Templates](./packages/repo-tools/templates), Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) Copyright 2018 SmartBear Software +- Optic CLI (https://github.com/opticdev/optic), Copyright 2022, Optic Labs Corporation diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index abf134b5be..126a392265 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -49,6 +49,7 @@ "@stoplight/spectral-rulesets": "^1.18.0", "@stoplight/spectral-runtime": "^1.1.2", "@stoplight/types": "^14.0.0", + "@useoptic/openapi-utilities": "^0.54.8", "chalk": "^4.0.0", "codeowners-utils": "^1.0.2", "command-exists": "^1.2.9", diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 9f0461e7ea..6a557d909e 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -78,6 +78,14 @@ function registerPackageCommand(program: Command) { .action( lazy(() => import('./package/schema/openapi/fuzz').then(m => m.command)), ); + + openApiCommand + .command('check') + .option('--ignore', 'Ignore linting failures and only log the results.') + .option('--json', 'Output the results as JSON') + .action( + lazy(() => import('./package/schema/openapi/check').then(m => m.command)), + ); } function registerRepoCommand(program: Command) { @@ -96,11 +104,7 @@ function registerRepoCommand(program: Command) { openApiCommand .command('verify [paths...]') .description( - 'Verify that all OpenAPI schemas are valid and set up correctly. This also verifies that your API has not changed in a breaking way.', - ) - .option( - '--from ', - 'The base ref to compare against. Defaults to the fork point of the current branch.', + 'Verify that all OpenAPI schemas are valid and set up correctly.', ) .action( lazy(() => @@ -137,6 +141,20 @@ function registerRepoCommand(program: Command) { .action( lazy(() => import('./repo/schema/openapi/fuzz').then(m => m.command)), ); + + openApiCommand + .command('check') + .description( + 'Check the repository against a specific ref, will run all package `check:api` scripts.', + ) + .option( + '--since ', + 'Check the API against a specific ref', + 'origin/master', + ) + .action( + lazy(() => import('./repo/schema/openapi/check').then(m => m.command)), + ); } export function registerCommands(program: Command) { diff --git a/packages/repo-tools/src/commands/package/schema/openapi/check.ts b/packages/repo-tools/src/commands/package/schema/openapi/check.ts new file mode 100644 index 0000000000..22d2d0a0d6 --- /dev/null +++ b/packages/repo-tools/src/commands/package/schema/openapi/check.ts @@ -0,0 +1,93 @@ +/* + * 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 chalk from 'chalk'; +import { exec } from '../../../../lib/exec'; +import { getPathToCurrentOpenApiSpec } from '../../../../lib/openapi/helpers'; +import { paths as cliPaths } from '../../../../lib/paths'; +import { OptionValues } from 'commander'; +import { env } from 'process'; +import { readFile, rm } from 'fs/promises'; +import { resolve } from 'path'; + +const reduceOpticOutput = (output: string) => { + return output + .split('\n') + .filter(e => !e.startsWith('Rerun') && e.trim()) + .join('\n'); +}; + +async function check(opts: OptionValues) { + const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); + + let baseRef = opts.since ?? process.env.GITHUB_BASE_REF; + if (!baseRef) { + const { stdout: branch } = await exec( + 'git merge-base --fork-point origin/master', + ); + baseRef = branch.toString().trim(); + } + + let failed = false; + let output = ''; + try { + const { stdout } = await exec( + 'yarn optic diff', + [ + resolvedOpenapiPath, + '--check', + opts.json ? '--json' : '', + '--base', + baseRef, + ], + { + cwd: cliPaths.targetRoot, + env: { CI: opts.json ? '1' : undefined, ...env }, + }, + ); + output = stdout.toString(); + } catch (err) { + output = err.stdout; + failed = true; + } + + if (opts.json) { + const file = ( + await readFile(resolve(cliPaths.targetRoot, 'ci-run-details.json')) + ).toString(); + const results = JSON.parse(file); + console.log(file); + if (!opts.ignore && results.failed) { + throw new Error('Some checks failed'); + } + + await rm(resolve(cliPaths.targetRoot, 'ci-run-details.json')); + } else { + console.log(reduceOpticOutput(output)); + if (!opts.ignore && failed) { + throw new Error('Some checks failed'); + } + } +} + +export async function command(opts: OptionValues) { + try { + await check(opts); + if (!opts.json) console.log(chalk.green(`All checks passed.`)); + } catch (err) { + if (!opts.json) console.log(chalk.red(err.message)); + process.exit(1); + } +} diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts new file mode 100644 index 0000000000..f77586a840 --- /dev/null +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -0,0 +1,80 @@ +/* + * 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 { PackageGraph } from '@backstage/cli-node'; +import { OptionValues } from 'commander'; +import { exec } from '../../../../lib/exec'; +import { + CiRunDetails, + generateCompareSummaryMarkdown, +} from '../../../../lib/openapi/optic/helpers'; + +export async function command(opts: OptionValues) { + let packages = await PackageGraph.listTargetPackages(); + if (opts.since) { + const graph = PackageGraph.fromPackages(packages); + const changedPackages = await graph.listChangedPackages({ + ref: opts.since, + analyzeLockfile: true, + }); + const withDevDependents = graph.collectPackageNames( + changedPackages.map(pkg => pkg.name), + pkg => pkg.localDevDependents.keys(), + ); + packages = Array.from(withDevDependents).map(name => graph.get(name)!); + } + + const checkablePackages = packages.filter( + e => e.packageJson.scripts?.['check:api'], + ); + try { + const outputs = { + completed: [], + failed: [], + noop: [], + severity: 0, + } as CiRunDetails; + for (const pkg of checkablePackages) { + const { stdout } = await exec( + 'yarn', + ['check:api', '--ignore', '--json'], + { + cwd: pkg.dir, + }, + ); + const result = JSON.parse(stdout.toString()); + outputs.completed.push(...(result.completed ?? [])); + outputs.failed.push(...(result.failed ?? [])); + outputs.noop.push(...(result.noop ?? [])); + } + + const { stdout: currentSha } = await exec('git', ['rev-parse', 'HEAD']); + console.log( + generateCompareSummaryMarkdown( + { sha: currentSha.toString().trim() }, + outputs, + { verbose: true }, + ), + ); + + const failed = outputs.failed.length > 0; + if (failed) { + throw new Error('Some checks failed'); + } + } catch (err) { + console.error(err); + process.exit(1); + } +} diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts index 78b5a966c3..2902d99ea4 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts @@ -29,17 +29,14 @@ import { YAML_SCHEMA_PATH, } from '../../../../lib/openapi/constants'; import { getPathToOpenApiSpec } from '../../../../lib/openapi/helpers'; -import { exec } from '../../../../lib/exec'; -import { OptionValues } from 'commander'; -async function verify(directoryPath: string, options: OptionValues) { - let openapiPath = ''; - try { - openapiPath = await getPathToOpenApiSpec(directoryPath); - } catch { - // Unable to find spec at path. - return; - } +const verifySpecAndGeneratedSpecMatch = async ( + openapiPath: string, + directoryPath: string, +) => { + const openapiTempDirectory = resolvePath(cliPaths.targetDir, '.openapi'); + await fs.mkdirp(openapiTempDirectory); + console.log(openapiTempDirectory); const yaml = YAML.load(await fs.readFile(openapiPath, 'utf8')); await Parser.validate(cloneDeep(yaml) as any); @@ -60,39 +57,22 @@ async function verify(directoryPath: string, options: OptionValues) { `\`${YAML_SCHEMA_PATH}\` and \`${TS_SCHEMA_PATH}\` do not match. Please run \`yarn backstage-repo-tools package schema openapi generate\` from '${path}' to regenerate \`${TS_SCHEMA_PATH}\`.`, ); } +}; - let baseRef = options.from ?? process.env.GITHUB_BASE_REF; - if (!baseRef) { - const { stdout: branch } = await exec('git merge-base --fork-point HEAD'); - baseRef = branch.toString().trim(); - } - +async function verify(directoryPath: string) { + let openapiPath = ''; try { - const { stdout } = await exec('optic diff', [ - openapiPath, - '--check', - '--base', - baseRef, - ]); - // Log out the results as this still shows API changes that aren't breakages. - console.log( - stdout - .toString() - .split('\n') - .filter(e => !e.startsWith('Rerun') && e.trim()) - .join('\n'), - ); - } catch (err) { - err.message = err.stdout; - throw err; + openapiPath = await getPathToOpenApiSpec(directoryPath); + } catch { + // Unable to find spec at path. + return; } + + await verifySpecAndGeneratedSpecMatch(openapiPath, directoryPath); } -export async function bulkCommand( - paths: string[] = [], - options: OptionValues, -): Promise { - const resultsList = await runner(paths, dir => verify(dir, options)); +export async function bulkCommand(paths: string[] = []): Promise { + const resultsList = await runner(paths, dir => verify(dir)); let failed = false; for (const { relativeDir, resultText } of resultsList) { diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts new file mode 100644 index 0000000000..4c50f9bbd9 --- /dev/null +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -0,0 +1,244 @@ +/* + * 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. + */ +/* eslint-disable no-nested-ternary */ + +import { + compareSpecs, + groupDiffsByEndpoint, + Severity, + getOperationsChangedLabel, + getOperationsChanged, +} from '@useoptic/openapi-utilities'; +import { GroupedDiffs } from '@useoptic/openapi-utilities/build/openapi3/group-diff'; +import { relative } from 'path'; +import { paths as cliPaths } from '../../paths'; + +type Comparison = { + groupedDiffs: ReturnType; + results: Awaited>['results']; +}; + +export type CiRunDetails = { + completed: { + warnings: string[]; + apiName: string; + opticWebUrl?: string | null; + comparison: Comparison; + specUrl?: string | null; + capture?: any; + }[]; + failed: { apiName: string; error: string }[]; + noop: { apiName: string }[]; + severity: Severity; +}; + +const getChecksLabel = ( + results: CiRunDetails['completed'][number]['comparison']['results'], + severity: Severity, +) => { + const totalChecks = results.length; + let failingChecks = 0; + let exemptedFailingChecks = 0; + + for (const result of results) { + if (result.passed) continue; + if (result.severity < severity) continue; + if (result.exempted) exemptedFailingChecks += 1; + else failingChecks += 1; + } + + const exemptedChunk = + exemptedFailingChecks > 0 ? `, ${exemptedFailingChecks} exempted` : ''; + + return failingChecks > 0 + ? `⚠️ **${failingChecks}**/**${totalChecks}** failed${exemptedChunk}` + : totalChecks > 0 + ? `✅ **${totalChecks}** passed${exemptedChunk}` + : `ℹ️ No automated checks have run`; +}; + +function getOperationsText( + groupedDiffs: GroupedDiffs, + options: { webUrl?: string | null; verbose: boolean; labelJoiner?: string }, +) { + const ops = getOperationsChanged(groupedDiffs); + + const operationsText = options.verbose + ? [ + ...[...ops.added].map(o => `\`${o}\` (added)`), + ...[...ops.changed].map(o => `\`${o}\` (changed)`), + ...[...ops.removed].map(o => `\`${o}\` (removed)`), + ].join('\n') + : ''; + return `${getOperationsChangedLabel(groupedDiffs, { + joiner: options.labelJoiner, + })} + + ${operationsText} + `; +} + +const getCaptureIssuesLabel = ({ + unmatchedInteractions, + mismatchedEndpoints, +}: { + unmatchedInteractions: number; + mismatchedEndpoints: number; +}) => { + return [ + ...(unmatchedInteractions + ? [ + `🆕 ${unmatchedInteractions} undocumented path${ + unmatchedInteractions > 1 ? 's' : '' + }`, + ] + : []), + ...(mismatchedEndpoints + ? [ + `⚠️ ${mismatchedEndpoints} mismatch${ + mismatchedEndpoints > 1 ? 'es' : '' + }`, + ] + : []), + ].join('\n'); +}; + +export const generateCompareSummaryMarkdown = ( + commit: { sha: string }, + results: CiRunDetails, + options: { verbose: boolean }, +) => { + const anyCompletedHasWarning = results.completed.some( + s => s.warnings.length > 0, + ); + const anyCompletedHasCapture = results.completed.some(s => s.capture); + return ` + ${ + results.completed.length > 0 + ? `### APIs Changed + + + + + + + + ${anyCompletedHasWarning ? '' : ''} + ${anyCompletedHasCapture ? '' : ''} + + + + + ${results.completed + .map( + s => + ` + + + + + ${anyCompletedHasWarning ? `` : ''} + + ${ + anyCompletedHasCapture + ? ` + + + + ` + : '' + } + `, + ) + .join('\n')} + +
APIChangesRulesWarningsTests
+ + ${relative(cliPaths.targetDir, s.apiName)} + + + + ${getOperationsText(s.comparison.groupedDiffs, { + webUrl: s.opticWebUrl, + verbose: options.verbose, + labelJoiner: ',\n', + })} + + + + ${getChecksLabel(s.comparison.results, results.severity)} + + ${s.warnings.join('\n')} + + ${ + s.capture + ? s.capture.success + ? s.capture.mismatchedEndpoints || s.capture.unmatchedInteractions + ? getCaptureIssuesLabel({ + unmatchedInteractions: s.capture.unmatchedInteractions, + mismatchedEndpoints: s.capture.mismatchedEndpoints, + }) + : `✅ ${s.capture.percentCovered}% coverage` + : '❌ Failed to run' + : '' + } + +
+ ` + : '' + } + ${ + results.failed.length > 0 + ? `### Errors running optic + + + + + + + + + + ${results.failed + .map( + s => ` + + + `, + ) + .join('\n')} + +
APIError
${s.apiName} + + ${'```'} + ${s.error} + ${'```'} + +
+ ` + : '' + } + + Summary of API changes for commit (${commit.sha}) + + ${ + results.noop.length > 0 + ? `${ + results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` + } had no changes.` + : '' + }`; +}; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 0a20041ee4..058b9318d7 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -42,6 +42,7 @@ ], "scripts": { "build": "backstage-cli package build", + "check:api": "backstage-repo-tools package schema openapi check", "clean": "backstage-cli package clean", "fuzz": "backstage-repo-tools package schema openapi fuzz --exclude-checks response_schema_conformance", "generate": "backstage-repo-tools package schema openapi generate --server --client-package packages/catalog-client", diff --git a/yarn.lock b/yarn.lock index b7a8b0ce2c..b6a138814b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10108,6 +10108,7 @@ __metadata: "@types/is-glob": ^4.0.2 "@types/node": ^18.17.8 "@types/prettier": ^2.0.0 + "@useoptic/openapi-utilities": ^0.54.8 chalk: ^4.0.0 codeowners-utils: ^1.0.2 command-exists: ^1.2.9 @@ -20292,6 +20293,16 @@ __metadata: languageName: node linkType: hard +"@useoptic/json-pointer-helpers@npm:0.54.8": + version: 0.54.8 + resolution: "@useoptic/json-pointer-helpers@npm:0.54.8" + dependencies: + jsonpointer: ^5.0.1 + minimatch: 9.0.3 + checksum: 4eddabb6dce3ca8160dcd4904299b6964945c3fe47d39bfeca6c68b9a50b058b901a6fb10ab168295475d651df3349149faa5f27f77293e15b6eee8d4417432e + languageName: node + linkType: hard + "@useoptic/openapi-io@npm:0.50.10": version: 0.50.10 resolution: "@useoptic/openapi-io@npm:0.50.10" @@ -20346,6 +20357,31 @@ __metadata: languageName: node linkType: hard +"@useoptic/openapi-utilities@npm:^0.54.8": + version: 0.54.8 + resolution: "@useoptic/openapi-utilities@npm:0.54.8" + dependencies: + "@useoptic/json-pointer-helpers": 0.54.8 + ajv: ^8.6.0 + ajv-errors: ~3.0.0 + ajv-formats: ~2.1.0 + chalk: ^4.1.2 + fast-deep-equal: ^3.1.3 + is-url: ^1.2.4 + js-yaml: ^4.1.0 + json-stable-stringify: ^1.0.1 + lodash.groupby: ^4.6.0 + lodash.isequal: ^4.5.0 + lodash.omit: ^4.5.0 + node-machine-id: ^1.1.12 + openapi-types: ^12.0.2 + ts-invariant: ^0.9.3 + url-join: ^4.0.1 + yaml-ast-parser: ^0.0.43 + checksum: fa9e9f430c77687591aaf8b43b7b31a7c2f80fe9c140aaa978f1948f84d3e974181c91c3d8ec3e06efca9735c7826290baf4be72063bf733887aa632b40c3c4a + languageName: node + linkType: hard + "@useoptic/optic@npm:^0.50.10": version: 0.50.10 resolution: "@useoptic/optic@npm:0.50.10" From 7b06c6c78fbd398b1f70b5986448beb810e7ad3c Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:14:17 -0500 Subject: [PATCH 008/234] add attribution comment as well Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- packages/repo-tools/src/lib/openapi/optic/helpers.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts index 4c50f9bbd9..cd2a1637d4 100644 --- a/packages/repo-tools/src/lib/openapi/optic/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -26,6 +26,11 @@ import { GroupedDiffs } from '@useoptic/openapi-utilities/build/openapi3/group-d import { relative } from 'path'; import { paths as cliPaths } from '../../paths'; +/** + * The below code is copied from https://github.com/opticdev/optic/blob/main/projects/optic/src/commands/ci/comment/common.ts#L82 for use + * with a security flow for forked repositories. + */ + type Comparison = { groupedDiffs: ReturnType; results: Awaited>['results']; From 53c1ec25e221b8d0bd4fb0ded8412f7788938315 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:14:52 -0500 Subject: [PATCH 009/234] update paths requirement Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .github/workflows/api-breaking-changes.yml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index ed2dcd9a0e..c6227792c1 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -2,17 +2,8 @@ name: API Breaking Changes (Trigger) on: pull_request: types: [opened, synchronize, reopened, closed] - paths-ignore: - - '.changeset/**' - - 'contrib/**' - - 'docs/**' - - 'microsite/**' - - 'beps/**' - - 'scripts/**' - - 'storybook/**' - - '**/*.test.*' - - '**/package.json' - - '*.md' + paths: + - '**/openapi.yaml' jobs: get-backstage-changes: From fe4c26532bee4514f4cb171780cb4e9333b4fea9 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:22:00 -0500 Subject: [PATCH 010/234] update git command for GA env Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- packages/repo-tools/src/commands/index.ts | 1 + .../repo-tools/src/commands/package/schema/openapi/check.ts | 2 +- packages/repo-tools/src/commands/repo/schema/openapi/check.ts | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 6a557d909e..3fa16aa512 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -83,6 +83,7 @@ function registerPackageCommand(program: Command) { .command('check') .option('--ignore', 'Ignore linting failures and only log the results.') .option('--json', 'Output the results as JSON') + .option('--since ', 'Check the API against a specific ref') .action( lazy(() => import('./package/schema/openapi/check').then(m => m.command)), ); diff --git a/packages/repo-tools/src/commands/package/schema/openapi/check.ts b/packages/repo-tools/src/commands/package/schema/openapi/check.ts index 22d2d0a0d6..3cb0baf313 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/check.ts @@ -32,7 +32,7 @@ const reduceOpticOutput = (output: string) => { async function check(opts: OptionValues) { const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); - let baseRef = opts.since ?? process.env.GITHUB_BASE_REF; + let baseRef = opts.since; if (!baseRef) { const { stdout: branch } = await exec( 'git merge-base --fork-point origin/master', diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts index f77586a840..41d0bec0e5 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -47,9 +47,10 @@ export async function command(opts: OptionValues) { severity: 0, } as CiRunDetails; for (const pkg of checkablePackages) { + const baseRef = opts.since ?? process.env.GITHUB_BASE_REF; const { stdout } = await exec( 'yarn', - ['check:api', '--ignore', '--json'], + ['check:api', '--ignore', '--json', '--since', baseRef], { cwd: pkg.dir, }, From b0b1371075e58308dcc7aaef45c6aa8809d12163 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:28:04 -0500 Subject: [PATCH 011/234] use base ref instead Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .github/workflows/api-breaking-changes.yml | 2 +- packages/repo-tools/src/commands/repo/schema/openapi/check.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index c6227792c1..fbe4764a9f 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -36,7 +36,7 @@ jobs: - name: breaking changes check run: | - yarn backstage-repo-tools repo schema openapi check > comment.md + yarn backstage-repo-tools repo schema openapi check --since ${{ github.base_ref }} > comment.md - name: Upload Rendered Comment as Artifact uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts index 41d0bec0e5..492dd3b5be 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -47,7 +47,7 @@ export async function command(opts: OptionValues) { severity: 0, } as CiRunDetails; for (const pkg of checkablePackages) { - const baseRef = opts.since ?? process.env.GITHUB_BASE_REF; + const baseRef = opts.since; const { stdout } = await exec( 'yarn', ['check:api', '--ignore', '--json', '--since', baseRef], From d1b44c9a4af18c380aa0d26884209250c25d0952 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:34:22 -0500 Subject: [PATCH 012/234] get the actual sha and run with that Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .../repo-tools/src/commands/repo/schema/openapi/check.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts index 492dd3b5be..5599937322 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -23,7 +23,11 @@ import { export async function command(opts: OptionValues) { let packages = await PackageGraph.listTargetPackages(); + + let since = ''; if (opts.since) { + const { stdout: sinceRaw } = await exec('git', ['rev-parse', opts.since]); + since = sinceRaw.toString().trim(); const graph = PackageGraph.fromPackages(packages); const changedPackages = await graph.listChangedPackages({ ref: opts.since, @@ -47,10 +51,10 @@ export async function command(opts: OptionValues) { severity: 0, } as CiRunDetails; for (const pkg of checkablePackages) { - const baseRef = opts.since; + const sinceCommands = since ? ['--since', since] : []; const { stdout } = await exec( 'yarn', - ['check:api', '--ignore', '--json', '--since', baseRef], + ['check:api', '--ignore', '--json', ...sinceCommands], { cwd: pkg.dir, }, From dce3d7870dd468a4e7e7daede7e915e1429a4e8f Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:38:23 -0500 Subject: [PATCH 013/234] actually check out the needed branches Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .github/workflows/api-breaking-changes.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index fbe4764a9f..406205d578 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -20,8 +20,13 @@ jobs: with: egress-policy: audit - - name: checkout - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + # Fetch the commit that's merged into the base rather than the target ref + # This will let us diff only the contents of the PR, without fetching more history + ref: 'refs/pull/${{ github.event.pull_request.number }}/merge' + - name: fetch base + run: git fetch --depth 1 origin ${{ github.base_ref }} - name: setup-node uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 From e2c9b91fe899feee8308756cafc25af2c18327dd Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:41:06 -0500 Subject: [PATCH 014/234] add origin to base ref Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .github/workflows/api-breaking-changes.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 406205d578..5c75910546 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -41,7 +41,7 @@ jobs: - name: breaking changes check run: | - yarn backstage-repo-tools repo schema openapi check --since ${{ github.base_ref }} > comment.md + yarn backstage-repo-tools repo schema openapi check --since origin/${{ github.base_ref }} > comment.md - name: Upload Rendered Comment as Artifact uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3 From 8f681584e8f4b6cbc91732885f322823c819c6b6 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 16:48:23 -0500 Subject: [PATCH 015/234] update comment to show correctly Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .../src/lib/openapi/optic/helpers.ts | 219 +++++++++--------- 1 file changed, 104 insertions(+), 115 deletions(-) diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts index cd2a1637d4..9bcd8c45a3 100644 --- a/packages/repo-tools/src/lib/openapi/optic/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -131,119 +131,108 @@ export const generateCompareSummaryMarkdown = ( ); const anyCompletedHasCapture = results.completed.some(s => s.capture); return ` - ${ - results.completed.length > 0 - ? `### APIs Changed - - - - - - - - ${anyCompletedHasWarning ? '' : ''} - ${anyCompletedHasCapture ? '' : ''} - - - - - ${results.completed - .map( - s => - ` - - - - - ${anyCompletedHasWarning ? `` : ''} - - ${ - anyCompletedHasCapture - ? ` - - - - ` - : '' - } - `, - ) - .join('\n')} - -
APIChangesRulesWarningsTests
- - ${relative(cliPaths.targetDir, s.apiName)} - - - - ${getOperationsText(s.comparison.groupedDiffs, { - webUrl: s.opticWebUrl, - verbose: options.verbose, - labelJoiner: ',\n', - })} - - - - ${getChecksLabel(s.comparison.results, results.severity)} - - ${s.warnings.join('\n')} - - ${ - s.capture - ? s.capture.success - ? s.capture.mismatchedEndpoints || s.capture.unmatchedInteractions - ? getCaptureIssuesLabel({ - unmatchedInteractions: s.capture.unmatchedInteractions, - mismatchedEndpoints: s.capture.mismatchedEndpoints, - }) - : `✅ ${s.capture.percentCovered}% coverage` - : '❌ Failed to run' - : '' - } - -
- ` - : '' - } - ${ - results.failed.length > 0 - ? `### Errors running optic - - - - - - - - - - ${results.failed - .map( - s => ` - - - `, - ) - .join('\n')} - -
APIError
${s.apiName} - - ${'```'} - ${s.error} - ${'```'} - -
- ` - : '' - } - - Summary of API changes for commit (${commit.sha}) - - ${ - results.noop.length > 0 - ? `${ - results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` - } had no changes.` - : '' - }`; +${ + results.completed.length > 0 + ? `### APIs Changed + + + + + + + +${anyCompletedHasWarning ? '' : ''} +${anyCompletedHasCapture ? '' : ''} + + + +${results.completed + .map( + s => + ` + + + + +${anyCompletedHasWarning ? `` : ''} + +${ + anyCompletedHasCapture + ? ` + +` + : '' +} +`, + ) + .join('\n')} + +
APIChangesRulesWarningsTests
+${relative(cliPaths.targetDir, s.apiName)} + +${getOperationsText(s.comparison.groupedDiffs, { + webUrl: s.opticWebUrl, + verbose: options.verbose, + labelJoiner: ',\n', +})} + +${getChecksLabel(s.comparison.results, results.severity)} +${s.warnings.join('\n')} +${ + s.capture + ? s.capture.success + ? s.capture.mismatchedEndpoints || s.capture.unmatchedInteractions + ? getCaptureIssuesLabel({ + unmatchedInteractions: s.capture.unmatchedInteractions, + mismatchedEndpoints: s.capture.mismatchedEndpoints, + }) + : `✅ ${s.capture.percentCovered}% coverage` + : '❌ Failed to run' + : '' +} +
+` + : '' +} +${ + results.failed.length > 0 + ? `### Errors running optic + + + + + + + + + +${results.failed + .map( + s => ` + + +`, + ) + .join('\n')} + +
APIError
${s.apiName} + +${'```'} +${s.error} +${'```'} + +
+` + : '' +} + +Summary of API changes for commit (${commit.sha}) + +${ + results.noop.length > 0 + ? `${ + results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` + } had no changes.` + : '' +}`; }; From 351ae33e34b8f160d020004548291e1549a72a66 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 18:30:11 -0500 Subject: [PATCH 016/234] update workflow with improved comment structure Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .../api-breaking-changes-comment.yml | 12 +- .github/workflows/api-breaking-changes.yml | 2 - .../src/commands/repo/schema/openapi/check.ts | 62 ++++++++-- .../src/lib/openapi/optic/helpers.ts | 112 +++++++++++++++--- .../src/schema/openapi.generated.ts | 2 +- 5 files changed, 157 insertions(+), 33 deletions(-) diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml index 91741bba5b..4c7fa452e7 100644 --- a/.github/workflows/api-breaking-changes-comment.yml +++ b/.github/workflows/api-breaking-changes-comment.yml @@ -77,7 +77,7 @@ jobs: run: | unzip preview-spec.zip comment.md ls - echo "MANIFESTS_FILE_HASH=$(md5sum manifests.rendered.yml | awk '{ print $1 }')" >> $GITHUB_OUTPUT + grep add-comment: name: Write comment about issues @@ -93,19 +93,19 @@ jobs: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 # Identify comment to be updated - - name: Find comment for Ephemeral Environment + - name: Find comment for API Changes uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3 id: find-comment with: - issue-number: ${{ needs.cache-manifests-file.outputs.pr-number }} + issue-number: ${{ needs.setup.outputs.pr-number }} comment-author: 'github-actions[bot]' - body-includes: pr-changes-${{ needs.cache-manifests-file.outputs.pr-number }} + body-includes: API changes direction: last - - name: Create or Update Comment with Deployment URL + - name: Create or Update Comment with API Changes uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4 with: - comment-id: ${{ steps.notification.outputs.comment-id }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} issue-number: ${{ github.event.pull_request.number }} body-path: comment.md edit-mode: replace diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 5c75910546..07206718bb 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -12,8 +12,6 @@ jobs: name: Build PR image runs-on: ubuntu-latest if: ${{ github.event_name != 'pull_request' || github.event.action != 'closed' }} - outputs: - tags: ${{ steps.meta.outputs.tags }} steps: - name: Harden Runner uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts index 5599937322..d434e0cd4d 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -20,6 +20,8 @@ import { CiRunDetails, generateCompareSummaryMarkdown, } from '../../../../lib/openapi/optic/helpers'; +import { paths as cliPaths } from '../../../../lib/paths'; +import { YAML_SCHEMA_PATH } from '../../../../lib/openapi/constants'; export async function command(opts: OptionValues) { let packages = await PackageGraph.listTargetPackages(); @@ -28,26 +30,34 @@ export async function command(opts: OptionValues) { if (opts.since) { const { stdout: sinceRaw } = await exec('git', ['rev-parse', opts.since]); since = sinceRaw.toString().trim(); - const graph = PackageGraph.fromPackages(packages); - const changedPackages = await graph.listChangedPackages({ - ref: opts.since, - analyzeLockfile: true, - }); - const withDevDependents = graph.collectPackageNames( - changedPackages.map(pkg => pkg.name), - pkg => pkg.localDevDependents.keys(), + const { stdout: changedFilesRaw } = await exec('git', [ + 'diff', + '--name-only', + since, + ]); + const changedFiles = changedFilesRaw.toString().trim(); + + const changedOpenApiSpecs = changedFiles + .split('\n') + .filter(e => e.endsWith(YAML_SCHEMA_PATH)) + .map(e => cliPaths.resolveTarget(e)); + + // filter packages by changedFiles + packages = packages.filter(pkg => + changedOpenApiSpecs.some(e => e.startsWith(`${pkg.dir}/`)), ); - packages = Array.from(withDevDependents).map(name => graph.get(name)!); } const checkablePackages = packages.filter( e => e.packageJson.scripts?.['check:api'], ); + try { const outputs = { completed: [], failed: [], noop: [], + warning: [], severity: 0, } as CiRunDetails; for (const pkg of checkablePackages) { @@ -65,6 +75,40 @@ export async function command(opts: OptionValues) { outputs.noop.push(...(result.noop ?? [])); } + for (const pkg of packages.filter( + e => !e.packageJson.scripts?.['check:api'], + )) { + outputs.warning?.push({ + apiName: `${pkg.dir}/`, + warning: 'No check:api script found in package.json', + }); + } + + outputs.completed.forEach( + e => + (e.apiName = e.apiName + .replace(cliPaths.targetDir, '') + .replace(YAML_SCHEMA_PATH, '')), + ); + outputs.failed.forEach( + e => + (e.apiName = e.apiName + .replace(cliPaths.targetDir, '') + .replace(YAML_SCHEMA_PATH, '')), + ); + outputs.noop.forEach( + e => + (e.apiName = e.apiName + .replace(cliPaths.targetDir, '') + .replace(YAML_SCHEMA_PATH, '')), + ); + outputs.warning?.forEach( + e => + (e.apiName = e.apiName + .replace(cliPaths.targetDir, '') + .replace(YAML_SCHEMA_PATH, '')), + ); + const { stdout: currentSha } = await exec('git', ['rev-parse', 'HEAD']); console.log( generateCompareSummaryMarkdown( diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts index 9bcd8c45a3..2393e71ce8 100644 --- a/packages/repo-tools/src/lib/openapi/optic/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -23,8 +23,6 @@ import { getOperationsChanged, } from '@useoptic/openapi-utilities'; import { GroupedDiffs } from '@useoptic/openapi-utilities/build/openapi3/group-diff'; -import { relative } from 'path'; -import { paths as cliPaths } from '../../paths'; /** * The below code is copied from https://github.com/opticdev/optic/blob/main/projects/optic/src/commands/ci/comment/common.ts#L82 for use @@ -45,6 +43,7 @@ export type CiRunDetails = { specUrl?: string | null; capture?: any; }[]; + warning?: { apiName: string; warning: string }[]; failed: { apiName: string; error: string }[]; noop: { apiName: string }[]; severity: Severity; @@ -121,6 +120,18 @@ const getCaptureIssuesLabel = ({ ].join('\n'); }; +const getBreakagesRow = (breakage: CiRunDetails['completed'][number]) => { + return ` + - ${breakage.apiName} + ${breakage.comparison.results.map( + s => ` + - ${s.where} + ${'```'} + ${s.error} + ${'```'}`, + )}`; +}; + export const generateCompareSummaryMarkdown = ( commit: { sha: string }, results: CiRunDetails, @@ -130,10 +141,61 @@ export const generateCompareSummaryMarkdown = ( s => s.warnings.length > 0, ); const anyCompletedHasCapture = results.completed.some(s => s.capture); - return ` + if ( + results.completed.length === 0 && + results.failed.length === 0 && + results.failed.length === 0 + ) { + return `No API changes detected for commit (${commit.sha})`; + } + const breakages = results.completed + .filter(s => s.comparison.results.some(e => !e.passed)) + .map(e => ({ + ...e, + comparison: { + ...e.comparison, + results: e.comparison.results.filter(f => !f.passed), + }, + })); + const successfullyCompletedCount = + results.completed.length - breakages.length; + return `### Summary for commit (${commit.sha}) + +${ + results.noop.length > 0 + ? `${ + results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` + } had no changes.` + : '' +} +${ + breakages.length > 0 + ? `${ + breakages.length === 1 ? '1 API' : `${breakages.length} APIs` + } had breaking changes.` + : '' +} +${ + successfullyCompletedCount > 0 + ? `${ + successfullyCompletedCount === 1 + ? '1 API' + : `${successfullyCompletedCount} APIs` + } had non-breaking changes.` + : '' +} +${ + results.warning && results.warning.length > 0 + ? `${ + results.warning.length === 1 + ? '1 API' + : `${results.warning.length} APIs` + } had warnings.` + : '' +} ${ results.completed.length > 0 - ? `### APIs Changed + ? `### APIs with Changes @@ -151,7 +213,7 @@ ${results.completed s => `
-${relative(cliPaths.targetDir, s.apiName)} +${s.apiName} ${getOperationsText(s.comparison.groupedDiffs, { @@ -190,13 +252,13 @@ ${ ) .join('\n')} -
-` +` : '' } + ${ results.failed.length > 0 - ? `### Errors running optic + ? `### APIs with Errors @@ -226,13 +288,33 @@ ${'```'} : '' } -Summary of API changes for commit (${commit.sha}) - ${ - results.noop.length > 0 - ? `${ - results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` - } had no changes.` + results.warning && results.warning.length + ? ` +### APIs with Warnings +
+ + + + + + + + ${results.warning + .map(e => ``) + .join('\n')} + +
APIWarning
${e.apiName}${e.warning}
` : '' -}`; +} +${ + breakages.length > 0 + ? ` +### Routes with Breakages + +${breakages.map(getBreakagesRow).join('\n')} +` + : '' +} +`; }; diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index c8c9c73a84..550db58f2e 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -1072,7 +1072,7 @@ export const spec = { 'application/json': { schema: { type: 'object', - required: ['entityRefs', 'fields'], + required: ['entityRefs'], properties: { entityRefs: { type: 'array', From 683870a29ada01c4971a5a6efa4ac3c3fe330bc6 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 18:35:09 -0500 Subject: [PATCH 017/234] add changeset Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .changeset/flat-countries-clap.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/flat-countries-clap.md diff --git a/.changeset/flat-countries-clap.md b/.changeset/flat-countries-clap.md new file mode 100644 index 0000000000..ccd779a2b5 --- /dev/null +++ b/.changeset/flat-countries-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': minor +--- + +Adds 2 new commands `repo schema openapi check` and `package schema openapi check`. `repo schema openapi check` is intended to power a new breaking changes check on pull requests and the package level command allows plugin developers to quickly see new API breaking changes.They're intended to be used in complement with the existing `repo schema openapi verify` command to validate your OpenAPI spec against a variety of things. From d3d227d44fba792da9cdce1b184e86b137fb45dd Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 18:36:14 -0500 Subject: [PATCH 018/234] revert catalog changes Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- plugins/catalog-backend/src/schema/openapi.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index d3a65490bf..75dc1d9bbc 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -841,7 +841,6 @@ paths: type: object required: - entityRefs - - fields properties: entityRefs: type: array From 9341347dc129e53a343ebcef742a583568dec45c Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 18:36:33 -0500 Subject: [PATCH 019/234] revert prettier change Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- plugins/catalog-backend/src/schema/openapi.generated.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index 550db58f2e..823ac40a14 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. From f4aebb8e88012d1f9f80f63962b5a7b2ad866971 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 2 Mar 2024 18:44:06 -0500 Subject: [PATCH 020/234] small refactoring Signed-off-by: aramissennyeydd Signed-off-by: web-next-automation --- .../src/commands/repo/schema/openapi/check.ts | 34 ++++--------- .../src/lib/openapi/optic/helpers.ts | 48 +++++++------------ 2 files changed, 26 insertions(+), 56 deletions(-) diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts index d434e0cd4d..95bd4dffed 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/check.ts @@ -23,6 +23,12 @@ import { import { paths as cliPaths } from '../../../../lib/paths'; import { YAML_SCHEMA_PATH } from '../../../../lib/openapi/constants'; +function cleanUpApiName(e: { apiName: string }) { + e.apiName = e.apiName + .replace(cliPaths.targetDir, '') + .replace(YAML_SCHEMA_PATH, ''); +} + export async function command(opts: OptionValues) { let packages = await PackageGraph.listTargetPackages(); @@ -84,30 +90,10 @@ export async function command(opts: OptionValues) { }); } - outputs.completed.forEach( - e => - (e.apiName = e.apiName - .replace(cliPaths.targetDir, '') - .replace(YAML_SCHEMA_PATH, '')), - ); - outputs.failed.forEach( - e => - (e.apiName = e.apiName - .replace(cliPaths.targetDir, '') - .replace(YAML_SCHEMA_PATH, '')), - ); - outputs.noop.forEach( - e => - (e.apiName = e.apiName - .replace(cliPaths.targetDir, '') - .replace(YAML_SCHEMA_PATH, '')), - ); - outputs.warning?.forEach( - e => - (e.apiName = e.apiName - .replace(cliPaths.targetDir, '') - .replace(YAML_SCHEMA_PATH, '')), - ); + outputs.completed.forEach(cleanUpApiName); + outputs.failed.forEach(cleanUpApiName); + outputs.noop.forEach(cleanUpApiName); + outputs.warning?.forEach(cleanUpApiName); const { stdout: currentSha } = await exec('git', ['rev-parse', 'HEAD']); console.log( diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts index 2393e71ce8..df8ecfc723 100644 --- a/packages/repo-tools/src/lib/openapi/optic/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -132,6 +132,14 @@ const getBreakagesRow = (breakage: CiRunDetails['completed'][number]) => { )}`; }; +const addSummaryLine = (items: any[] | number | undefined, label: string) => { + const length = Array.isArray(items) ? items.length : items; + if (!length) return ''; + let text = length === 1 ? `1 API` : `${length} APIs`; + text += ` had ${label}`; + return text; +}; + export const generateCompareSummaryMarkdown = ( commit: { sha: string }, results: CiRunDetails, @@ -161,38 +169,14 @@ export const generateCompareSummaryMarkdown = ( results.completed.length - breakages.length; return `### Summary for commit (${commit.sha}) -${ - results.noop.length > 0 - ? `${ - results.noop.length === 1 ? '1 API' : `${results.noop.length} APIs` - } had no changes.` - : '' -} -${ - breakages.length > 0 - ? `${ - breakages.length === 1 ? '1 API' : `${breakages.length} APIs` - } had breaking changes.` - : '' -} -${ - successfullyCompletedCount > 0 - ? `${ - successfullyCompletedCount === 1 - ? '1 API' - : `${successfullyCompletedCount} APIs` - } had non-breaking changes.` - : '' -} -${ - results.warning && results.warning.length > 0 - ? `${ - results.warning.length === 1 - ? '1 API' - : `${results.warning.length} APIs` - } had warnings.` - : '' -} +${addSummaryLine(results.noop, 'no changes')} + +${addSummaryLine(breakages.length, 'breaking changes')} + +${addSummaryLine(successfullyCompletedCount, 'non-breaking changes')} + +${addSummaryLine(results.warning, 'warnings')} + ${ results.completed.length > 0 ? `### APIs with Changes From 61a9e8801f20671e08b9cf64dad2286611a42cbc Mon Sep 17 00:00:00 2001 From: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Date: Mon, 1 Apr 2024 11:13:19 -0400 Subject: [PATCH 021/234] add changeset Signed-off-by: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> --- packages/repo-tools/cli-report.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index 7c7dd445bc..65c0f50163 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -168,7 +168,7 @@ Options: -h, --help Commands: - verify [options] [paths...] + verify [paths...] lint [options] [paths...] test [options] [paths...] fuzz [options] @@ -179,6 +179,14 @@ Commands: ``` Usage: backstage-repo-tools repo schema openapi fuzz [options] + check [options] + help [command] +``` + +### `backstage-repo-tools repo schema openapi check` + +``` +Usage: backstage-repo-tools repo schema openapi check [options] Options: --since @@ -211,7 +219,6 @@ Options: Usage: backstage-repo-tools repo schema openapi verify [options] [paths...] Options: - --from -h, --help ``` From 5d091a6405dc6bca517f49c2e889e46f256751fd Mon Sep 17 00:00:00 2001 From: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Date: Mon, 1 Apr 2024 19:46:05 -0400 Subject: [PATCH 022/234] revert verify changes Signed-off-by: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Signed-off-by: web-next-automation --- .../commands/repo/schema/openapi/verify.ts | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts index 2902d99ea4..23e07d1230 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/verify.ts @@ -30,14 +30,14 @@ import { } from '../../../../lib/openapi/constants'; import { getPathToOpenApiSpec } from '../../../../lib/openapi/helpers'; -const verifySpecAndGeneratedSpecMatch = async ( - openapiPath: string, - directoryPath: string, -) => { - const openapiTempDirectory = resolvePath(cliPaths.targetDir, '.openapi'); - await fs.mkdirp(openapiTempDirectory); - console.log(openapiTempDirectory); - +async function verify(directoryPath: string) { + let openapiPath = ''; + try { + openapiPath = await getPathToOpenApiSpec(directoryPath); + } catch { + // Unable to find spec at path. + return; + } const yaml = YAML.load(await fs.readFile(openapiPath, 'utf8')); await Parser.validate(cloneDeep(yaml) as any); @@ -57,18 +57,6 @@ const verifySpecAndGeneratedSpecMatch = async ( `\`${YAML_SCHEMA_PATH}\` and \`${TS_SCHEMA_PATH}\` do not match. Please run \`yarn backstage-repo-tools package schema openapi generate\` from '${path}' to regenerate \`${TS_SCHEMA_PATH}\`.`, ); } -}; - -async function verify(directoryPath: string) { - let openapiPath = ''; - try { - openapiPath = await getPathToOpenApiSpec(directoryPath); - } catch { - // Unable to find spec at path. - return; - } - - await verifySpecAndGeneratedSpecMatch(openapiPath, directoryPath); } export async function bulkCommand(paths: string[] = []): Promise { From 490d96829b582c0a707a20d81b8167d94fda5e0d Mon Sep 17 00:00:00 2001 From: web-next-automation Date: Mon, 8 Apr 2024 21:01:31 -0400 Subject: [PATCH 023/234] fix merge issue Signed-off-by: web-next-automation --- packages/repo-tools/src/lib/openapi/optic/helpers.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/repo-tools/src/lib/openapi/optic/helpers.ts b/packages/repo-tools/src/lib/openapi/optic/helpers.ts index df8ecfc723..9f5deb72e0 100644 --- a/packages/repo-tools/src/lib/openapi/optic/helpers.ts +++ b/packages/repo-tools/src/lib/openapi/optic/helpers.ts @@ -87,9 +87,7 @@ function getOperationsText( ...[...ops.removed].map(o => `\`${o}\` (removed)`), ].join('\n') : ''; - return `${getOperationsChangedLabel(groupedDiffs, { - joiner: options.labelJoiner, - })} + return `${getOperationsChangedLabel(groupedDiffs)} ${operationsText} `; From 5826d70b544472d394b72bc2162a5fbbe8821473 Mon Sep 17 00:00:00 2001 From: web-next-automation Date: Mon, 8 Apr 2024 21:08:15 -0400 Subject: [PATCH 024/234] add cli report Signed-off-by: web-next-automation --- packages/repo-tools/cli-report.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index 65c0f50163..68d679fb06 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -172,13 +172,6 @@ Commands: lint [options] [paths...] test [options] [paths...] fuzz [options] - help [command] -``` - -### `backstage-repo-tools repo schema openapi fuzz` - -``` -Usage: backstage-repo-tools repo schema openapi fuzz [options] check [options] help [command] ``` @@ -193,6 +186,16 @@ Options: -h, --help ``` +### `backstage-repo-tools repo schema openapi fuzz` + +``` +Usage: backstage-repo-tools repo schema openapi fuzz [options] + +Options: + --since + -h, --help +``` + ### `backstage-repo-tools repo schema openapi lint` ``` From fd84ca431702a0faa267b475a9a81b8c7d4b4021 Mon Sep 17 00:00:00 2001 From: web-next-automation Date: Mon, 8 Apr 2024 21:09:13 -0400 Subject: [PATCH 025/234] revert Signed-off-by: web-next-automation --- plugins/catalog-backend/src/schema/openapi.generated.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index 823ac40a14..550db58f2e 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -1,5 +1,5 @@ /* - * Copyright 2023 The Backstage Authors + * 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. From 0501243a575569e894c8e29949f91b6779fd8259 Mon Sep 17 00:00:00 2001 From: JeevaRamanathan Date: Tue, 9 Apr 2024 21:49:45 +0530 Subject: [PATCH 026/234] Enhance Accessibility: Add ARIA Attributes to SearchModal Component Signed-off-by: JeevaRamanathan --- .changeset/thirty-mangos-travel.md | 5 +++++ plugins/search/src/components/SearchModal/SearchModal.tsx | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changeset/thirty-mangos-travel.md diff --git a/.changeset/thirty-mangos-travel.md b/.changeset/thirty-mangos-travel.md new file mode 100644 index 0000000000..2281cc06b6 --- /dev/null +++ b/.changeset/thirty-mangos-travel.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search': patch +--- + +Added `aria-label` attribute to DialogTitle element and set `aria-modal` attribute to `true` for improved accessibility in the search modal. diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index 814779fb45..07eec4438e 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -190,7 +190,8 @@ export const SearchModal = (props: SearchModalProps) => { paperFullWidth: classes.paperFullWidth, }} onClose={toggleModal} - aria-labelledby="search-modal-title" + aria-label="Search Modal" + aria-modal="true" fullWidth maxWidth="lg" open={open} From fade5e020fe1a3e114262e6bf719753a9a58c31d Mon Sep 17 00:00:00 2001 From: CiscoRob <133238823+CiscoRob@users.noreply.github.com> Date: Sat, 6 Apr 2024 14:38:20 -0500 Subject: [PATCH 027/234] Update CatalogTable to default total count to 0 instead of undefined Depending on latency in making the request the context can load without items and then refresh moments later with the correct count. Until the entities are loaded the total will show as "All (undefined)", which is not confidence inspiring. Signed-off-by: CiscoRob <133238823+CiscoRob@users.noreply.github.com> Signed-off-by: Coderrob --- .../src/components/CatalogTable/CatalogTable.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 674271a044..ba52b7d3a0 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -88,10 +88,15 @@ export const CatalogTable = (props: CatalogTableProps) => { } = props; const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); const entityListContext = useEntityList(); - const { loading, error, entities, filters, pageInfo, totalItems } = - entityListContext; + const { + loading, + error, + entities, + filters, + pageInfo, + totalItems = 0, + } = entityListContext; const enablePagination = !!pageInfo; - const tableColumns = useMemo( () => typeof columns === 'function' ? columns(entityListContext) : columns, From 411853058ffd516f113f84daf9a467152dea4c4f Mon Sep 17 00:00:00 2001 From: Coderrob Date: Tue, 9 Apr 2024 15:46:11 -0500 Subject: [PATCH 028/234] Add changeset per contributor guide Change display to avoid displaying counts when not loaded yet Signed-off-by: Coderrob --- .changeset/swift-humans-hunt.md | 5 ++++ .../CatalogTable/CatalogTable.test.tsx | 4 ++-- .../components/CatalogTable/CatalogTable.tsx | 23 ++++++++++--------- 3 files changed, 19 insertions(+), 13 deletions(-) create mode 100644 .changeset/swift-humans-hunt.md diff --git a/.changeset/swift-humans-hunt.md b/.changeset/swift-humans-hunt.md new file mode 100644 index 0000000000..b96b3e41bc --- /dev/null +++ b/.changeset/swift-humans-hunt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Avoiding pre-loading display total count undefined for table counts diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx index 052d8f4787..35639069df 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx @@ -343,7 +343,7 @@ describe('CatalogTable component', () => { expect(screen.getByText('Should be rendered')).toBeInTheDocument(); }); - it('should render the label column with customised title and value as specified', async () => { + it('should render the label column with customized title and value as specified', async () => { const columns = [ CatalogTable.columns.createNameColumn({ defaultKind: 'API' }), CatalogTable.columns.createLabelColumn('category', { title: 'Category' }), @@ -381,7 +381,7 @@ describe('CatalogTable component', () => { expect(labelCellValue).toBeInTheDocument(); }); - it('should render the label column with customised title and value as specified using function', async () => { + it('should render the label column with customized title and value as specified using function', async () => { const columns: CatalogTableColumnsFunc = ({ filters, entities: entities1, diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index ba52b7d3a0..08da250051 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -88,14 +88,8 @@ export const CatalogTable = (props: CatalogTableProps) => { } = props; const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); const entityListContext = useEntityList(); - const { - loading, - error, - entities, - filters, - pageInfo, - totalItems = 0, - } = entityListContext; + const { loading, error, entities, filters, pageInfo, totalItems } = + entityListContext; const enablePagination = !!pageInfo; const tableColumns = useMemo( () => @@ -175,13 +169,20 @@ export const CatalogTable = (props: CatalogTableProps) => { const currentKind = filters.kind?.value || ''; const currentType = filters.type?.value || ''; + const currentCount = Number.isSafeInteger(totalItems) + ? `(${totalItems})` + : ''; // TODO(timbonicus): remove the title from the CatalogTable once using EntitySearchBar const titlePreamble = capitalize(filters.user?.value ?? 'all'); - const titleDisplay = [titlePreamble, currentType, pluralize(currentKind)] + const title = [ + titlePreamble, + currentType, + pluralize(currentKind), + currentCount, + ] .filter(s => s) .join(' '); - const title = `${titleDisplay} (${totalItems})`; const actions = props.actions || defaultActions; const options = { actionsColumnIndex: -1, @@ -197,7 +198,7 @@ export const CatalogTable = (props: CatalogTableProps) => { columns={tableColumns} emptyContent={emptyContent} isLoading={loading} - title={titleDisplay} + title={title} actions={actions} subtitle={subtitle} options={options} From 71089cf5325277fecbf7e9f6bf2c0c76d78970ec Mon Sep 17 00:00:00 2001 From: Edoardo Pessina Date: Fri, 12 Apr 2024 15:55:39 +0200 Subject: [PATCH 029/234] Added plugin yaml file Signed-off-by: Edoardo Pessina --- microsite/data/plugins/mia-platform.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 microsite/data/plugins/mia-platform.yaml diff --git a/microsite/data/plugins/mia-platform.yaml b/microsite/data/plugins/mia-platform.yaml new file mode 100644 index 0000000000..d4a7b692cd --- /dev/null +++ b/microsite/data/plugins/mia-platform.yaml @@ -0,0 +1,13 @@ +--- +title: Mia Platform Sync +author: Mia-Platform +authorUrl: https://mia-platform.eu +category: Discovery +description: Elevate your workflow with our plugin that seamlessly integrates your Mia-Platform components into Backstage. Keep your information synchronized effortlessly and gain insights into your company's domains, projects, deployment environments, and repository links, all while exploring their interrelations. +documentation: https://github.com/mia-platform/backstage-plugin/blob/main/README.md +iconUrl: /img/mia-platform.png +npmPackageName: '@mia-platform/backstage-plugin-frontend' +tags: + - platform + - discovery +addedDate: '2024-04-12' From c79dded9657fb2eaa2f57b22c32723be2831a2c0 Mon Sep 17 00:00:00 2001 From: Edoardo Pessina Date: Fri, 12 Apr 2024 15:56:12 +0200 Subject: [PATCH 030/234] Added plugin image Signed-off-by: Edoardo Pessina --- microsite/static/img/mia-platform.png | Bin 0 -> 6118 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 microsite/static/img/mia-platform.png diff --git a/microsite/static/img/mia-platform.png b/microsite/static/img/mia-platform.png new file mode 100644 index 0000000000000000000000000000000000000000..9ee5fa96b75b5800eb853a89260f36343e61c261 GIT binary patch literal 6118 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 Date: Tue, 6 Feb 2024 15:21:22 +0200 Subject: [PATCH 031/234] feat: allow overriding default ownership resolving This allows to modify the ownership resolving in the auth resolve context. For example if user wants to include parent groups also to the ownershipEntityRefs, it's not possible unless the built-in auth providers are forked and rewritten. Signed-off-by: Heikki Hellgren --- .changeset/selfish-pigs-glow.md | 6 +++ plugins/auth-backend/api-report.md | 12 ++++-- plugins/auth-backend/src/index.ts | 2 +- .../resolvers/CatalogAuthResolverContext.ts | 30 ++++--------- .../resolvers/DefaultAuthOwnershipResolver.ts | 43 +++++++++++++++++++ .../auth-backend/src/lib/resolvers/index.ts | 6 +-- plugins/auth-backend/src/providers/router.ts | 11 ++++- plugins/auth-backend/src/service/router.ts | 8 ++-- plugins/auth-node/api-report.md | 6 +++ plugins/auth-node/src/index.ts | 1 + plugins/auth-node/src/types.ts | 9 ++++ 11 files changed, 99 insertions(+), 35 deletions(-) create mode 100644 .changeset/selfish-pigs-glow.md create mode 100644 plugins/auth-backend/src/lib/resolvers/DefaultAuthOwnershipResolver.ts diff --git a/.changeset/selfish-pigs-glow.md b/.changeset/selfish-pigs-glow.md new file mode 100644 index 0000000000..ca29a22feb --- /dev/null +++ b/.changeset/selfish-pigs-glow.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-auth-backend': patch +'@backstage/plugin-auth-node': patch +--- + +Allow overriding default ownership resolving diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 1beddb6419..0f537d3dc2 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AuthOwnershipResolver } from '@backstage/plugin-auth-node'; import { AuthProviderConfig as AuthProviderConfig_2 } from '@backstage/plugin-auth-node'; import { AuthProviderFactory as AuthProviderFactory_2 } from '@backstage/plugin-auth-node'; import { AuthProviderRouteHandlers as AuthProviderRouteHandlers_2 } from '@backstage/plugin-auth-node'; @@ -193,6 +194,12 @@ export function createOriginFilter(config: Config): (origin: string) => boolean; // @public (undocumented) export function createRouter(options: RouterOptions): Promise; +// @public +export class DefaultAuthOwnershipResolver implements AuthOwnershipResolver { + // (undocumented) + getOwnershipEntityRefs(entity: Entity): Promise; +} + // @public export const defaultAuthProviderFactories: { [providerId: string]: AuthProviderFactory_2; @@ -216,9 +223,6 @@ export type GcpIapResult = GcpIapResult_2; // @public @deprecated export type GcpIapTokenInfo = GcpIapTokenInfo_2; -// @public -export function getDefaultOwnershipEntityRefs(entity: Entity): string[]; - // @public (undocumented) export type GithubOAuthResult = { fullProfile: Profile; @@ -668,6 +672,8 @@ export interface RouterOptions { // (undocumented) logger: LoggerService; // (undocumented) + ownershipResolver?: AuthOwnershipResolver; + // (undocumented) providerFactories?: ProviderFactories; // (undocumented) tokenFactoryAlgorithm?: string; diff --git a/plugins/auth-backend/src/index.ts b/plugins/auth-backend/src/index.ts index 6c3f866031..d3688934c5 100644 --- a/plugins/auth-backend/src/index.ts +++ b/plugins/auth-backend/src/index.ts @@ -34,4 +34,4 @@ export * from './lib/oauth'; export * from './lib/catalog'; -export { getDefaultOwnershipEntityRefs } from './lib/resolvers'; +export { DefaultAuthOwnershipResolver } from './lib/resolvers'; diff --git a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts index 4f9673ac79..be0cd0da97 100644 --- a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts +++ b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts @@ -20,7 +20,6 @@ import { DEFAULT_NAMESPACE, Entity, parseEntityRef, - RELATION_MEMBER_OF, stringifyEntityRef, } from '@backstage/catalog-model'; import { ConflictError, InputError, NotFoundError } from '@backstage/errors'; @@ -31,31 +30,13 @@ import { LoggerService, } from '@backstage/backend-plugin-api'; import { TokenIssuer } from '../../identity/types'; -import { CatalogIdentityClient } from '../catalog'; import { + AuthOwnershipResolver, AuthResolverCatalogUserQuery, AuthResolverContext, TokenParams, } from '@backstage/plugin-auth-node'; - -/** - * Uses the default ownership resolution logic to return an array - * of entity refs that the provided entity claims ownership through. - * - * A reference to the entity itself will also be included in the returned array. - * - * @public - */ -export function getDefaultOwnershipEntityRefs(entity: Entity) { - const membershipRefs = - entity.relations - ?.filter( - r => r.type === RELATION_MEMBER_OF && r.targetRef.startsWith('group:'), - ) - .map(r => r.targetRef) ?? []; - - return Array.from(new Set([stringifyEntityRef(entity), ...membershipRefs])); -} +import { CatalogIdentityClient } from '../catalog'; /** * @internal @@ -69,6 +50,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext { discovery: DiscoveryService; auth: AuthService; httpAuth: HttpAuthService; + ownershipResolver: AuthOwnershipResolver; }): CatalogAuthResolverContext { const catalogIdentityClient = new CatalogIdentityClient({ catalogApi: options.catalogApi, @@ -84,6 +66,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext { catalogIdentityClient, options.catalogApi, options.auth, + options.ownershipResolver, ); } @@ -93,6 +76,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext { public readonly catalogIdentityClient: CatalogIdentityClient, private readonly catalogApi: CatalogApi, private readonly auth: AuthService, + private readonly ownershipResolver: AuthOwnershipResolver, ) {} async issueToken(params: TokenParams) { @@ -160,7 +144,9 @@ export class CatalogAuthResolverContext implements AuthResolverContext { async signInWithCatalogUser(query: AuthResolverCatalogUserQuery) { const { entity } = await this.findCatalogUser(query); - const ownershipRefs = getDefaultOwnershipEntityRefs(entity); + const ownershipRefs = await this.ownershipResolver.getOwnershipEntityRefs( + entity, + ); const token = await this.tokenIssuer.issueToken({ claims: { diff --git a/plugins/auth-backend/src/lib/resolvers/DefaultAuthOwnershipResolver.ts b/plugins/auth-backend/src/lib/resolvers/DefaultAuthOwnershipResolver.ts new file mode 100644 index 0000000000..3b6bab861f --- /dev/null +++ b/plugins/auth-backend/src/lib/resolvers/DefaultAuthOwnershipResolver.ts @@ -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. + */ +import { AuthOwnershipResolver } from '@backstage/plugin-auth-node'; +import { + Entity, + RELATION_MEMBER_OF, + stringifyEntityRef, +} from '@backstage/catalog-model'; + +/** + * Uses the default ownership resolution logic to return an array + * of entity refs that the provided entity claims ownership through. + * + * A reference to the entity itself will also be included in the returned array. + * + * @public + */ +export class DefaultAuthOwnershipResolver implements AuthOwnershipResolver { + async getOwnershipEntityRefs(entity: Entity): Promise { + const membershipRefs = + entity.relations + ?.filter( + r => + r.type === RELATION_MEMBER_OF && r.targetRef.startsWith('group:'), + ) + .map(r => r.targetRef) ?? []; + + return Array.from(new Set([stringifyEntityRef(entity), ...membershipRefs])); + } +} diff --git a/plugins/auth-backend/src/lib/resolvers/index.ts b/plugins/auth-backend/src/lib/resolvers/index.ts index c1ca59cb25..a9c132b386 100644 --- a/plugins/auth-backend/src/lib/resolvers/index.ts +++ b/plugins/auth-backend/src/lib/resolvers/index.ts @@ -14,7 +14,5 @@ * limitations under the License. */ -export { - CatalogAuthResolverContext, - getDefaultOwnershipEntityRefs, -} from './CatalogAuthResolverContext'; +export { CatalogAuthResolverContext } from './CatalogAuthResolverContext'; +export { DefaultAuthOwnershipResolver } from './DefaultAuthOwnershipResolver'; diff --git a/plugins/auth-backend/src/providers/router.ts b/plugins/auth-backend/src/providers/router.ts index 8c927f52c6..d2000288a7 100644 --- a/plugins/auth-backend/src/providers/router.ts +++ b/plugins/auth-backend/src/providers/router.ts @@ -25,13 +25,17 @@ import { } from '@backstage/backend-plugin-api'; import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; -import { NotFoundError, assertError } from '@backstage/errors'; -import { AuthProviderFactory } from '@backstage/plugin-auth-node'; +import { assertError, NotFoundError } from '@backstage/errors'; +import { + AuthOwnershipResolver, + AuthProviderFactory, +} from '@backstage/plugin-auth-node'; import express from 'express'; import Router from 'express-promise-router'; import { Minimatch } from 'minimatch'; import { CatalogAuthResolverContext } from '../lib/resolvers/CatalogAuthResolverContext'; import { TokenIssuer } from '../identity/types'; +import { DefaultAuthOwnershipResolver } from '../lib/resolvers'; /** @public */ export type ProviderFactories = { [s: string]: AuthProviderFactory }; @@ -49,6 +53,7 @@ export function bindProviderRouters( httpAuth: HttpAuthService; tokenManager: TokenManager; tokenIssuer: TokenIssuer; + ownershipResolver?: AuthOwnershipResolver; catalogApi?: CatalogApi; }, ) { @@ -64,6 +69,7 @@ export function bindProviderRouters( tokenManager, tokenIssuer, catalogApi, + ownershipResolver = new DefaultAuthOwnershipResolver(), } = options; const providersConfig = config.getOptionalConfig('auth.providers'); @@ -95,6 +101,7 @@ export function bindProviderRouters( discovery, auth, httpAuth, + ownershipResolver, }), }); diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index f1487d07a2..11c150aa33 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -23,15 +23,16 @@ import { LoggerService, } from '@backstage/backend-plugin-api'; import { defaultAuthProviderFactories } from '../providers'; +import { AuthOwnershipResolver } from '@backstage/plugin-auth-node'; import { + createLegacyAuthAdapters, PluginDatabaseManager, PluginEndpointDiscovery, TokenManager, - createLegacyAuthAdapters, } from '@backstage/backend-common'; import { NotFoundError } from '@backstage/errors'; import { CatalogApi } from '@backstage/catalog-client'; -import { bindOidcRouter, TokenFactory, KeyStores } from '../identity'; +import { bindOidcRouter, KeyStores, TokenFactory } from '../identity'; import session from 'express-session'; import connectSessionKnex from 'connect-session-knex'; import passport from 'passport'; @@ -41,7 +42,7 @@ import { TokenIssuer } from '../identity/types'; import { StaticTokenIssuer } from '../identity/StaticTokenIssuer'; import { StaticKeyStore } from '../identity/StaticKeyStore'; import { Config } from '@backstage/config'; -import { ProviderFactories, bindProviderRouters } from '../providers/router'; +import { bindProviderRouters, ProviderFactories } from '../providers/router'; /** @public */ export interface RouterOptions { @@ -56,6 +57,7 @@ export interface RouterOptions { providerFactories?: ProviderFactories; disableDefaultProviderFactories?: boolean; catalogApi?: CatalogApi; + ownershipResolver?: AuthOwnershipResolver; } /** @public */ diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index e79facc7f9..da3166d3f2 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/api-report.md @@ -21,6 +21,12 @@ import { Strategy } from 'passport'; import { ZodSchema } from 'zod'; import { ZodTypeDef } from 'zod'; +// @public +export interface AuthOwnershipResolver { + // (undocumented) + getOwnershipEntityRefs(entity: Entity): Promise; +} + // @public @deprecated (undocumented) export type AuthProviderConfig = { baseUrl: string; diff --git a/plugins/auth-node/src/index.ts b/plugins/auth-node/src/index.ts index 8b35aaa1e1..39063bf7c2 100644 --- a/plugins/auth-node/src/index.ts +++ b/plugins/auth-node/src/index.ts @@ -43,5 +43,6 @@ export type { SignInInfo, SignInResolver, TokenParams, + AuthOwnershipResolver, } from './types'; export { tokenTypes } from './types'; diff --git a/plugins/auth-node/src/types.ts b/plugins/auth-node/src/types.ts index 39172437ec..4dc5e570de 100644 --- a/plugins/auth-node/src/types.ts +++ b/plugins/auth-node/src/types.ts @@ -163,6 +163,15 @@ export type AuthResolverContext = { ): Promise; }; +/** + * Resolver interface for resolving the ownership entity references for entity + * + * @public + */ +export interface AuthOwnershipResolver { + getOwnershipEntityRefs(entity: Entity): Promise; +} + /** * Any Auth provider needs to implement this interface which handles the routes in the * auth backend. Any auth API requests from the frontend reaches these methods. From b0ae9ccac4def8cf15be6b05962c3b36e0d6c9a0 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Wed, 7 Feb 2024 13:02:49 +0200 Subject: [PATCH 032/234] feat: support for new backend system via extension point Signed-off-by: Heikki Hellgren --- plugins/auth-backend/src/authPlugin.ts | 44 +++++++++++++------ plugins/auth-node/api-report.md | 2 + .../extensions/AuthProvidersExtensionPoint.ts | 3 +- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/plugins/auth-backend/src/authPlugin.ts b/plugins/auth-backend/src/authPlugin.ts index f531ffabc6..f8e1f011b3 100644 --- a/plugins/auth-backend/src/authPlugin.ts +++ b/plugins/auth-backend/src/authPlugin.ts @@ -19,12 +19,39 @@ import { createBackendPlugin, } from '@backstage/backend-plugin-api'; import { + AuthOwnershipResolver, AuthProviderFactory, + AuthProviderRegistrationOptions, + AuthProvidersExtensionPoint, authProvidersExtensionPoint, } from '@backstage/plugin-auth-node'; import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; import { createRouter } from './service/router'; +class AuthProvidersExtensionPointImpl implements AuthProvidersExtensionPoint { + #providers = new Map(); + #ownershipResolver?: AuthOwnershipResolver; + registerProvider(options: AuthProviderRegistrationOptions): void { + const { providerId, factory } = options; + if (this.#providers.has(providerId)) { + throw new Error(`Auth provider '${providerId}' was already registered`); + } + this.#providers.set(providerId, factory); + } + + setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void { + this.#ownershipResolver = ownershipResolver; + } + + get providers() { + return this.#providers; + } + + get ownershipResolver() { + return this.#ownershipResolver; + } +} + /** * Auth plugin * @@ -33,18 +60,8 @@ import { createRouter } from './service/router'; export const authPlugin = createBackendPlugin({ pluginId: 'auth', register(reg) { - const providers = new Map(); - - reg.registerExtensionPoint(authProvidersExtensionPoint, { - registerProvider({ providerId, factory }) { - if (providers.has(providerId)) { - throw new Error( - `Auth provider '${providerId}' was already registered`, - ); - } - providers.set(providerId, factory); - }, - }); + const extensionPoint = new AuthProvidersExtensionPointImpl(); + reg.registerExtensionPoint(authProvidersExtensionPoint, extensionPoint); reg.registerInit({ deps: { @@ -78,8 +95,9 @@ export const authPlugin = createBackendPlugin({ auth, httpAuth, catalogApi, - providerFactories: Object.fromEntries(providers), + providerFactories: Object.fromEntries(extensionPoint.providers), disableDefaultProviderFactories: true, + ownershipResolver: extensionPoint.ownershipResolver, }); httpRouter.addAuthPolicy({ path: '/', diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index da3166d3f2..6a2e653d93 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/api-report.md @@ -68,6 +68,8 @@ export interface AuthProviderRouteHandlers { export interface AuthProvidersExtensionPoint { // (undocumented) registerProvider(options: AuthProviderRegistrationOptions): void; + // (undocumented) + setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void; } // @public (undocumented) diff --git a/plugins/auth-node/src/extensions/AuthProvidersExtensionPoint.ts b/plugins/auth-node/src/extensions/AuthProvidersExtensionPoint.ts index b67d5be017..e3396db4e0 100644 --- a/plugins/auth-node/src/extensions/AuthProvidersExtensionPoint.ts +++ b/plugins/auth-node/src/extensions/AuthProvidersExtensionPoint.ts @@ -15,7 +15,7 @@ */ import { createExtensionPoint } from '@backstage/backend-plugin-api'; -import { AuthProviderFactory } from '../types'; +import { AuthOwnershipResolver, AuthProviderFactory } from '../types'; /** @public */ export interface AuthProviderRegistrationOptions { @@ -26,6 +26,7 @@ export interface AuthProviderRegistrationOptions { /** @public */ export interface AuthProvidersExtensionPoint { registerProvider(options: AuthProviderRegistrationOptions): void; + setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void; } /** @public */ From 776ec144edf0f3a9a0ad6c7d1381263cac1b6365 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 2 Apr 2024 09:12:29 +0300 Subject: [PATCH 033/234] fix: review comments and naming Signed-off-by: Heikki Hellgren --- .changeset/selfish-pigs-glow.md | 2 +- plugins/auth-backend/api-report.md | 9 +-- plugins/auth-backend/src/authPlugin.ts | 55 +++++++++---------- plugins/auth-backend/src/index.ts | 2 +- .../resolvers/CatalogAuthResolverContext.ts | 37 +++++++++++-- .../resolvers/DefaultAuthOwnershipResolver.ts | 43 --------------- .../auth-backend/src/lib/resolvers/index.ts | 6 +- plugins/auth-backend/src/providers/router.ts | 3 +- plugins/auth-node/api-report.md | 15 ++++- .../AuthOwnershipResolutionExtensionPoint.ts | 28 ++++++++++ .../extensions/AuthProvidersExtensionPoint.ts | 3 +- plugins/auth-node/src/extensions/index.ts | 5 ++ plugins/auth-node/src/types.ts | 4 +- 13 files changed, 115 insertions(+), 97 deletions(-) delete mode 100644 plugins/auth-backend/src/lib/resolvers/DefaultAuthOwnershipResolver.ts create mode 100644 plugins/auth-node/src/extensions/AuthOwnershipResolutionExtensionPoint.ts diff --git a/.changeset/selfish-pigs-glow.md b/.changeset/selfish-pigs-glow.md index ca29a22feb..c7808c0283 100644 --- a/.changeset/selfish-pigs-glow.md +++ b/.changeset/selfish-pigs-glow.md @@ -3,4 +3,4 @@ '@backstage/plugin-auth-node': patch --- -Allow overriding default ownership resolving +Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 0f537d3dc2..15a2aa690e 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -194,12 +194,6 @@ export function createOriginFilter(config: Config): (origin: string) => boolean; // @public (undocumented) export function createRouter(options: RouterOptions): Promise; -// @public -export class DefaultAuthOwnershipResolver implements AuthOwnershipResolver { - // (undocumented) - getOwnershipEntityRefs(entity: Entity): Promise; -} - // @public export const defaultAuthProviderFactories: { [providerId: string]: AuthProviderFactory_2; @@ -223,6 +217,9 @@ export type GcpIapResult = GcpIapResult_2; // @public @deprecated export type GcpIapTokenInfo = GcpIapTokenInfo_2; +// @public +export function getDefaultOwnershipEntityRefs(entity: Entity): string[]; + // @public (undocumented) export type GithubOAuthResult = { fullProfile: Profile; diff --git a/plugins/auth-backend/src/authPlugin.ts b/plugins/auth-backend/src/authPlugin.ts index f8e1f011b3..7687462d32 100644 --- a/plugins/auth-backend/src/authPlugin.ts +++ b/plugins/auth-backend/src/authPlugin.ts @@ -19,39 +19,14 @@ import { createBackendPlugin, } from '@backstage/backend-plugin-api'; import { + authOwnershipResolutionExtensionPoint, AuthOwnershipResolver, AuthProviderFactory, - AuthProviderRegistrationOptions, - AuthProvidersExtensionPoint, authProvidersExtensionPoint, } from '@backstage/plugin-auth-node'; import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; import { createRouter } from './service/router'; -class AuthProvidersExtensionPointImpl implements AuthProvidersExtensionPoint { - #providers = new Map(); - #ownershipResolver?: AuthOwnershipResolver; - registerProvider(options: AuthProviderRegistrationOptions): void { - const { providerId, factory } = options; - if (this.#providers.has(providerId)) { - throw new Error(`Auth provider '${providerId}' was already registered`); - } - this.#providers.set(providerId, factory); - } - - setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void { - this.#ownershipResolver = ownershipResolver; - } - - get providers() { - return this.#providers; - } - - get ownershipResolver() { - return this.#ownershipResolver; - } -} - /** * Auth plugin * @@ -60,8 +35,28 @@ class AuthProvidersExtensionPointImpl implements AuthProvidersExtensionPoint { export const authPlugin = createBackendPlugin({ pluginId: 'auth', register(reg) { - const extensionPoint = new AuthProvidersExtensionPointImpl(); - reg.registerExtensionPoint(authProvidersExtensionPoint, extensionPoint); + const providers = new Map(); + let ownershipResolver: AuthOwnershipResolver | undefined = undefined; + + reg.registerExtensionPoint(authProvidersExtensionPoint, { + registerProvider({ providerId, factory }) { + if (providers.has(providerId)) { + throw new Error( + `Auth provider '${providerId}' was already registered`, + ); + } + providers.set(providerId, factory); + }, + }); + + reg.registerExtensionPoint(authOwnershipResolutionExtensionPoint, { + setAuthOwnershipResolver(resolver) { + if (ownershipResolver) { + throw new Error('Auth ownership resolver is already set'); + } + ownershipResolver = resolver; + }, + }); reg.registerInit({ deps: { @@ -95,9 +90,9 @@ export const authPlugin = createBackendPlugin({ auth, httpAuth, catalogApi, - providerFactories: Object.fromEntries(extensionPoint.providers), + providerFactories: Object.fromEntries(providers), disableDefaultProviderFactories: true, - ownershipResolver: extensionPoint.ownershipResolver, + ownershipResolver, }); httpRouter.addAuthPolicy({ path: '/', diff --git a/plugins/auth-backend/src/index.ts b/plugins/auth-backend/src/index.ts index d3688934c5..6c3f866031 100644 --- a/plugins/auth-backend/src/index.ts +++ b/plugins/auth-backend/src/index.ts @@ -34,4 +34,4 @@ export * from './lib/oauth'; export * from './lib/catalog'; -export { DefaultAuthOwnershipResolver } from './lib/resolvers'; +export { getDefaultOwnershipEntityRefs } from './lib/resolvers'; diff --git a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts index be0cd0da97..c81ac4c355 100644 --- a/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts +++ b/plugins/auth-backend/src/lib/resolvers/CatalogAuthResolverContext.ts @@ -20,6 +20,7 @@ import { DEFAULT_NAMESPACE, Entity, parseEntityRef, + RELATION_MEMBER_OF, stringifyEntityRef, } from '@backstage/catalog-model'; import { ConflictError, InputError, NotFoundError } from '@backstage/errors'; @@ -38,6 +39,25 @@ import { } from '@backstage/plugin-auth-node'; import { CatalogIdentityClient } from '../catalog'; +/** + * Uses the default ownership resolution logic to return an array + * of entity refs that the provided entity claims ownership through. + * + * A reference to the entity itself will also be included in the returned array. + * + * @public + */ +export function getDefaultOwnershipEntityRefs(entity: Entity) { + const membershipRefs = + entity.relations + ?.filter( + r => r.type === RELATION_MEMBER_OF && r.targetRef.startsWith('group:'), + ) + .map(r => r.targetRef) ?? []; + + return Array.from(new Set([stringifyEntityRef(entity), ...membershipRefs])); +} + /** * @internal */ @@ -50,7 +70,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext { discovery: DiscoveryService; auth: AuthService; httpAuth: HttpAuthService; - ownershipResolver: AuthOwnershipResolver; + ownershipResolver?: AuthOwnershipResolver; }): CatalogAuthResolverContext { const catalogIdentityClient = new CatalogIdentityClient({ catalogApi: options.catalogApi, @@ -76,7 +96,7 @@ export class CatalogAuthResolverContext implements AuthResolverContext { public readonly catalogIdentityClient: CatalogIdentityClient, private readonly catalogApi: CatalogApi, private readonly auth: AuthService, - private readonly ownershipResolver: AuthOwnershipResolver, + private readonly ownershipResolver?: AuthOwnershipResolver, ) {} async issueToken(params: TokenParams) { @@ -144,14 +164,19 @@ export class CatalogAuthResolverContext implements AuthResolverContext { async signInWithCatalogUser(query: AuthResolverCatalogUserQuery) { const { entity } = await this.findCatalogUser(query); - const ownershipRefs = await this.ownershipResolver.getOwnershipEntityRefs( - entity, - ); + let ent: string[]; + if (this.ownershipResolver) { + const { ownershipEntityRefs } = + await this.ownershipResolver.resolveOwnershipEntityRefs(entity); + ent = ownershipEntityRefs; + } else { + ent = getDefaultOwnershipEntityRefs(entity); + } const token = await this.tokenIssuer.issueToken({ claims: { sub: stringifyEntityRef(entity), - ent: ownershipRefs, + ent, }, }); return { token }; diff --git a/plugins/auth-backend/src/lib/resolvers/DefaultAuthOwnershipResolver.ts b/plugins/auth-backend/src/lib/resolvers/DefaultAuthOwnershipResolver.ts deleted file mode 100644 index 3b6bab861f..0000000000 --- a/plugins/auth-backend/src/lib/resolvers/DefaultAuthOwnershipResolver.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 { AuthOwnershipResolver } from '@backstage/plugin-auth-node'; -import { - Entity, - RELATION_MEMBER_OF, - stringifyEntityRef, -} from '@backstage/catalog-model'; - -/** - * Uses the default ownership resolution logic to return an array - * of entity refs that the provided entity claims ownership through. - * - * A reference to the entity itself will also be included in the returned array. - * - * @public - */ -export class DefaultAuthOwnershipResolver implements AuthOwnershipResolver { - async getOwnershipEntityRefs(entity: Entity): Promise { - const membershipRefs = - entity.relations - ?.filter( - r => - r.type === RELATION_MEMBER_OF && r.targetRef.startsWith('group:'), - ) - .map(r => r.targetRef) ?? []; - - return Array.from(new Set([stringifyEntityRef(entity), ...membershipRefs])); - } -} diff --git a/plugins/auth-backend/src/lib/resolvers/index.ts b/plugins/auth-backend/src/lib/resolvers/index.ts index a9c132b386..c1ca59cb25 100644 --- a/plugins/auth-backend/src/lib/resolvers/index.ts +++ b/plugins/auth-backend/src/lib/resolvers/index.ts @@ -14,5 +14,7 @@ * limitations under the License. */ -export { CatalogAuthResolverContext } from './CatalogAuthResolverContext'; -export { DefaultAuthOwnershipResolver } from './DefaultAuthOwnershipResolver'; +export { + CatalogAuthResolverContext, + getDefaultOwnershipEntityRefs, +} from './CatalogAuthResolverContext'; diff --git a/plugins/auth-backend/src/providers/router.ts b/plugins/auth-backend/src/providers/router.ts index d2000288a7..d84133685c 100644 --- a/plugins/auth-backend/src/providers/router.ts +++ b/plugins/auth-backend/src/providers/router.ts @@ -35,7 +35,6 @@ import Router from 'express-promise-router'; import { Minimatch } from 'minimatch'; import { CatalogAuthResolverContext } from '../lib/resolvers/CatalogAuthResolverContext'; import { TokenIssuer } from '../identity/types'; -import { DefaultAuthOwnershipResolver } from '../lib/resolvers'; /** @public */ export type ProviderFactories = { [s: string]: AuthProviderFactory }; @@ -69,7 +68,7 @@ export function bindProviderRouters( tokenManager, tokenIssuer, catalogApi, - ownershipResolver = new DefaultAuthOwnershipResolver(), + ownershipResolver, } = options; const providersConfig = config.getOptionalConfig('auth.providers'); diff --git a/plugins/auth-node/api-report.md b/plugins/auth-node/api-report.md index 6a2e653d93..6a43d3b127 100644 --- a/plugins/auth-node/api-report.md +++ b/plugins/auth-node/api-report.md @@ -21,10 +21,21 @@ import { Strategy } from 'passport'; import { ZodSchema } from 'zod'; import { ZodTypeDef } from 'zod'; +// @public (undocumented) +export interface AuthOwnershipResolutionExtensionPoint { + // (undocumented) + setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void; +} + +// @public (undocumented) +export const authOwnershipResolutionExtensionPoint: ExtensionPoint; + // @public export interface AuthOwnershipResolver { // (undocumented) - getOwnershipEntityRefs(entity: Entity): Promise; + resolveOwnershipEntityRefs(entity: Entity): Promise<{ + ownershipEntityRefs: string[]; + }>; } // @public @deprecated (undocumented) @@ -68,8 +79,6 @@ export interface AuthProviderRouteHandlers { export interface AuthProvidersExtensionPoint { // (undocumented) registerProvider(options: AuthProviderRegistrationOptions): void; - // (undocumented) - setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void; } // @public (undocumented) diff --git a/plugins/auth-node/src/extensions/AuthOwnershipResolutionExtensionPoint.ts b/plugins/auth-node/src/extensions/AuthOwnershipResolutionExtensionPoint.ts new file mode 100644 index 0000000000..d6235a75e2 --- /dev/null +++ b/plugins/auth-node/src/extensions/AuthOwnershipResolutionExtensionPoint.ts @@ -0,0 +1,28 @@ +/* + * 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 { AuthOwnershipResolver } from '../types'; +import { createExtensionPoint } from '@backstage/backend-plugin-api'; + +/** @public */ +export interface AuthOwnershipResolutionExtensionPoint { + setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void; +} + +/** @public */ +export const authOwnershipResolutionExtensionPoint = + createExtensionPoint({ + id: 'auth.ownershipResolution', + }); diff --git a/plugins/auth-node/src/extensions/AuthProvidersExtensionPoint.ts b/plugins/auth-node/src/extensions/AuthProvidersExtensionPoint.ts index e3396db4e0..b67d5be017 100644 --- a/plugins/auth-node/src/extensions/AuthProvidersExtensionPoint.ts +++ b/plugins/auth-node/src/extensions/AuthProvidersExtensionPoint.ts @@ -15,7 +15,7 @@ */ import { createExtensionPoint } from '@backstage/backend-plugin-api'; -import { AuthOwnershipResolver, AuthProviderFactory } from '../types'; +import { AuthProviderFactory } from '../types'; /** @public */ export interface AuthProviderRegistrationOptions { @@ -26,7 +26,6 @@ export interface AuthProviderRegistrationOptions { /** @public */ export interface AuthProvidersExtensionPoint { registerProvider(options: AuthProviderRegistrationOptions): void; - setAuthOwnershipResolver(ownershipResolver: AuthOwnershipResolver): void; } /** @public */ diff --git a/plugins/auth-node/src/extensions/index.ts b/plugins/auth-node/src/extensions/index.ts index 43e1751685..efca1623b3 100644 --- a/plugins/auth-node/src/extensions/index.ts +++ b/plugins/auth-node/src/extensions/index.ts @@ -19,3 +19,8 @@ export { type AuthProviderRegistrationOptions, type AuthProvidersExtensionPoint, } from './AuthProvidersExtensionPoint'; + +export { + authOwnershipResolutionExtensionPoint, + type AuthOwnershipResolutionExtensionPoint, +} from './AuthOwnershipResolutionExtensionPoint'; diff --git a/plugins/auth-node/src/types.ts b/plugins/auth-node/src/types.ts index 4dc5e570de..845fd7b0cb 100644 --- a/plugins/auth-node/src/types.ts +++ b/plugins/auth-node/src/types.ts @@ -169,7 +169,9 @@ export type AuthResolverContext = { * @public */ export interface AuthOwnershipResolver { - getOwnershipEntityRefs(entity: Entity): Promise; + resolveOwnershipEntityRefs( + entity: Entity, + ): Promise<{ ownershipEntityRefs: string[] }>; } /** From ee09dfceb05a5edbc670a9db8469ef4089e415b5 Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Mon, 15 Apr 2024 14:33:30 +0200 Subject: [PATCH 034/234] fix(notifications): add documentation for external services Signed-off-by: Marek Libra --- .changeset/unlucky-rivers-collect.md | 5 +++++ plugins/notifications-backend/README.md | 18 +++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 .changeset/unlucky-rivers-collect.md diff --git a/.changeset/unlucky-rivers-collect.md b/.changeset/unlucky-rivers-collect.md new file mode 100644 index 0000000000..c5ec865658 --- /dev/null +++ b/.changeset/unlucky-rivers-collect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend': patch +--- + +Updated documentation for sending messages by external services. diff --git a/plugins/notifications-backend/README.md b/plugins/notifications-backend/README.md index 233d0e5e21..60c9a4e078 100644 --- a/plugins/notifications-backend/README.md +++ b/plugins/notifications-backend/README.md @@ -71,7 +71,19 @@ export const myPlugin = createBackendPlugin({ }); ``` -## Sending notifications +## Sending Notifications By Backend Plugins -To be able to send notifications to users, you have to integrate the `@backstage/plugin-notifications-node` -to your application and plugins. +To be able to send notifications to users by other plugins, you have to integrate the `@backstage/plugin-notifications-node` +to your application and plugins. For the API, please refer documentation there. + +## Sending Notifications By External Services + +External services can create new messages by sending POST request to the REST API. + +To be able to do so, `external access` needs to be enabled as described in the [documentation](https://backstage.io/docs/auth/service-to-service-auth), e.g. via the `static tokens`. + +Once the API can be accessed, the request can look like: + +``` +curl -X POST [YOUR_SERVER_URL]/api/notifications -H "Content-Type: application/json" -H "Authorization: Bearer [BASE64_ENCODED_ACCESS_TOKEN]" -d '{"recipients":{"type":"entity","entityRef":"user:development/guest"},"payload": {"title": "Title of user-targeted external message","description": "The description","link": "http://foo.com/bar","severity": "high","topic": "The topic"}}' +``` From ee2bf431ff66716ec3a6ae9aeb2b1e0deda4df7d Mon Sep 17 00:00:00 2001 From: Binishma SS Date: Tue, 16 Apr 2024 14:24:43 +0530 Subject: [PATCH 035/234] Added ESLint to package-app-next Signed-off-by: Binishma SS --- packages/app-next/.eslintrc.js | 6 +++++- .../app-next/src/examples/notFoundErrorPageExtension.tsx | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/app-next/.eslintrc.js b/packages/app-next/.eslintrc.js index e2a53a6ad2..6f6668b53d 100644 --- a/packages/app-next/.eslintrc.js +++ b/packages/app-next/.eslintrc.js @@ -1 +1,5 @@ -module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + rules: { + '@backstage/no-top-level-material-ui-4-imports': 'error', + }, +}); diff --git a/packages/app-next/src/examples/notFoundErrorPageExtension.tsx b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx index f83671bc23..f5108c6c15 100644 --- a/packages/app-next/src/examples/notFoundErrorPageExtension.tsx +++ b/packages/app-next/src/examples/notFoundErrorPageExtension.tsx @@ -19,7 +19,8 @@ import { createComponentExtension, coreComponentRefs, } from '@backstage/frontend-plugin-api'; -import { Box, Typography } from '@material-ui/core'; +import Box from '@material-ui/core/Box'; +import Typography from '@material-ui/core/Typography'; import { Button } from '@backstage/core-components'; export function CustomNotFoundErrorPage() { From d1c325e1c7864aa0d65259a4100a3015b852d053 Mon Sep 17 00:00:00 2001 From: Karl Haworth Date: Tue, 16 Apr 2024 07:51:02 -0400 Subject: [PATCH 036/234] - update readme to include information on wolfi package strategy Signed-off-by: Karl Haworth --- contrib/docker/minimal-harded-image/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/docker/minimal-harded-image/README.md b/contrib/docker/minimal-harded-image/README.md index accb2188ac..5737cd3be0 100644 --- a/contrib/docker/minimal-harded-image/README.md +++ b/contrib/docker/minimal-harded-image/README.md @@ -10,3 +10,5 @@ The `Dockerfile` in this directory uses a [`wolfi-base`](https://github.com/wolf - `Wolfi` OS uses packages from the [os repository](https://github.com/wolfi-dev/os) on GitHub. Some packages may be named differently. - While `Wolfi` uses `apk`, the OS is designed to support `glibc`. - Due to the stripped down nature of the base image, additional packages might be needed compared to a distribution like Debian or Ubuntu. +- Chainguard will maintain one version of each Wolfi package at a time, which will track the latest version of the upstream software in the package. Chainguard will end patch support for previous versions of packages in Wolfi. [Read more here](https://edu.chainguard.dev/chainguard/chainguard-images/faq/#what-packages-are-available-in-chainguard-images) + - It is encouraged to use a relative pin or use a third-party tool to ensure the packages are kept up to date From 3ad8f49c1decfc5f1d78838581940e5c72e14421 Mon Sep 17 00:00:00 2001 From: Karl Haworth Date: Tue, 16 Apr 2024 07:51:23 -0400 Subject: [PATCH 037/234] add info on digests Signed-off-by: Karl Haworth --- contrib/docker/minimal-harded-image/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/contrib/docker/minimal-harded-image/README.md b/contrib/docker/minimal-harded-image/README.md index 5737cd3be0..a4f19784d4 100644 --- a/contrib/docker/minimal-harded-image/README.md +++ b/contrib/docker/minimal-harded-image/README.md @@ -4,6 +4,16 @@ DockerHub images in general did not seem ideal for Backstage as the number of vu The `Dockerfile` in this directory uses a [`wolfi-base`](https://github.com/wolfi-dev) image from Chainguard Images. This improves the security of the application and reduces false positives in scanners. +## Pinning Digest + +To reduce maintenance, the digest of the image has been removed from the `./Dockerfile` file. A complete example with the digest would be `cgr.dev/chainguard/wolfi-base:latest@sha256:3d6dece13cdb5546cd03b20e14f9af354bc1a56ab5a7b47dca3e6c1557211fcf` and it is suggested to update the `FROM` line in the `Dockerfile` to use a digest. + +Using the digest allows tools such as Dependabot or Renovate to know exactly which image digest is being utilized and allows for Pull Requests to be triggered when a new digest is available. It is suggested to setup Dependabot/Renovate or a similar tool to ensure the image is kept up to date so that vulnerability fixes that have been addressed are pulled in frequently. + +### Obtaining Digest + +To obtain the latest digest, perform a `docker pull` on the image to get the latest digest in the command output or use `crane digest `. + ## Considerations - `Wolfi` only releases the `latest` tag for public consumption however digests can be pinned. From 4120800bd9e4b46c84b2e1b1436929b3f133eb07 Mon Sep 17 00:00:00 2001 From: Karl Haworth Date: Tue, 16 Apr 2024 07:51:40 -0400 Subject: [PATCH 038/234] - move path from `minimal-harded-image` -> `minimal-hardened-image` Signed-off-by: Karl Haworth --- .../Dockerfile | 0 .../README.md | 0 docs/deployment/docker.md | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename contrib/docker/{minimal-harded-image => minimal-hardened-image}/Dockerfile (100%) rename contrib/docker/{minimal-harded-image => minimal-hardened-image}/README.md (100%) diff --git a/contrib/docker/minimal-harded-image/Dockerfile b/contrib/docker/minimal-hardened-image/Dockerfile similarity index 100% rename from contrib/docker/minimal-harded-image/Dockerfile rename to contrib/docker/minimal-hardened-image/Dockerfile diff --git a/contrib/docker/minimal-harded-image/README.md b/contrib/docker/minimal-hardened-image/README.md similarity index 100% rename from contrib/docker/minimal-harded-image/README.md rename to contrib/docker/minimal-hardened-image/README.md diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 791882224d..9ec5460a88 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -345,8 +345,8 @@ The `Dockerfile` mentioned above located in `packages/backend` is maintained by ### Minimal Hardened Image -A contributed `Dockerfile` exists within the directory of `contrib/docker/minimal-harded-image` which uses the [`wolfi-base`](https://github.com/wolfi-dev) image to reduce vulnerabilities. When this was contributed, this alternative `Dockerfile` reduced 98.2% of vulnerabilities in the built Backstage docker image when compared with the image built from `packages/backend/Dockerfile`. +A contributed `Dockerfile` exists within the directory of `contrib/docker/minimal-hardened-image` which uses the [`wolfi-base`](https://github.com/wolfi-dev) image to reduce vulnerabilities. When this was contributed, this alternative `Dockerfile` reduced 98.2% of vulnerabilities in the built Backstage docker image when compared with the image built from `packages/backend/Dockerfile`. -To reduce maintenance, the digest of the image has been removed from the `contrib/docker/minimal-harded-image/Dockerfile` file. A complete example with the digest would be `cgr.dev/chainguard/wolfi-base:latest@sha256:3d6dece13cdb5546cd03b20e14f9af354bc1a56ab5a7b47dca3e6c1557211fcf` and it is suggested to update the `FROM` line in the `Dockerfile` to use a digest. Please do a docker pull on the image to get the latest digest. Using the digest allows tools such as Dependabot or Renovate to know exactly which image digest is being utilized and allows for Pull Requests to be triggered when a new digest is available. +To reduce maintenance, the digest of the image has been removed from the `contrib/docker/minimal-hardened-image/Dockerfile` file. A complete example with the digest would be `cgr.dev/chainguard/wolfi-base:latest@sha256:3d6dece13cdb5546cd03b20e14f9af354bc1a56ab5a7b47dca3e6c1557211fcf` and it is suggested to update the `FROM` line in the `Dockerfile` to use a digest. Please do a docker pull on the image to get the latest digest. Using the digest allows tools such as Dependabot or Renovate to know exactly which image digest is being utilized and allows for Pull Requests to be triggered when a new digest is available. It is suggested to setup Dependabot/Renovate or a similar tool to ensure the image is kept up to date so that vulnerability fixes that have been addressed are pulled in frequently. From 9d089a421916cd366e2c669983bf2d8303d1061d Mon Sep 17 00:00:00 2001 From: Karl Haworth Date: Tue, 16 Apr 2024 07:52:35 -0400 Subject: [PATCH 039/234] change item to subbullet Signed-off-by: Karl Haworth --- contrib/docker/minimal-hardened-image/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/docker/minimal-hardened-image/README.md b/contrib/docker/minimal-hardened-image/README.md index a4f19784d4..c9a41d0d19 100644 --- a/contrib/docker/minimal-hardened-image/README.md +++ b/contrib/docker/minimal-hardened-image/README.md @@ -17,7 +17,8 @@ To obtain the latest digest, perform a `docker pull` on the image to get the lat ## Considerations - `Wolfi` only releases the `latest` tag for public consumption however digests can be pinned. -- `Wolfi` OS uses packages from the [os repository](https://github.com/wolfi-dev/os) on GitHub. Some packages may be named differently. +- `Wolfi` OS uses packages from the [os repository](https://github.com/wolfi-dev/os) on GitHub. + - Some packages may be named differently. - While `Wolfi` uses `apk`, the OS is designed to support `glibc`. - Due to the stripped down nature of the base image, additional packages might be needed compared to a distribution like Debian or Ubuntu. - Chainguard will maintain one version of each Wolfi package at a time, which will track the latest version of the upstream software in the package. Chainguard will end patch support for previous versions of packages in Wolfi. [Read more here](https://edu.chainguard.dev/chainguard/chainguard-images/faq/#what-packages-are-available-in-chainguard-images) From 79d91e06af741dd2701c69c1706859ceaad9ef2e Mon Sep 17 00:00:00 2001 From: Karl Haworth Date: Tue, 16 Apr 2024 11:48:32 -0500 Subject: [PATCH 040/234] add wolfi to vale accept list Signed-off-by: Karl Haworth --- .github/vale/config/vocabularies/Backstage/accept.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index b525d7e48e..77b144dbcb 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -462,6 +462,7 @@ Weaveworks Webpack widget's winston +Wolfi www WWW XCMetrics From b55f026d43b2c8a14b4c2340470bb9cdd7903271 Mon Sep 17 00:00:00 2001 From: Karl Haworth Date: Tue, 16 Apr 2024 11:49:12 -0500 Subject: [PATCH 041/234] remove backticks from 'wolfi' Signed-off-by: Karl Haworth --- contrib/docker/minimal-hardened-image/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/docker/minimal-hardened-image/README.md b/contrib/docker/minimal-hardened-image/README.md index c9a41d0d19..62045a4ded 100644 --- a/contrib/docker/minimal-hardened-image/README.md +++ b/contrib/docker/minimal-hardened-image/README.md @@ -2,7 +2,7 @@ DockerHub images in general did not seem ideal for Backstage as the number of vulnerabilities were quite high regardless of the image used. -The `Dockerfile` in this directory uses a [`wolfi-base`](https://github.com/wolfi-dev) image from Chainguard Images. This improves the security of the application and reduces false positives in scanners. +The `Dockerfile` in this directory uses a [wolfi-base](https://github.com/wolfi-dev) image from Chainguard Images. This improves the security of the application and reduces false positives in scanners. ## Pinning Digest @@ -16,10 +16,10 @@ To obtain the latest digest, perform a `docker pull` on the image to get the lat ## Considerations -- `Wolfi` only releases the `latest` tag for public consumption however digests can be pinned. -- `Wolfi` OS uses packages from the [os repository](https://github.com/wolfi-dev/os) on GitHub. +- Wolfi only releases the `latest` tag for public consumption however digests can be pinned. +- Wolfi OS uses packages from the [os repository](https://github.com/wolfi-dev/os) on GitHub. - Some packages may be named differently. -- While `Wolfi` uses `apk`, the OS is designed to support `glibc`. +- While Wolfi uses `apk`, the OS is designed to support `glibc`. - Due to the stripped down nature of the base image, additional packages might be needed compared to a distribution like Debian or Ubuntu. - Chainguard will maintain one version of each Wolfi package at a time, which will track the latest version of the upstream software in the package. Chainguard will end patch support for previous versions of packages in Wolfi. [Read more here](https://edu.chainguard.dev/chainguard/chainguard-images/faq/#what-packages-are-available-in-chainguard-images) - It is encouraged to use a relative pin or use a third-party tool to ensure the packages are kept up to date From 6f5a3a3ce5b337de29ccb54c0f887ec113d1b583 Mon Sep 17 00:00:00 2001 From: secustor Date: Wed, 17 Apr 2024 17:19:35 +0200 Subject: [PATCH 042/234] fix(plugins/catalog-backend-module-unprocessed): correctly translate owner to string in case of nullish value Signed-off-by: secustor --- .changeset/cyan-suns-shave.md | 5 +++++ .../src/UnprocessedEntitiesModule.ts | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/cyan-suns-shave.md diff --git a/.changeset/cyan-suns-shave.md b/.changeset/cyan-suns-shave.md new file mode 100644 index 0000000000..09e51d9548 --- /dev/null +++ b/.changeset/cyan-suns-shave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-unprocessed': patch +--- + +Correctly convert owner to string in case owner has not been provided diff --git a/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts b/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts index 23042ff2d8..c46848f201 100644 --- a/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts +++ b/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts @@ -162,7 +162,7 @@ export class UnprocessedEntitiesModule { return res.json( await this.unprocessed({ reason: 'failed', - owner: String(req.query.owner), + owner: req.query.owner?.toString(), }), ); }) @@ -170,7 +170,7 @@ export class UnprocessedEntitiesModule { return res.json( await this.unprocessed({ reason: 'pending', - owner: String(req.query.owner), + owner: req.query.owner?.toString(), }), ); }) From 4039d328ae08f51b550c0d6b191776b004335446 Mon Sep 17 00:00:00 2001 From: Coderrob Date: Wed, 17 Apr 2024 11:14:53 -0500 Subject: [PATCH 043/234] Adjust to make lighthouse happier Signed-off-by: Coderrob --- plugins/catalog/src/components/CatalogTable/CatalogTable.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 08da250051..b1590bdff3 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -169,9 +169,7 @@ export const CatalogTable = (props: CatalogTableProps) => { const currentKind = filters.kind?.value || ''; const currentType = filters.type?.value || ''; - const currentCount = Number.isSafeInteger(totalItems) - ? `(${totalItems})` - : ''; + const currentCount = typeof totalItems === 'number' ? `(${totalItems})` : ''; // TODO(timbonicus): remove the title from the CatalogTable once using EntitySearchBar const titlePreamble = capitalize(filters.user?.value ?? 'all'); const title = [ From 32da2b1954e360ffb083cbff3d6669878a2aaba9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Apr 2024 02:26:13 +0000 Subject: [PATCH 044/234] chore(deps): update dependency @types/tar to v6.1.13 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ad5d48bd8f..4b093fd7f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19770,12 +19770,12 @@ __metadata: linkType: hard "@types/tar@npm:^6.1.1": - version: 6.1.12 - resolution: "@types/tar@npm:6.1.12" + version: 6.1.13 + resolution: "@types/tar@npm:6.1.13" dependencies: "@types/node": "*" minipass: ^4.0.0 - checksum: b1cbae1894cc943e3a86f88613853986f97f552c6bec34ee990a47fe5905871d1552397ff440108233d75d05653be2fbc356e62beb0b93a45b927fb88060b438 + checksum: bb3910936a6b37f093e38b73a52b0544fd73079685f5ea72e5c049fddc3770e58d80cf6d714425853f0746290221852c1a7ca89ffdb9614f3b2a858a3bf5436a languageName: node linkType: hard From f793112df9c0ebbef910d63692e033c467a42551 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Thu, 18 Apr 2024 10:43:29 +0300 Subject: [PATCH 045/234] feat: allow defining custom class name + other props for notifications sidebar item Signed-off-by: Heikki Hellgren --- .changeset/thick-llamas-itch.md | 5 ++++ plugins/notifications/api-report.md | 6 +++++ .../NotificationsSideBarItem.tsx | 25 +++++++++++++++---- 3 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 .changeset/thick-llamas-itch.md diff --git a/.changeset/thick-llamas-itch.md b/.changeset/thick-llamas-itch.md new file mode 100644 index 0000000000..17d4fb23b6 --- /dev/null +++ b/.changeset/thick-llamas-itch.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Allow defining `className` and additional properties for `NotificationsSideBarItem` diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index 11c671112a..dc0db705ba 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -9,6 +9,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; +import { IconComponent } from '@backstage/core-plugin-api'; import { JSX as JSX_2 } from 'react'; import { Notification as Notification_2 } from '@backstage/plugin-notifications-common'; import { NotificationSeverity } from '@backstage/plugin-notifications-common'; @@ -87,6 +88,11 @@ export const notificationsPlugin: BackstagePlugin< export const NotificationsSidebarItem: (props?: { webNotificationsEnabled?: boolean; titleCounterEnabled?: boolean; + className?: string; + icon?: IconComponent; + text?: string; + disableHighlight?: boolean; + noTrack?: boolean; }) => React_2.JSX.Element; // @public (undocumented) diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 2163c6660f..4842a41c0c 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -17,7 +17,7 @@ import React, { useEffect } from 'react'; import { useNotificationsApi } from '../../hooks'; import { SidebarItem } from '@backstage/core-components'; import NotificationsIcon from '@material-ui/icons/Notifications'; -import { useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { IconComponent, useApi, useRouteRef } from '@backstage/core-plugin-api'; import { rootRouteRef } from '../../routes'; import { useSignal } from '@backstage/plugin-signals-react'; import { NotificationSignal } from '@backstage/plugin-notifications-common'; @@ -29,9 +29,21 @@ import { notificationsApiRef } from '../../api'; export const NotificationsSidebarItem = (props?: { webNotificationsEnabled?: boolean; titleCounterEnabled?: boolean; + className?: string; + icon?: IconComponent; + text?: string; + disableHighlight?: boolean; + noTrack?: boolean; }) => { - const { webNotificationsEnabled = false, titleCounterEnabled = true } = - props ?? { webNotificationsEnabled: false, titleCounterEnabled: true }; + const { + webNotificationsEnabled = false, + titleCounterEnabled = true, + className, + icon = NotificationsIcon, + text = 'Notifications', + disableHighlight, + noTrack, + } = props ?? { webNotificationsEnabled: false, titleCounterEnabled: true }; const { loading, error, value, retry } = useNotificationsApi(api => api.getStatus(), @@ -95,10 +107,13 @@ export const NotificationsSidebarItem = (props?: { // TODO: Figure out if the count can be added to hasNotifications return ( ); }; From 42171594dbc38688e3651d01faf31284374d7ba5 Mon Sep 17 00:00:00 2001 From: drodil Date: Thu, 18 Apr 2024 14:31:48 +0300 Subject: [PATCH 046/234] Update plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx Co-authored-by: Marek Libra Signed-off-by: drodil Signed-off-by: Heikki Hellgren --- .../NotificationsSideBarItem.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 4842a41c0c..d143344a9a 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -38,11 +38,9 @@ export const NotificationsSidebarItem = (props?: { const { webNotificationsEnabled = false, titleCounterEnabled = true, - className, icon = NotificationsIcon, text = 'Notifications', - disableHighlight, - noTrack, + ...restProps } = props ?? { webNotificationsEnabled: false, titleCounterEnabled: true }; const { loading, error, value, retry } = useNotificationsApi(api => @@ -107,13 +105,11 @@ export const NotificationsSidebarItem = (props?: { // TODO: Figure out if the count can be added to hasNotifications return ( ); }; From 0652addd56dcee46d23c6604bed512ae1ccf3b64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 18 Apr 2024 17:06:29 +0200 Subject: [PATCH 047/234] update BEP-0007 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- beps/0007-auth-external-services/README.md | 179 ++++++++++++++------- 1 file changed, 125 insertions(+), 54 deletions(-) diff --git a/beps/0007-auth-external-services/README.md b/beps/0007-auth-external-services/README.md index c8c20879d0..d49cd7992f 100644 --- a/beps/0007-auth-external-services/README.md +++ b/beps/0007-auth-external-services/README.md @@ -12,9 +12,7 @@ creation-date: 2024-03-30 # BEP: Authentication of External Services - - -[**Discussion Issue**](https://github.com/backstage/backstage/issues/NNNNN) +[**Discussion Issue**](https://github.com/backstage/backstage/issues/24349) - [Summary](#summary) - [Motivation](#motivation) @@ -28,17 +26,15 @@ creation-date: 2024-03-30 ## Summary -This proposal builds upon the foundation laid out in the 0003-auth-architecture-evolution BEP by introducing an authentication mechanism designed for external callers seeking to access the REST APIs exposed by Backstage or its plugins. +This proposal builds upon the foundation laid out in the [Auth Architecture Evolution BEP](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution), by improving the authentication mechanisms used by external callers seeking to access the REST APIs exposed by Backstage plugins. -This addition is not intended to supplant or offer an alternative to existing JWT tokens utilized for backend-to-backend or frontend-to-backend communication. JWT tokens remain a viable option for external callers. However, this proposal presents additional options to simplify usage, especially in heterogeneous setups. +Specifically, the inclusion of shared-key based authentication is proposed, along with a framework for integrating different authentication types in the future. Compatibility with the legacy shared-secret token signature mechanism is retained. -Specifically, the inclusion of shared-key based authentication is proposed, along with a framework for integrating different authentication types in the future. - -By implementing this proposed solution, security should not be compromised beyond the existing state when exposing the API to the internet. Nonetheless, it is advisable to maintain a level of protection by shielding the API from direct internet access. +By implementing this proposed solution, security should not be compromised beyond the existing state when exposing the API to the internet. Nonetheless, it is advisable to maintain a level of protection by shielding your deployments from the internet access unless you explicitly need public access. ## Motivation -This proposal represents a progression from the tutorial in https://backstage.io/docs/auth/service-to-service-auth/#usage-in-external-callers in the context of the new Authentication service. +This proposal represents a progression from [the old service to service auth tutorial](https://backstage.io/docs/auth/service-to-service-auth--old/#usage-in-external-callers) in the context of the new `coreServices.auth` and `coreServices.httpAuth` authentication services. It offers expanded configuration possibilities and will include ready-to-use examples as part of the implementation to streamline the process of invoking REST services exposed by Backstage backend plugins for external services. @@ -46,12 +42,16 @@ The previous tutorial lacked clarity on how to effectively structure the call, r Examples of potential use-cases include: -- An external asynchronous service requiring the ability to send notifications to users. To facilitate this process, no code change but configuration only is needed on the Backstage side. +- An external asynchronous service requiring the ability to send notifications to users +- Integrations for external services interacting with the catalog +- Temporary `curl` friendly tokens in local development + +To facilitate this process, no code change but configuration only is needed on the Backstage side. ### Goals - The primary objective is to facilitate access for external heterogeneous services to the Backstage REST API without involving unnecessary complexity. -- This addition is entirely optional. JWT tokens remain mandatory for all internal communication within Backstage. +- Moving to the proposed new auth scheme is entirely optional. The old shared key signature tokens remain usable. - Configuration options will allow specification of which API endpoints can be accessed by an external entity. - Limiting API context and granting distinct external access independently helps mitigate security risks. A compromised external service can only access a restricted portion of the API. - Enhanced clarity and updated examples make it easy to comprehend and utilize. @@ -60,54 +60,108 @@ Examples of potential use-cases include: It is not a goal -- to replace the current Backstage service-to-service authentication or tokens, -- to offer a bypass option for Backstage plugins to circumvent existing service-to-service authentication. Even if feasible, such a bypass would be considered as an anti-pattern. +- To replace the current Backstage service-to-service authentication or tokens, +- To offer a bypass option for Backstage plugins to circumvent existing service-to-service authentication. Even if feasible, such a bypass would be considered as an anti-pattern. ## Proposal -The `app-config.yaml` `backend.auth.keys` property will become an array containing alternative authentication mechanisms for callers. +The `backend.auth.keys` section of your app config remains supported for the time being, but is considered legacy. It is read by the new auth handler code solely for backward compatibility reasons. Using this format will lead to a logged warning urging a migration to the new format described below. -JWT tokens remain obligatory for internal Backstage calls and become optional for external callers. Given the complexity associated with composing JWT tokens by external entities, alternative authentication options will be provided for external callers: +This BEP adds a `backend.auth.externalAccess` section to your app config. This section is the new recommended way of declaring all methods of external service access that you want to support. Like the legacy section above, it is an array, but each element has a `type` field to allow for extensibility. Using an array also means that the configuration system will not make any config merging of access methods across different config files - which avoids unintended mistakes that might otherwise have impacted security. -``` -backend: - auth: - keys: - - type: static - value: ${SERVICE_API_KEY} - context: '/' - - type: jwks - value: https://other-service.acme.org/.well-known/jwks.json - context: '/foo' - - type: certificate - value: - $file: ./service-cert.pem - context: '/foo/bar' -``` - -The static type is designed for a straightforward shared-key strategy. In this approach, an external service provides a secret string to authenticate itself and access REST resources within a specified context. The shared key is transmitted via a new request header, `authorization_static`. - -Ideally, each external service should have its own unique secret key, even when accessing the same context. If a service requires access to different, separate contexts, multiple corresponding entries will be added to the configuration. - -While other authentication types are provided as examples for potential future expansions, they will not be implemented in the initial phase. However, additional types, including those not yet defined, can be introduced in subsequent phases. - -Introducing a new external caller with its exclusive secret key entails updating the app-config.yaml file and restarting the backend. This mechanism is designed to support integration with selected services, and therefore dynamic addition of callers is not necessary. - -New type-specific functions, such as authenticateStatic(), will be integrated into the AuthService, alongside the existing authenticate() function, which will remain dedicated to JWT tokens. - -No changes will be made to the existing APIs, as any necessary adjustments can be incorporated within private functions of the existing DefaultHttpAuthService implementation. - -Specifically, the DefaultHttpAuthService.extractCredentialsFromRequest function will continue to retrieve a bearer token in JWT format from the authorization header as before. If this header is absent, the service will proceed to read and manage the header corresponding to the next authentication type. - -Furthermore, the options parameter of the HttpAuthService.credentials() function will be expanded to include a new context property. This property will be compared to the value of the context configuration property associated with the corresponding authentication key. If the context parameter is not a subset of the configuration property, the request will be denied. +The two types of external access covered by this BEP are `legacy` and `static`. More on that below. ## Design Details - +### Configuration + +The following is an example of what an app config section with a single access method could look like: + +```yaml +backend: + auth: + externalAccess: + - type: static + options: + token: ${SERVICE_API_TOKEN} + scope: + plugins: catalog +``` + +Each entry has a `type`, which is any string. The framework handles a number of these types - initially a fixed set, but in the future we might make it extensible. + +The `options` is a general object, and is specific to each type, configuring it as needed. This particular type only had a `token`, which was configured to be taken from an environment variable. + +Each entry may also optionally have a `scope` section that controls what operations that this entry can be used for. Attempting to use this access method for any other scope will lead to 403 error rejections. Scopes can contain plugin IDs and/or permissions. + +Introducing a new external caller with its exclusive secret key entails updating the app-config.yaml file and restarting the backend. This mechanism is designed to support integration with selected services, and therefore dynamic addition of callers is not necessary at this point. + +### Scopes + +Scopes are optional. If you do not specify any scopes, the access method in question has unlimited scope and can perform all types of action. + +In the examples below, either a string or a list of strings can be given. + +If _any_ scope rule matches, the action is permitted. I.e., if you specify a plugin rule, then also adding permission rules for that plugin will have no effect since the plugin rule will have already matched. + +You can limit by the target plugin ID being accessed with the token: + +```yaml +scope: + plugin: catalog +``` + +You can limit by the type of permission being requested: + +```yaml +scope: + permission: catalog.entity.read +``` + +Or by permissions attribute: + +```yaml +scope: + permissionAttributes: { action: read } +``` + +### The Legacy Access Type + +Configuration example: + +```yaml +backend: + auth: + externalAccess: + - type: legacy + options: + secret: ${EXTERNAL_ACCESS_SIGNATURE_SECRET} + # scope: ... +``` + +This corresponds exactly to the old shared secret signature method, and any secrets entered here will be merged with those specified under `backend.auth.keys` if any. + +### The Static Access Type + +Configuration example: + +```yaml +backend: + auth: + externalAccess: + - type: static + options: + token: ${SERVICE_API_TOKEN} + # scope: ... +``` + +This allows you to specify any static token string as an API key, which callers send verbatim as the header `Authorization: Bearer `. + +### Implementation + +The token verification of the two access methods will live entirely with the `coreServices.auth` service implementation, and will be returning regular credentials with a service principal. The implementation will only need to alter the `authenticate` method and no additions to the API will be necessary. + +The service principal type will need to be amended with an optional scope field, carried over from the configuration. This will let the `ServerPermissionClient` compare against the set of allowlisted actions. In addition, the auth service can early-reject based on plugin ID if such rules are given. ## Release Plan @@ -117,13 +171,30 @@ This section should describe the rollout process for any new features. It must t If there is any particular feedback to be gathered during the rollout, this should be described here as well. --> -TBD +The initial pilot implementation of the `legacy` and `static` types has already been merged, along with the corresponding config additions. -- adding different types in the future +Validating this implementation, and adding scope restrictions, is still on todo but could be added incrementally at any time without breaking changes - as long as the default scope is "all". + +We have left it out of the scope of this BEP to allow for addition of more access types. Making that a possibility will likely require the addition of service extension points at a framework level, which is not yet made a priority. + +Potential _future_ access types (not part of this BEP, but rather only for illustration) could for example be: + +```yaml +backend: + auth: + externalAccess: + - type: jwks + options: + url: https://other-service.acme.org/.well-known/jwks.json + - type: certificate + options: + publicCert: + $file: ./service-cert.pem +``` ## Dependencies -- 0003-auth-architecture-evolution +- [BEP-0003: Auth Architecture Evolution](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution) ## Alternatives From fcda449a9c35e5abcbccf635481a7cef1e04597f Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Fri, 19 Apr 2024 12:14:16 +0200 Subject: [PATCH 048/234] fix(notifications): limit size of notfcation on NotificationsPage The grid item size is limited so notifications with very long descriptions do not occupy all the space. Signed-off-by: Marek Libra --- .changeset/warm-fans-promise.md | 5 ++++ .../NotificationsTable/NotificationsTable.tsx | 24 +++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 .changeset/warm-fans-promise.md diff --git a/.changeset/warm-fans-promise.md b/.changeset/warm-fans-promise.md new file mode 100644 index 0000000000..a8a8f8a2c7 --- /dev/null +++ b/.changeset/warm-fans-promise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +The rendered size of a notification is limited for very long descriptions. diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx index daaa937611..3b34f9766b 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx @@ -21,6 +21,7 @@ import Box from '@material-ui/core/Box'; import Grid from '@material-ui/core/Grid'; import CheckBox from '@material-ui/core/Checkbox'; import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core/styles'; import { Notification } from '@backstage/plugin-notifications-common'; import { notificationsApiRef } from '../../api'; @@ -38,6 +39,16 @@ import { BulkActions } from './BulkActions'; const ThrottleDelayMs = 1000; +const useStyles = makeStyles({ + description: { + maxHeight: '5rem', + overflow: 'scroll', + }, + severityItem: { + alignContent: 'center', + }, +}); + /** @public */ export type NotificationsTableProps = Pick< TableProps, @@ -62,6 +73,7 @@ export const NotificationsTable = ({ pageSize, totalCount, }: NotificationsTableProps) => { + const classes = useStyles(); const notificationsApi = useApi(notificationsApiRef); const [selectedNotifications, setSelectedNotifications] = React.useState( new Set(), @@ -155,10 +167,10 @@ export const NotificationsTable = ({ // Compact content return ( - + - + {notification.payload.link ? ( @@ -169,7 +181,7 @@ export const NotificationsTable = ({ notification.payload.title )} - + {notification.payload.description} @@ -211,11 +223,13 @@ export const NotificationsTable = ({ }, ], [ + selectedNotifications, + notifications, onSwitchReadStatus, onSwitchSavedStatus, - selectedNotifications, onNotificationsSelectChange, - notifications, + classes.severityItem, + classes.description, ], ); From c35ea7c8ecb3a6c0873ce89752cd5401f0ec695c Mon Sep 17 00:00:00 2001 From: Sebastian Poxhofer Date: Sat, 20 Apr 2024 08:47:59 +0200 Subject: [PATCH 049/234] Apply suggestions from code review Co-authored-by: Vincenzo Scamporlino Signed-off-by: Sebastian Poxhofer --- .../src/UnprocessedEntitiesModule.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts b/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts index c46848f201..6f0fa7d2e5 100644 --- a/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts +++ b/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts @@ -162,7 +162,7 @@ export class UnprocessedEntitiesModule { return res.json( await this.unprocessed({ reason: 'failed', - owner: req.query.owner?.toString(), + owner: typeof req.query.owner === 'string' ? req.query.owner : undefined, }), ); }) @@ -170,7 +170,7 @@ export class UnprocessedEntitiesModule { return res.json( await this.unprocessed({ reason: 'pending', - owner: req.query.owner?.toString(), + owner: typeof req.query.owner === 'string' ? req.query.owner : undefined }), ); }) From 821f902bfec625628ce82e38003a37c0bc9cd78f Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Sat, 20 Apr 2024 22:15:57 -0400 Subject: [PATCH 050/234] update to diff instead of check Signed-off-by: aramissennyeydd --- .changeset/flat-countries-clap.md | 2 +- packages/repo-tools/src/commands/index.ts | 14 +++++++------- .../package/schema/openapi/{check.ts => diff.ts} | 0 .../repo/schema/openapi/{check.ts => diff.ts} | 12 ++++-------- plugins/catalog-backend/package.json | 2 +- 5 files changed, 13 insertions(+), 17 deletions(-) rename packages/repo-tools/src/commands/package/schema/openapi/{check.ts => diff.ts} (100%) rename packages/repo-tools/src/commands/repo/schema/openapi/{check.ts => diff.ts} (90%) diff --git a/.changeset/flat-countries-clap.md b/.changeset/flat-countries-clap.md index ccd779a2b5..9c962b04a3 100644 --- a/.changeset/flat-countries-clap.md +++ b/.changeset/flat-countries-clap.md @@ -2,4 +2,4 @@ '@backstage/repo-tools': minor --- -Adds 2 new commands `repo schema openapi check` and `package schema openapi check`. `repo schema openapi check` is intended to power a new breaking changes check on pull requests and the package level command allows plugin developers to quickly see new API breaking changes.They're intended to be used in complement with the existing `repo schema openapi verify` command to validate your OpenAPI spec against a variety of things. +Adds 2 new commands `repo schema openapi diff` and `package schema openapi diff`. `repo schema openapi diff` is intended to power a new breaking changes check on pull requests and the package level command allows plugin developers to quickly see new API breaking changes. They're intended to be used in complement with the existing `repo schema openapi verify` command to validate your OpenAPI spec against a variety of things. diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 3fa16aa512..83266ea9f9 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -80,12 +80,12 @@ function registerPackageCommand(program: Command) { ); openApiCommand - .command('check') + .command('diff') .option('--ignore', 'Ignore linting failures and only log the results.') .option('--json', 'Output the results as JSON') - .option('--since ', 'Check the API against a specific ref') + .option('--since ', 'Diff the API against a specific ref') .action( - lazy(() => import('./package/schema/openapi/check').then(m => m.command)), + lazy(() => import('./package/schema/openapi/diff').then(m => m.command)), ); } @@ -144,17 +144,17 @@ function registerRepoCommand(program: Command) { ); openApiCommand - .command('check') + .command('diff') .description( - 'Check the repository against a specific ref, will run all package `check:api` scripts.', + 'Diff the repository against a specific ref, will run all package `diff` scripts.', ) .option( '--since ', - 'Check the API against a specific ref', + 'Diff the API against a specific ref', 'origin/master', ) .action( - lazy(() => import('./repo/schema/openapi/check').then(m => m.command)), + lazy(() => import('./repo/schema/openapi/diff').then(m => m.command)), ); } diff --git a/packages/repo-tools/src/commands/package/schema/openapi/check.ts b/packages/repo-tools/src/commands/package/schema/openapi/diff.ts similarity index 100% rename from packages/repo-tools/src/commands/package/schema/openapi/check.ts rename to packages/repo-tools/src/commands/package/schema/openapi/diff.ts diff --git a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts b/packages/repo-tools/src/commands/repo/schema/openapi/diff.ts similarity index 90% rename from packages/repo-tools/src/commands/repo/schema/openapi/check.ts rename to packages/repo-tools/src/commands/repo/schema/openapi/diff.ts index 95bd4dffed..02f3db0c43 100644 --- a/packages/repo-tools/src/commands/repo/schema/openapi/check.ts +++ b/packages/repo-tools/src/commands/repo/schema/openapi/diff.ts @@ -54,9 +54,7 @@ export async function command(opts: OptionValues) { ); } - const checkablePackages = packages.filter( - e => e.packageJson.scripts?.['check:api'], - ); + const checkablePackages = packages.filter(e => e.packageJson.scripts?.diff); try { const outputs = { @@ -70,7 +68,7 @@ export async function command(opts: OptionValues) { const sinceCommands = since ? ['--since', since] : []; const { stdout } = await exec( 'yarn', - ['check:api', '--ignore', '--json', ...sinceCommands], + ['diff', '--ignore', '--json', ...sinceCommands], { cwd: pkg.dir, }, @@ -81,12 +79,10 @@ export async function command(opts: OptionValues) { outputs.noop.push(...(result.noop ?? [])); } - for (const pkg of packages.filter( - e => !e.packageJson.scripts?.['check:api'], - )) { + for (const pkg of packages.filter(e => !e.packageJson.scripts?.diff)) { outputs.warning?.push({ apiName: `${pkg.dir}/`, - warning: 'No check:api script found in package.json', + warning: 'No diff script found in package.json', }); } diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 058b9318d7..04a7778926 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -42,8 +42,8 @@ ], "scripts": { "build": "backstage-cli package build", - "check:api": "backstage-repo-tools package schema openapi check", "clean": "backstage-cli package clean", + "diff": "backstage-repo-tools package schema openapi diff", "fuzz": "backstage-repo-tools package schema openapi fuzz --exclude-checks response_schema_conformance", "generate": "backstage-repo-tools package schema openapi generate --server --client-package packages/catalog-client", "lint": "backstage-cli package lint", From 295c05db695f79f7b762773160393832de74486c Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Wed, 17 Apr 2024 22:58:42 +0300 Subject: [PATCH 051/234] feat: support to filter notification recipients after resolving Signed-off-by: Heikki Hellgren --- .changeset/four-cooks-serve.md | 6 ++++++ .../src/service/router.ts | 19 +++++++++++++++++-- plugins/notifications-node/api-report.md | 1 + .../src/service/DefaultNotificationService.ts | 9 +++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 .changeset/four-cooks-serve.md diff --git a/.changeset/four-cooks-serve.md b/.changeset/four-cooks-serve.md new file mode 100644 index 0000000000..864807cc49 --- /dev/null +++ b/.changeset/four-cooks-serve.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications-backend': patch +'@backstage/plugin-notifications-node': patch +--- + +Support for filtering entities from notification recipients after resolving them from the recipients diff --git a/plugins/notifications-backend/src/service/router.ts b/plugins/notifications-backend/src/service/router.ts index 30829a578b..585b1ad104 100644 --- a/plugins/notifications-backend/src/service/router.ts +++ b/plugins/notifications-backend/src/service/router.ts @@ -94,6 +94,7 @@ export async function createRouter( const getUsersForEntityRef = async ( entityRef: string | string[] | null, + excludeEntityRefs: string | string[], ): Promise => { const { token } = await auth.getPluginRequestToken({ onBehalfOf: await auth.getOwnServiceCredentials(), @@ -114,13 +115,23 @@ export async function createRouter( }, { token }, ); + + const excluded = Array.isArray(excludeEntityRefs) + ? excludeEntityRefs + : [excludeEntityRefs]; + const mapEntity = async (entity: Entity | undefined): Promise => { if (!entity) { return []; } + const currentEntityRef = stringifyEntityRef(entity); + if (excluded.includes(currentEntityRef)) { + return []; + } + if (isUserEntity(entity)) { - return [stringifyEntityRef(entity)]; + return [currentEntityRef]; } else if (isGroupEntity(entity) && entity.relations) { const users = entity.relations .filter(relation => relation.type === RELATION_HAS_MEMBER) @@ -162,6 +173,7 @@ export async function createRouter( const u = await mapEntity(entity); users.push(...u); } + return users; }; @@ -482,7 +494,10 @@ export async function createRouter( const entityRef = recipients.entityRef; try { - users = await getUsersForEntityRef(entityRef); + users = await getUsersForEntityRef( + entityRef, + recipients.excludeEntityRef ?? [], + ); } catch (e) { logger.error(`Failed to resolve notification receivers: ${e}`); throw new InputError('Failed to resolve notification receivers', e); diff --git a/plugins/notifications-node/api-report.md b/plugins/notifications-node/api-report.md index a16c141c41..1914b562d1 100644 --- a/plugins/notifications-node/api-report.md +++ b/plugins/notifications-node/api-report.md @@ -41,6 +41,7 @@ export type NotificationRecipients = | { type: 'entity'; entityRef: string | string[]; + excludeEntityRef?: string | string[]; } | { type: 'broadcast'; diff --git a/plugins/notifications-node/src/service/DefaultNotificationService.ts b/plugins/notifications-node/src/service/DefaultNotificationService.ts index a041dd0ecf..950f42ae83 100644 --- a/plugins/notifications-node/src/service/DefaultNotificationService.ts +++ b/plugins/notifications-node/src/service/DefaultNotificationService.ts @@ -28,7 +28,16 @@ export type NotificationServiceOptions = { export type NotificationRecipients = | { type: 'entity'; + /** + * Entity references to send the notifications to + */ entityRef: string | string[]; + /** + * Optional entity reference(s) to filter out of the resolved recipients. + * Usually the currently logged-in user for preventing sending notification + * of user action to him/herself. + */ + excludeEntityRef?: string | string[]; } | { type: 'broadcast' }; From 841ee6aa0d947895c0f868675a46dd652bd1dcdc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:27:44 +0000 Subject: [PATCH 052/234] fix(deps): update dependency yauzl to v3.1.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 8f9efffc32..303bb33a5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -42245,12 +42245,12 @@ __metadata: linkType: hard "yauzl@npm:^3.0.0": - version: 3.1.2 - resolution: "yauzl@npm:3.1.2" + version: 3.1.3 + resolution: "yauzl@npm:3.1.3" dependencies: buffer-crc32: ~0.2.3 pend: ~1.2.0 - checksum: 8ed80d4b2dfe749498b77120089bd216f50bbcd463a7a719e84cd92d1ba9ffc64eb90f3a8590f93521fccfc6dad5c757e4cef9aef3d78d94890aab2194450005 + checksum: 5b782f6e99361a9c715e7a82e7aae3d983b6ddff6ebe3a66d2dd3f4ee601ec41c55fa88587bf6de0acbc013aac0b2cac84f9f0cd48372fd5329ee5e273f46f2c languageName: node linkType: hard From 9e59823f9bd977b8ecf894f29d94513bfe2690fc Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Thu, 18 Apr 2024 19:00:26 +0200 Subject: [PATCH 053/234] Add new parameter to set the visibility of the repository created Signed-off-by: cmoulliard --- .../package.json | 2 +- .../src/actions/gitea.examples.ts | 18 +++++ .../src/actions/gitea.test.ts | 73 +++++++++++++++++++ .../src/actions/gitea.ts | 23 +++++- 4 files changed, 114 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 1751a1a6ce..1d8ad190bc 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.1.7", + "version": "0.1.8", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts index ccf0f4ddf6..dd4d038ead 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.examples.ts @@ -50,6 +50,23 @@ export const examples: TemplateExample[] = [ ], }), }, + { + description: 'Initializes a private Gitea repository ', + example: yaml.stringify({ + steps: [ + { + id: 'publish', + action: 'publish:gitea', + name: 'Publish to Gitea', + input: { + repoUrl: 'gitea.com?repo=repo&owner=owner', + defaultBranch: 'main', + repoVisibility: 'private', + }, + }, + ], + }), + }, { description: 'Initializes a Gitea repository with a default Branch, if not set defaults to main', @@ -150,6 +167,7 @@ export const examples: TemplateExample[] = [ gitAuthorName: 'John Doe', gitAuthorEmail: 'johndoe@email.com', sourcePath: 'repository/', + repoVisibility: 'public', }, }, ], diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts index 8675b1ff97..8a72265df0 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.test.ts @@ -53,6 +53,13 @@ describe('publish:gitea', () => { description, }, }); + const mockContextWithPublicRepoVisibility = createMockActionContext({ + input: { + repoUrl: 'gitea.com?repo=repo&owner=owner', + description, + private: false, + }, + }); const server = setupServer(); setupRequestMockHandlers(server); @@ -111,6 +118,7 @@ describe('publish:gitea', () => { ); expect(req.body).toEqual({ name: 'repo', + private: false, description, }); return res( @@ -148,6 +156,71 @@ describe('publish:gitea', () => { ); }); + it('should create a Gitea repository where visibility is public', async () => { + server.use( + rest.get('https://gitea.com/api/v1/orgs/org1', (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + id: 1, + name: 'org1', + visibility: 'public', + repo_admin_change_team_access: false, + username: 'org1', + }), + ); + }), + rest.get( + 'https://gitea.com/org1/repo/src/branch/main', + (_req, res, ctx) => { + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }, + ), + rest.post('https://gitea.com/api/v1/orgs/org1/repos', (req, res, ctx) => { + // Basic auth must match the user and password defined part of the config + expect(req.headers.get('Authorization')).toBe( + 'basic Z2l0ZWFfdXNlcjpnaXRlYV9wYXNzd29yZA==', + ); + expect(req.body).toEqual({ + name: 'repo', + private: false, + description, + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + await action.handler({ + ...mockContextWithPublicRepoVisibility, + input: { + ...mockContextWithPublicRepoVisibility.input, + repoUrl: 'gitea.com?repo=repo&owner=org1', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContextWithPublicRepoVisibility.workspacePath, + remoteUrl: 'https://gitea.com/org1/repo.git', + defaultBranch: 'main', + auth: { username: 'gitea_user', password: 'gitea_password' }, + logger: mockContextWithPublicRepoVisibility.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: { + email: undefined, + name: undefined, + }, + }); + }); + afterEach(() => { jest.resetAllMocks(); }); diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts index 089f9b2669..03ac364538 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts @@ -96,10 +96,11 @@ const createGiteaProject = async ( options: { projectName: string; owner?: string; + repoVisibility?: string; description: string; }, ): Promise => { - const { projectName, description, owner } = options; + const { projectName, description, owner, repoVisibility } = options; /* Several options exist to create a repository using either the user or organisation @@ -112,12 +113,23 @@ const createGiteaProject = async ( This is the default scenario that we support currently */ let response: Response; + let visibility: boolean; + + if (repoVisibility === 'private') { + visibility = true; + } else if (repoVisibility === 'public') { + visibility = false; + } else { + // Provide a default value if repoVisibility is neither "private" nor "public" + visibility = false; + } const postOptions: RequestInit = { method: 'POST', body: JSON.stringify({ name: projectName, description, + private: visibility, }), headers: { ...getGiteaRequestOptions(config).headers, @@ -202,6 +214,7 @@ export function createPublishGiteaAction(options: { repoUrl: string; description: string; defaultBranch?: string; + repoVisibility?: 'private' | 'public'; gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; @@ -229,6 +242,12 @@ export function createPublishGiteaAction(options: { type: 'string', description: `Sets the default branch on the repository. The default value is 'main'`, }, + repoVisibility: { + title: 'Repository Visibility', + description: `Sets the visibility of the repository. The default value is 'public'.`, + type: 'string', + enum: ['private', 'public'], + }, gitCommitMessage: { title: 'Git Commit Message', type: 'string', @@ -274,6 +293,7 @@ export function createPublishGiteaAction(options: { repoUrl, description, defaultBranch = 'main', + repoVisibility = 'public', gitAuthorName, gitAuthorEmail, gitCommitMessage = 'initial commit', @@ -301,6 +321,7 @@ export function createPublishGiteaAction(options: { await createGiteaProject(integrationConfig.config, { description, + repoVisibility, owner: owner, projectName: repo, }); From 6eaba9fb98179ed528693c9d3a8c0ab060492cc7 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 19 Apr 2024 12:16:25 +0200 Subject: [PATCH 054/234] Review the README to better explain how to integrate gitea in a template. Include parseRepoUrl to get the host name provided by the user. Add a warning to tell to the user that they should define the allowedOwners according to the gitea organisation. Include the new field: repoVisibility Signed-off-by: cmoulliard --- .../scaffolder-backend-module-gitea/README.md | 78 +++++++++---------- 1 file changed, 38 insertions(+), 40 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitea/README.md b/plugins/scaffolder-backend-module-gitea/README.md index 7eff6130db..7133f33393 100644 --- a/plugins/scaffolder-backend-module-gitea/README.md +++ b/plugins/scaffolder-backend-module-gitea/README.md @@ -10,6 +10,13 @@ To use this action, you will have to add the package using the following command yarn --cwd packages/backend add @backstage/plugin-scaffolder-backend-module-gitea ``` +Alternatively, if you use the new backend system, then register it like this: + +```typescript +// packages/backend/src/index.ts +backend.add(import('@backstage/plugin-scaffolder-backend-module-gitea')); +``` + Configure the action (if not yet done): (you can check the [docs](https://backstage.io/docs/features/software-templates/writing-custom-actions#registering-custom-actions) to see all options): @@ -27,29 +34,29 @@ integrations: password: '' ``` -**Important**: As backstage will issue HTTPS/TLS requests to the gitea instance, it is needed to configure `gitea` with a valid certificate or at least with a -self-signed certificate `gitea cert --host localhost -ca` trusted by a CA authority. Don't forget to set the env var `NODE_EXTRA_CA_CERTS` to point to the CA file before launching backstage ! +**Important**: As backstage will issue `HTTPS/TLS` requests to the gitea instance, it is needed to configure `gitea` with a valid certificate or at least with a +self-signed certificate `gitea cert --host localhost -ca` trusted by a CA authority. Don't forget to set the env var `NODE_EXTRA_CA_CERTS` to point to the CA file before launching backstage or to ignore using the following Node.js parameter: `NODE_TLS_REJECT_UNAUTHORIZED=0` ! When done, you can create a template which: -- Declare the `RepoUrlPicker` within the `spec/parameters` section to select the gitea hosts -- Include a step able to publish by example the newly generated project using the action `action: publish:gitea` +- Declare the `RepoUrlPicker` within the `spec/parameters` section to select the gitea host and to provide the name of the repository +- Add an enum list allowing the user to define the visibility about the repository to be created: `public` or `private`. If this field is omitted, `public` is then used by the action. +- Include in a step the action: `publish:gitea` + +**Warning**: The list of the `allowedOwners` of the `repoUrlPicker` must match the list of the `organizations` which are available on the gitea host ! ```yaml -apiVersion: scaffolder.backstage.io/v1beta3 kind: Template metadata: - name: quarkus-web-template - title: Quarkus Hello world - description: Create a simple microservice using Quarkus - tags: - - java - - quarkus + name: simple-gitea-project + title: Create a gitea repository + description: Create a gitea repository spec: - owner: quarkus + owner: guests type: service + parameters: - - title: Git repository Information + - title: Choose a location required: - repoUrl properties: @@ -58,42 +65,33 @@ spec: type: string ui:field: RepoUrlPicker ui:options: + allowedOwners: + - qteam + - qshift allowedHosts: - - gitea.: - - localhost: + - gitea.localtest.me:3333 + + repoVisibility: + title: Visibility of the repository + type: string + default: 'public' + enum: + - 'public' + - 'private' + enumNames: + - 'public' + - 'private' steps: - - id: template - name: Generating component - action: fetch:template - input: - url: ./skeleton - values: - name: ${{ parameters.name }} - + ... - id: publish name: Publishing to a gitea git repository action: publish:gitea input: - description: This is ${{ parameters.component_id }} + description: This is ${{ parameters.repoUrl | parseRepoUrl | pick('repo') }} + repoVisibility: ${{ parameters.repoVisibility }} repoUrl: ${{ parameters.repoUrl }} defaultBranch: main - - - id: register - if: ${{ parameters.dryRun !== true }} - name: Register - action: catalog:register - input: - repoContentsUrl: ${{ steps['publish'].output.repoContentsUrl }} - catalogInfoPath: 'main/catalog-info.yaml' - - output: - links: - - title: Source Code Repository - url: ${{ steps.publish.output.remoteUrl }} - - title: Open Component in catalog - icon: catalog - entityRef: ${{ steps.register.output.entityRef }} ``` Access the newly gitea repository created using the `repoContentsUrl` ;-) From 64e36f5f473573cfe5bf96a2ed31ad828a422d4e Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 19 Apr 2024 12:18:12 +0200 Subject: [PATCH 055/234] Changed how we log the error using the message and cause.code. Otherwise it is not possible to know if the gitea server replies Signed-off-by: cmoulliard --- plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts index 03ac364538..deda1b77bb 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts @@ -81,7 +81,7 @@ const checkGiteaOrg = async ( ); } catch (e) { throw new Error( - `Unable to get the Organization: ${owner}; Error cause: ${e.cause.message}`, + `Unable to get the Organization: ${owner}; Error cause: ${e.message}, code: ${e.cause.code}`, ); } if (response.status !== 200) { From 554af738c52c29e8af394e9ac7dc5148bcff9e1d Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 19 Apr 2024 12:25:27 +0200 Subject: [PATCH 056/234] Add the changeset file Signed-off-by: cmoulliard --- .changeset/cool-elephants-march.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/cool-elephants-march.md diff --git a/.changeset/cool-elephants-march.md b/.changeset/cool-elephants-march.md new file mode 100644 index 0000000000..2396f9553d --- /dev/null +++ b/.changeset/cool-elephants-march.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitea': patch +--- + +Allow defining `repoVisibility` field for the action `publish:gitea` From af6c03e2481fdfa600d50bf341eda47331d8f598 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Fri, 19 Apr 2024 12:49:45 +0200 Subject: [PATCH 057/234] Updating the API report as a new property has been added Signed-off-by: cmoulliard --- plugins/scaffolder-backend-module-gitea/api-report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder-backend-module-gitea/api-report.md b/plugins/scaffolder-backend-module-gitea/api-report.md index 373cc6258c..b5e20b7b13 100644 --- a/plugins/scaffolder-backend-module-gitea/api-report.md +++ b/plugins/scaffolder-backend-module-gitea/api-report.md @@ -18,6 +18,7 @@ export function createPublishGiteaAction(options: { repoUrl: string; description: string; defaultBranch?: string | undefined; + repoVisibility?: 'private' | 'public' | undefined; gitCommitMessage?: string | undefined; gitAuthorName?: string | undefined; gitAuthorEmail?: string | undefined; From c3fbfe67690111cf3c6adbf46ea23fdc7b1d7aed Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Mon, 22 Apr 2024 09:59:43 +0200 Subject: [PATCH 058/234] Don't bump the package version as the changeset will make the job Signed-off-by: cmoulliard --- plugins/scaffolder-backend-module-gitea/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 1d8ad190bc..1751a1a6ce 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.1.8", + "version": "0.1.7", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" From 6574d91f700f968c848be721844065e9f869240a Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Mon, 22 Apr 2024 10:48:12 +0200 Subject: [PATCH 059/234] Highlighting the enum word Signed-off-by: cmoulliard --- plugins/scaffolder-backend-module-gitea/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-gitea/README.md b/plugins/scaffolder-backend-module-gitea/README.md index 7133f33393..09b43fea1e 100644 --- a/plugins/scaffolder-backend-module-gitea/README.md +++ b/plugins/scaffolder-backend-module-gitea/README.md @@ -40,7 +40,7 @@ self-signed certificate `gitea cert --host localhost -ca` trusted by a CA author When done, you can create a template which: - Declare the `RepoUrlPicker` within the `spec/parameters` section to select the gitea host and to provide the name of the repository -- Add an enum list allowing the user to define the visibility about the repository to be created: `public` or `private`. If this field is omitted, `public` is then used by the action. +- Add an `enum` list allowing the user to define the visibility about the repository to be created: `public` or `private`. If this field is omitted, `public` is then used by the action. - Include in a step the action: `publish:gitea` **Warning**: The list of the `allowedOwners` of the `repoUrlPicker` must match the list of the `organizations` which are available on the gitea host ! From c6cb568f390fef2c35ad161bb24a0bab7530b9f1 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Wed, 27 Mar 2024 16:08:20 +0200 Subject: [PATCH 060/234] feat: add missing lifecycle monitoring for catalog, search and server Signed-off-by: Heikki Hellgren --- .changeset/chilly-adults-sing.md | 5 +++++ .changeset/late-planes-fix.md | 6 ++++++ .../src/service/CatalogPlugin.ts | 6 ++++-- .../search-backend-node/api-report-alpha.md | 1 + plugins/search-backend-node/src/alpha.ts | 20 ++++++++++++++++--- plugins/search-backend/src/alpha.ts | 16 +++++++++++---- 6 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 .changeset/chilly-adults-sing.md create mode 100644 .changeset/late-planes-fix.md diff --git a/.changeset/chilly-adults-sing.md b/.changeset/chilly-adults-sing.md new file mode 100644 index 0000000000..82f4cb88b2 --- /dev/null +++ b/.changeset/chilly-adults-sing.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Add lifecycle monitoring for the catalog processing diff --git a/.changeset/late-planes-fix.md b/.changeset/late-planes-fix.md new file mode 100644 index 0000000000..a57d7ec1d4 --- /dev/null +++ b/.changeset/late-planes-fix.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-search-backend-node': patch +'@backstage/plugin-search-backend': patch +--- + +Add lifecycle monitoring for the search index registry diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index 039d9368bf..dd44e20add 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -201,7 +201,7 @@ export const catalogPlugin = createBackendPlugin({ permissions: coreServices.permissions, database: coreServices.database, httpRouter: coreServices.httpRouter, - lifecycle: coreServices.lifecycle, + lifecycle: coreServices.rootLifecycle, scheduler: coreServices.scheduler, discovery: coreServices.discovery, auth: coreServices.auth, @@ -253,7 +253,9 @@ export const catalogPlugin = createBackendPlugin({ const { processingEngine, router } = await builder.build(); - await processingEngine.start(); + lifecycle.addStartupHook(async () => { + await processingEngine.start(); + }); lifecycle.addShutdownHook(() => processingEngine.stop()); httpRouter.use(router); }, diff --git a/plugins/search-backend-node/api-report-alpha.md b/plugins/search-backend-node/api-report-alpha.md index c8e5cbb65d..8e35e70f65 100644 --- a/plugins/search-backend-node/api-report-alpha.md +++ b/plugins/search-backend-node/api-report-alpha.md @@ -34,6 +34,7 @@ export const searchIndexRegistryExtensionPoint: ExtensionPoint; start(options: SearchIndexServiceStartOptions): Promise; + stop(): Promise; } // @alpha diff --git a/plugins/search-backend-node/src/alpha.ts b/plugins/search-backend-node/src/alpha.ts index 43bda67658..331f0ae304 100644 --- a/plugins/search-backend-node/src/alpha.ts +++ b/plugins/search-backend-node/src/alpha.ts @@ -22,11 +22,11 @@ import { LoggerService, } from '@backstage/backend-plugin-api'; import { DocumentTypeInfo } from '@backstage/plugin-search-common'; - import { IndexBuilder, RegisterCollatorParameters, RegisterDecoratorParameters, + Scheduler, SearchEngine, } from '@backstage/plugin-search-backend-node'; @@ -49,6 +49,11 @@ export interface SearchIndexService { * Starts indexing process */ start(options: SearchIndexServiceStartOptions): Promise; + + /** + * Stops indexing process + */ + stop(): Promise; /** * Returns an index types list. */ @@ -81,8 +86,9 @@ type DefaultSearchIndexServiceOptions = { * Reponsible for register the indexing task and start the schedule. */ class DefaultSearchIndexService implements SearchIndexService { - private logger: LoggerService; + private readonly logger: LoggerService; private indexBuilder: IndexBuilder | null = null; + private scheduler: Scheduler | null = null; private constructor(options: DefaultSearchIndexServiceOptions) { this.logger = options.logger; @@ -107,7 +113,15 @@ class DefaultSearchIndexService implements SearchIndexService { ); const { scheduler } = await this.indexBuilder?.build(); - scheduler.start(); + this.scheduler = scheduler; + this.scheduler!.start(); + } + + async stop(): Promise { + if (this.scheduler) { + this.scheduler.stop(); + this.scheduler = null; + } } getDocumentTypes(): Record { diff --git a/plugins/search-backend/src/alpha.ts b/plugins/search-backend/src/alpha.ts index 0ca5777102..a17b542903 100644 --- a/plugins/search-backend/src/alpha.ts +++ b/plugins/search-backend/src/alpha.ts @@ -98,6 +98,7 @@ export default createBackendPlugin({ auth: coreServices.auth, http: coreServices.httpRouter, httpAuth: coreServices.httpAuth, + lifecycle: coreServices.rootLifecycle, searchIndexService: searchIndexServiceRef, }, async init({ @@ -108,6 +109,7 @@ export default createBackendPlugin({ auth, http, httpAuth, + lifecycle, searchIndexService, }) { let searchEngine = searchEngineRegistry.getSearchEngine(); @@ -120,10 +122,16 @@ export default createBackendPlugin({ const collators = searchIndexRegistry.getCollators(); const decorators = searchIndexRegistry.getDecorators(); - await searchIndexService.start({ - searchEngine, - collators, - decorators, + lifecycle.addStartupHook(async () => { + await searchIndexService.start({ + searchEngine: searchEngine!, + collators, + decorators, + }); + }); + + lifecycle.addShutdownHook(async () => { + await searchIndexService.stop(); }); const router = await createRouter({ From f4856e9f9b1d950565a32e431a4c835a82354e91 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 22 Apr 2024 09:26:20 -0400 Subject: [PATCH 061/234] add api report Signed-off-by: aramissennyeydd --- packages/repo-tools/cli-report.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/repo-tools/cli-report.md b/packages/repo-tools/cli-report.md index 68d679fb06..a06f426baf 100644 --- a/packages/repo-tools/cli-report.md +++ b/packages/repo-tools/cli-report.md @@ -172,14 +172,14 @@ Commands: lint [options] [paths...] test [options] [paths...] fuzz [options] - check [options] + diff [options] help [command] ``` -### `backstage-repo-tools repo schema openapi check` +### `backstage-repo-tools repo schema openapi diff` ``` -Usage: backstage-repo-tools repo schema openapi check [options] +Usage: backstage-repo-tools repo schema openapi diff [options] Options: --since From 335151a281f76f9462c8cb5761e94c0b16b0681b Mon Sep 17 00:00:00 2001 From: web-next-automation Date: Sun, 7 Apr 2024 19:26:17 -0400 Subject: [PATCH 062/234] fix: adjust the backend plugin docs for new backend Signed-off-by: web-next-automation Signed-off-by: aramissennyeydd --- docs/plugins/backend-plugin.md | 145 ++++++++++++++++++--------------- 1 file changed, 78 insertions(+), 67 deletions(-) diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 497a6be9b9..827d82c4cf 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -44,7 +44,7 @@ cd plugins/carmen-backend yarn start ``` -> Note: this documentation assumes you are using the latest version of Backstage and the new backend system. If you are not please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md) +> Note: this documentation assumes you are using the latest version of Backstage and the new backend system. If you are not, please upgrade and migrate your backend using the [Migration Guide](../backend-system/building-backends/08-migrating.md) This will think for a bit, and then say `Listening on :7007`. In a different terminal window, now run @@ -72,38 +72,19 @@ to your backend. yarn --cwd packages/backend add @internal/plugin-carmen-backend@^0.1.0 # Change this to match the plugin's package.json ``` -Create a new file named `packages/backend/src/plugins/carmen.ts`, and add the -following to it +Update `packages/backend/src/index` with the following, ```ts -import { createRouter } from '@internal/plugin-carmen-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; +const backend = createBackend(); -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - // Here is where you will add all of the required initialization code that - // your backend plugin needs to be able to start! - - // The env contains a lot of goodies, but our router currently only - // needs a logger - return await createRouter({ - logger: env.logger, - }); -} -``` - -And finally, wire this into the overall backend router. Edit -`packages/backend/src/index.ts`: - -```ts -import carmen from './plugins/carmen'; // ... -async function main() { - // ... - const carmenEnv = useHotMemoize(module, () => createEnv('carmen')); - apiRouter.use('/carmen', await carmen(carmenEnv)); + +// highlight-add-next-line +backend.add(import('@internal/plugin-carmen-backend')); + +// ... + +backend.start(); ``` After you start the backend (e.g. using `yarn start-backend` from the repo @@ -122,30 +103,48 @@ The Backstage backend comes with a builtin facility for SQL database access. Most plugins that have persistence needs will choose to make use of this facility, so that Backstage operators can manage database needs uniformly. -As part of the environment object that is passed to your `createPlugin` -function, there is a `database` field. You can use that to get a -[Knex](http://knexjs.org/) connection object. +You can access this by adding a dependency on the `coreServices.database` service. +That will give you a [Knex](http://knexjs.org/) connection object. -```ts -// in packages/backend/src/plugins/carmen.ts -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const db: Knex = await env.database.getClient(); - - // You will then pass this client into your actual plugin implementation - // code, maybe similar to the following: - const model = new CarmenDatabaseModel(db); - return await createRouter({ - model: model, - logger: env.logger, - }); -} +```ts title="plugins/carmen-backend/src/plugin.ts" +export const carmenPlugin = createBackendPlugin({ + pluginId: 'carmenPlugin', + register(env) { + env.registerInit({ + deps: { + httpRouter: coreServices.httpRouter, + logger: coreServices.logger, + // highlight-next-line + database: coreServices.database, + }, + async init({ + httpRouter, + logger, + // highlight-next-line + database, + }) { + // You will then pass this client into your actual plugin implementation + // code, maybe similar to the following: + const model = new CarmenDatabaseModel(db); + httpRouter.use( + await createRouter({ + // highlight-next-line + model, + logger, + }), + ); + httpRouter.addAuthPolicy({ + path: '/health', + allow: 'unauthenticated', + }); + }, + }); + }, +}); ``` -You may note that the `getClient` call has no parameters. This is because all -plugin database needs are configured under the `backend.database` config key of -your `app-config.yaml`. The framework may even make sure behind the scenes that +All plugin database needs are configured under the `backend.database` config key +of your `app-config.yaml`. The framework may even make sure behind the scenes that the logical database is created automatically if it doesn't exist, based on rules that the Backstage operator decides on. @@ -159,24 +158,36 @@ database.. ## Making Use of the User's Identity -The Backstage backend comes with a facility for retrieving the identity of the -logged in user. +The Backstage backend also offers a core service to access the user's identity. You can access it through the `coreServices.identity` dependency. -As part of the environment object that is passed to your `createPlugin` -function, there is a `identity` field. You can use that to get an identity -from the request. - -```ts -// in packages/backend/src/plugins/carmen.ts -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - model: model, - logger: env.logger, - identity: env.identity, - }); -} +```ts title="plugins/carmen-backend/src/plugin.ts" +export const carmenPlugin = createBackendPlugin({ + pluginId: 'carmenPlugin', + register(env) { + env.registerInit({ + deps: { + httpRouter: coreServices.httpRouter, + logger: coreServices.logger, + // highlight-next-line + identity: coreServices.identity, + }, + async init({ + httpRouter, + logger, + // highlight-next-line + identity, + }) { + httpRouter.use( + await createRouter({ + // highlight-next-line + identity, + logger, + }), + ); + }, + }); + }, +}); ``` The plugin can then extract the identity from the request. From cf7f8d7836aa84faeb85137350c78c5b8b6c92a1 Mon Sep 17 00:00:00 2001 From: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Date: Mon, 8 Apr 2024 21:10:20 -0400 Subject: [PATCH 063/234] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Aramis Sennyey <159921952+aramissennyeydd@users.noreply.github.com> Signed-off-by: aramissennyeydd --- docs/plugins/backend-plugin.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 827d82c4cf..a47df2a106 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -108,7 +108,7 @@ That will give you a [Knex](http://knexjs.org/) connection object. ```ts title="plugins/carmen-backend/src/plugin.ts" export const carmenPlugin = createBackendPlugin({ - pluginId: 'carmenPlugin', + pluginId: 'carmen', register(env) { env.registerInit({ deps: { @@ -125,7 +125,7 @@ export const carmenPlugin = createBackendPlugin({ }) { // You will then pass this client into your actual plugin implementation // code, maybe similar to the following: - const model = new CarmenDatabaseModel(db); + const model = new CarmenDatabaseModel(database); httpRouter.use( await createRouter({ // highlight-next-line From 7c6d8600c9a9a21e5d9bc7bb72a97f7ca3d579c9 Mon Sep 17 00:00:00 2001 From: web-next-automation Date: Mon, 8 Apr 2024 21:29:12 -0400 Subject: [PATCH 064/234] doc updates Signed-off-by: web-next-automation Signed-off-by: aramissennyeydd --- docs/plugins/backend-plugin.md | 107 +++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index a47df2a106..24b6a76fbc 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -97,6 +97,109 @@ curl localhost:7007/api/carmen/health This should return `{"status":"ok"}` like before. Success! +## Secure by Default + +In 1.25, Backstage started moving to a secure by default model for plugins. This means that network requests to plugins will by default not allow unauthenticated users. Let's take a deeper look at the above curl request which should allow unauthenticated access. + +The actual endpoint that is being called is defined in + +```ts title="plugins/carmen-backend/src/service/router.ts" +export async function createRouter( + options: RouterOptions, +): Promise { + const { logger } = options; + + const router = Router(); + router.use(express.json()); + + // highlight-start + router.get('/health', (_, response) => { + logger.info('PONG!'); + response.json({ status: 'ok' }); + }); + // highlight-end + + router.use(errorHandler()); + return router; +} +``` + +You'll notice that there is no authentication mechanism defined here, just the route name and response data. That's because the authentication is handled in your plugin definition, + +```ts title="plugins/carmen-backend/src/plugin.ts" +export const carmenPlugin = createBackendPlugin({ + pluginId: 'carmenPlugin', + register(env) { + env.registerInit({ + deps: { + httpRouter: coreServices.httpRouter, + logger: coreServices.logger, + }, + async init({ httpRouter, logger }) { + httpRouter.use( + await createRouter({ + logger, + }), + ); + // highlight-start + httpRouter.addAuthPolicy({ + path: '/health', + allow: 'unauthenticated', + }); + // highlight-end + }, + }); + }, +}); +``` + +This allows requests to this plugin's `/health` endpoint to go through unauthenticated! + +## Using Dependencies + +In the new backend, dependencies are defined statically during registration and then "injected" during initialization. Here's an example of what this looks like, + +```ts title="plugins/carmen-backend/src/plugin.ts" + register(env) { + env.registerInit({ + // highlight-start + deps: { + httpRouter: coreServices.httpRouter, + logger: coreServices.logger, + }, + // highlight-end + // And then you can use them through the options property! + // highlight-next-line + async init({ httpRouter, logger }) { + httpRouter.use( + await createRouter({ + logger, + }), + ); + }, + }); + }, +``` + +Backstage provides a bunch of `coreServices` out of box: + +- **Database**: A database connection layer using knex. +- **Root Config**: Access to the config that Backstage was started with. +- **Logger**: A plugin scoped logger ready for logging! +- **HTTP Router**: An `express`-compatible router for adding HTTP routes to your plugin. +- **User Info**: Personalize your plugin experience for users by getting the currently logged in user. +- **Auth**: Perform low level authentication. +- **HTTP Auth**: HTTP authentication mechanisms, for example, allowing unauthenticated access. See [here](https://backstage.io/docs/backend-system/core-services/http-auth) for more info. +- **Cache**: A plugin-scoped cache. +- **Discovery**: Service discovery mechanism for your plugins. +- **Plugin Metadata**: Introspect your plugin. +- **Lifecycle**: Add hooks to lifecycle events. +- **Permissions**: Authorize requests to specific resources in your plugins. +- **URL Reader**: Authenticated URL calling mechanism. +- **Scheduler**: Schedule jobs to run on a certain cadence. + +If you want to override or create new ones, you can also define your own services much like you create your own plugins. + ## Making Use of a Database The Backstage backend comes with a builtin facility for SQL database access. @@ -133,10 +236,6 @@ export const carmenPlugin = createBackendPlugin({ logger, }), ); - httpRouter.addAuthPolicy({ - path: '/health', - allow: 'unauthenticated', - }); }, }); }, From 40f557c2e497855dcaf1ddfecb854a3d08927896 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 8 Apr 2024 21:36:19 -0400 Subject: [PATCH 065/234] small update Signed-off-by: aramissennyeydd --- docs/plugins/backend-plugin.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 24b6a76fbc..9951b67b18 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -181,6 +181,25 @@ In the new backend, dependencies are defined statically during registration and }, ``` +You can add your own dependencies by adding a named item to the `deps` parameter: + +```ts +deps: { + // highlight-next-line + myDependency: coreServices.rootConfig, +}, +``` + +And then you can access it by referencing it in the `init` block of your plugin definition, + +```ts +async init({ myDependency }) { + // .. +} +``` + +And then you're free to call it and pass it into your router as needed. + Backstage provides a bunch of `coreServices` out of box: - **Database**: A database connection layer using knex. From 55b47216c3bb4d58bcd4d6cd977879320af32f0a Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Tue, 9 Apr 2024 10:41:44 -0400 Subject: [PATCH 066/234] cut down the docs Signed-off-by: aramissennyeydd --- docs/plugins/backend-plugin.md | 186 +++++++++++---------------------- 1 file changed, 63 insertions(+), 123 deletions(-) diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 9951b67b18..a0b0b878cd 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -107,10 +107,7 @@ The actual endpoint that is being called is defined in export async function createRouter( options: RouterOptions, ): Promise { - const { logger } = options; - - const router = Router(); - router.use(express.json()); + // ... // highlight-start router.get('/health', (_, response) => { @@ -119,7 +116,7 @@ export async function createRouter( }); // highlight-end - router.use(errorHandler()); + // ... return router; } ``` @@ -127,30 +124,17 @@ export async function createRouter( You'll notice that there is no authentication mechanism defined here, just the route name and response data. That's because the authentication is handled in your plugin definition, ```ts title="plugins/carmen-backend/src/plugin.ts" -export const carmenPlugin = createBackendPlugin({ - pluginId: 'carmenPlugin', - register(env) { - env.registerInit({ - deps: { - httpRouter: coreServices.httpRouter, - logger: coreServices.logger, - }, - async init({ httpRouter, logger }) { - httpRouter.use( - await createRouter({ - logger, - }), - ); - // highlight-start - httpRouter.addAuthPolicy({ - path: '/health', - allow: 'unauthenticated', - }); - // highlight-end - }, - }); - }, +httpRouter.use( + await createRouter({ + logger, + }), +); +// highlight-start +httpRouter.addAuthPolicy({ + path: '/health', + allow: 'unauthenticated', }); +// highlight-end ``` This allows requests to this plugin's `/health` endpoint to go through unauthenticated! @@ -160,25 +144,19 @@ This allows requests to this plugin's `/health` endpoint to go through unauthent In the new backend, dependencies are defined statically during registration and then "injected" during initialization. Here's an example of what this looks like, ```ts title="plugins/carmen-backend/src/plugin.ts" - register(env) { - env.registerInit({ - // highlight-start - deps: { - httpRouter: coreServices.httpRouter, - logger: coreServices.logger, - }, - // highlight-end - // And then you can use them through the options property! - // highlight-next-line - async init({ httpRouter, logger }) { - httpRouter.use( - await createRouter({ - logger, - }), - ); - }, - }); - }, + +// highlight-start +deps: { + httpRouter: coreServices.httpRouter, + logger: coreServices.logger, +}, +// highlight-end +// And then you can use them through the options property! +// highlight-next-line +async init({ httpRouter, logger }) { + // ... +}, + ``` You can add your own dependencies by adding a named item to the `deps` parameter: @@ -200,24 +178,7 @@ async init({ myDependency }) { And then you're free to call it and pass it into your router as needed. -Backstage provides a bunch of `coreServices` out of box: - -- **Database**: A database connection layer using knex. -- **Root Config**: Access to the config that Backstage was started with. -- **Logger**: A plugin scoped logger ready for logging! -- **HTTP Router**: An `express`-compatible router for adding HTTP routes to your plugin. -- **User Info**: Personalize your plugin experience for users by getting the currently logged in user. -- **Auth**: Perform low level authentication. -- **HTTP Auth**: HTTP authentication mechanisms, for example, allowing unauthenticated access. See [here](https://backstage.io/docs/backend-system/core-services/http-auth) for more info. -- **Cache**: A plugin-scoped cache. -- **Discovery**: Service discovery mechanism for your plugins. -- **Plugin Metadata**: Introspect your plugin. -- **Lifecycle**: Add hooks to lifecycle events. -- **Permissions**: Authorize requests to specific resources in your plugins. -- **URL Reader**: Authenticated URL calling mechanism. -- **Scheduler**: Schedule jobs to run on a certain cadence. - -If you want to override or create new ones, you can also define your own services much like you create your own plugins. +Backstage provides a bunch of `coreServices` out of box, see the more in depth docs [here](https://backstage.io/docs/backend-system/core-services/index). ## Making Use of a Database @@ -229,36 +190,26 @@ You can access this by adding a dependency on the `coreServices.database` servic That will give you a [Knex](http://knexjs.org/) connection object. ```ts title="plugins/carmen-backend/src/plugin.ts" -export const carmenPlugin = createBackendPlugin({ - pluginId: 'carmen', - register(env) { - env.registerInit({ - deps: { - httpRouter: coreServices.httpRouter, - logger: coreServices.logger, - // highlight-next-line - database: coreServices.database, - }, - async init({ - httpRouter, - logger, - // highlight-next-line - database, - }) { - // You will then pass this client into your actual plugin implementation - // code, maybe similar to the following: - const model = new CarmenDatabaseModel(database); - httpRouter.use( - await createRouter({ - // highlight-next-line - model, - logger, - }), - ); - }, - }); - }, -}); +deps: { + // ... + // highlight-next-line + database: coreServices.database, +}, +async init({ + // highlight-next-line + database, +}) { + // You will then pass this client into your actual plugin implementation + // code, maybe similar to the following: + const model = new CarmenDatabaseModel(database); + httpRouter.use( + await createRouter({ + // highlight-next-line + model, + logger, + }), + ); +} ``` All plugin database needs are configured under the `backend.database` config key @@ -279,33 +230,22 @@ database.. The Backstage backend also offers a core service to access the user's identity. You can access it through the `coreServices.identity` dependency. ```ts title="plugins/carmen-backend/src/plugin.ts" -export const carmenPlugin = createBackendPlugin({ - pluginId: 'carmenPlugin', - register(env) { - env.registerInit({ - deps: { - httpRouter: coreServices.httpRouter, - logger: coreServices.logger, - // highlight-next-line - identity: coreServices.identity, - }, - async init({ - httpRouter, - logger, - // highlight-next-line - identity, - }) { - httpRouter.use( - await createRouter({ - // highlight-next-line - identity, - logger, - }), - ); - }, - }); - }, -}); +deps: { + // highlight-next-line + identity: coreServices.identity, +}, +async init({ + // highlight-next-line + identity, +}) { + httpRouter.use( + await createRouter({ + // highlight-next-line + identity, + logger, + }), + ); +} ``` The plugin can then extract the identity from the request. @@ -314,7 +254,7 @@ The plugin can then extract the identity from the request. export async function createRouter( options: RouterOptions, ): Promise { - const router = Router(); + // ... const { identity } = options; router.post('/example', async (req, res) => { From f3b9148f8840ce8fb4041cd3b8ca32cc62a3a1b5 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 22 Apr 2024 09:36:44 -0400 Subject: [PATCH 067/234] update docs for new auth Signed-off-by: aramissennyeydd --- docs/plugins/backend-plugin.md | 44 ++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index a0b0b878cd..9984112f96 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -178,7 +178,7 @@ async init({ myDependency }) { And then you're free to call it and pass it into your router as needed. -Backstage provides a bunch of `coreServices` out of box, see the more in depth docs [here](https://backstage.io/docs/backend-system/core-services/index). +Backstage provides a bunch of `coreServices` out of box, see the more in depth docs [here](../backend-system/core-services/01-index.md). ## Making Use of a Database @@ -231,17 +231,23 @@ The Backstage backend also offers a core service to access the user's identity. ```ts title="plugins/carmen-backend/src/plugin.ts" deps: { - // highlight-next-line - identity: coreServices.identity, + // highlight-start + httpAuth: coreServices.httpAuth, + userInfo: coreServices.userInfo + // highlight-end }, async init({ - // highlight-next-line - identity, + // highlight-start + httpAuth, + userInfo, + // highlight-end }) { httpRouter.use( await createRouter({ - // highlight-next-line - identity, + // highlight-start + httpAuth, + userInfo, + // highlight-end logger, }), ); @@ -251,14 +257,32 @@ async init({ The plugin can then extract the identity from the request. ```ts +export interface RouterOptions { + logger: LoggerService; + // highlight-start + userInfo: UserInfoService; + httpAuth: HttpAuthService; + // highlight-end +} + export async function createRouter( options: RouterOptions, ): Promise { // ... - const { identity } = options; + const { userInfo, httpAuth } = options; - router.post('/example', async (req, res) => { - const userIdentity = await identity.getIdentity({ request: req }); + router.post('/me', async (request, response) => { + const credentials = await httpAuth.credentials(request) + const userInfo = await userInfo.getUserInfo(credentials); + response.json( + { + // The catalog entity ref of the user. + userEntityRef: userInfo.userEntityRef, + + // The list of entities that this user or any teams this user is a part of owns. + ownershipEntityRefs: userInfo.ownershipEntityRefs + }, + ); ... }); ``` From 61d4dc593e73d4fa10ed09c34a222bd180affd4f Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 22 Apr 2024 10:40:03 -0400 Subject: [PATCH 068/234] whoops Signed-off-by: aramissennyeydd --- docs/plugins/backend-plugin.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 9984112f96..46997f0101 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -227,7 +227,7 @@ database.. ## Making Use of the User's Identity -The Backstage backend also offers a core service to access the user's identity. You can access it through the `coreServices.identity` dependency. +The Backstage backend also offers a core service to access the user's identity. You can access it through the `coreServices.httpAuth` and `coreServices.userInfo` dependencies. ```ts title="plugins/carmen-backend/src/plugin.ts" deps: { @@ -272,6 +272,10 @@ export async function createRouter( const { userInfo, httpAuth } = options; router.post('/me', async (request, response) => { + if (!auth.isPrincipal(credentials, 'user')) { + // Block requests that aren't from the user, this can include services or external callers. + response.status(401); + } const credentials = await httpAuth.credentials(request) const userInfo = await userInfo.getUserInfo(credentials); response.json( From db7c4847f2a2d3397e3ccfb62e54d728541870ac Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 22 Apr 2024 10:40:32 -0400 Subject: [PATCH 069/234] return early Signed-off-by: aramissennyeydd --- docs/plugins/backend-plugin.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 46997f0101..384c6ca861 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -274,7 +274,7 @@ export async function createRouter( router.post('/me', async (request, response) => { if (!auth.isPrincipal(credentials, 'user')) { // Block requests that aren't from the user, this can include services or external callers. - response.status(401); + return response.status(401); } const credentials = await httpAuth.credentials(request) const userInfo = await userInfo.getUserInfo(credentials); From f50d970af685f490c3367f74d817269159f29268 Mon Sep 17 00:00:00 2001 From: aramissennyeydd Date: Mon, 22 Apr 2024 10:46:19 -0400 Subject: [PATCH 070/234] inject auth Signed-off-by: aramissennyeydd --- docs/plugins/backend-plugin.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index 384c6ca861..fa27dcf8bb 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -233,13 +233,15 @@ The Backstage backend also offers a core service to access the user's identity. deps: { // highlight-start httpAuth: coreServices.httpAuth, - userInfo: coreServices.userInfo + userInfo: coreServices.userInfo, + auth: coreServices.auth, // highlight-end }, async init({ // highlight-start httpAuth, userInfo, + auth, // highlight-end }) { httpRouter.use( @@ -247,6 +249,7 @@ async init({ // highlight-start httpAuth, userInfo, + auth, // highlight-end logger, }), @@ -262,6 +265,7 @@ export interface RouterOptions { // highlight-start userInfo: UserInfoService; httpAuth: HttpAuthService; + auth: AuthService; // highlight-end } @@ -269,7 +273,7 @@ export async function createRouter( options: RouterOptions, ): Promise { // ... - const { userInfo, httpAuth } = options; + const { userInfo, httpAuth, auth } = options; router.post('/me', async (request, response) => { if (!auth.isPrincipal(credentials, 'user')) { From b649797bf42999d7035d2651938a1d39a25c76ac Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 22 Apr 2024 16:57:31 +0200 Subject: [PATCH 071/234] docs/plugins/backend-plugin: switch to httpAuth Signed-off-by: Patrik Oldsberg --- docs/plugins/backend-plugin.md | 39 ++++++++++++++++------------------ 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index fa27dcf8bb..7a51cb635c 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -234,14 +234,12 @@ deps: { // highlight-start httpAuth: coreServices.httpAuth, userInfo: coreServices.userInfo, - auth: coreServices.auth, // highlight-end }, async init({ // highlight-start httpAuth, userInfo, - auth, // highlight-end }) { httpRouter.use( @@ -249,7 +247,6 @@ async init({ // highlight-start httpAuth, userInfo, - auth, // highlight-end logger, }), @@ -265,32 +262,32 @@ export interface RouterOptions { // highlight-start userInfo: UserInfoService; httpAuth: HttpAuthService; - auth: AuthService; // highlight-end } export async function createRouter( options: RouterOptions, ): Promise { - // ... - const { userInfo, httpAuth, auth } = options; + const { userInfo, httpAuth } = options; + + router.post('/me', async (req, res) => { + const credentials = await httpAuth.credentials(req, { + // This rejects request from non-users. Only use this if your plugin needs to access the + // user identity, most of the time it's enough to just call `httpAuth.credentials(req)` + allow: ['user'], + }); - router.post('/me', async (request, response) => { - if (!auth.isPrincipal(credentials, 'user')) { - // Block requests that aren't from the user, this can include services or external callers. - return response.status(401); - } - const credentials = await httpAuth.credentials(request) const userInfo = await userInfo.getUserInfo(credentials); - response.json( - { - // The catalog entity ref of the user. - userEntityRef: userInfo.userEntityRef, - // The list of entities that this user or any teams this user is a part of owns. - ownershipEntityRefs: userInfo.ownershipEntityRefs - }, - ); - ... + res.json({ + // The catalog entity ref of the user. + userEntityRef: userInfo.userEntityRef, + + // The list of entities that this user or any teams this user is a part of owns. + ownershipEntityRefs: userInfo.ownershipEntityRefs, + }); }); + + // ... +} ``` From c0c0f7cb330f5de73811ac44d7a26587d974f393 Mon Sep 17 00:00:00 2001 From: Charles Moulliard Date: Mon, 22 Apr 2024 17:17:52 +0200 Subject: [PATCH 072/234] Update plugins/scaffolder-backend-module-gitea/README.md Co-authored-by: Ben Lambert Signed-off-by: Charles Moulliard --- plugins/scaffolder-backend-module-gitea/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-gitea/README.md b/plugins/scaffolder-backend-module-gitea/README.md index 09b43fea1e..5c6aa59b66 100644 --- a/plugins/scaffolder-backend-module-gitea/README.md +++ b/plugins/scaffolder-backend-module-gitea/README.md @@ -35,7 +35,7 @@ integrations: ``` **Important**: As backstage will issue `HTTPS/TLS` requests to the gitea instance, it is needed to configure `gitea` with a valid certificate or at least with a -self-signed certificate `gitea cert --host localhost -ca` trusted by a CA authority. Don't forget to set the env var `NODE_EXTRA_CA_CERTS` to point to the CA file before launching backstage or to ignore using the following Node.js parameter: `NODE_TLS_REJECT_UNAUTHORIZED=0` ! +self-signed certificate `gitea cert --host localhost -ca` trusted by a CA authority. Don't forget to set the env var `NODE_EXTRA_CA_CERTS` to point to the CA file before launching backstage or you can set temporarily `NODE_TLS_REJECT_UNAUTHORIZED=0` but this is not recommended for production! When done, you can create a template which: From e6bf85f0cf476c88efd259705b0d7c9bb35178db Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Mon, 22 Apr 2024 18:23:36 +0300 Subject: [PATCH 073/234] feat: allow overriding notifications page properties Signed-off-by: Heikki Hellgren --- .changeset/bright-pumpkins-rule.md | 5 ++++ plugins/notifications/api-report.md | 14 ++++++++- plugins/notifications/dev/index.tsx | 14 +++++++-- .../NotificationsPage/NotificationsPage.tsx | 30 +++++++++++++++++-- plugins/notifications/src/components/index.ts | 1 + 5 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 .changeset/bright-pumpkins-rule.md diff --git a/.changeset/bright-pumpkins-rule.md b/.changeset/bright-pumpkins-rule.md new file mode 100644 index 0000000000..07de4f313c --- /dev/null +++ b/.changeset/bright-pumpkins-rule.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Allow overriding `NotificationsPage` page properties diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index 11c671112a..2c6a4c6790 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -73,7 +73,19 @@ export class NotificationsClient implements NotificationsApi { } // @public (undocumented) -export const NotificationsPage: () => JSX_2.Element; +export const NotificationsPage: ( + props?: NotificationsPageProps | undefined, +) => JSX_2.Element; + +// @public (undocumented) +export type NotificationsPageProps = { + title?: string; + themeId?: string; + subtitle?: string; + tooltip?: string; + type?: string; + typeLink?: string; +}; // @public (undocumented) export const notificationsPlugin: BackstagePlugin< diff --git a/plugins/notifications/dev/index.tsx b/plugins/notifications/dev/index.tsx index f4d9176a70..b601c4b9a4 100644 --- a/plugins/notifications/dev/index.tsx +++ b/plugins/notifications/dev/index.tsx @@ -15,13 +15,21 @@ */ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; -import { NotificationsPage, notificationsPlugin } from '../src/plugin'; -import { NotificationsSidebarItem } from '../src'; +import { + NotificationsPage, + notificationsPlugin, + NotificationsSidebarItem, +} from '../src'; createDevApp() .registerPlugin(notificationsPlugin) .addPage({ - element: , + element: ( + + ), path: '/notifications', }) .addSidebarItem() diff --git a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx index 3069619eef..539db4f61e 100644 --- a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx +++ b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx @@ -37,7 +37,26 @@ import { NotificationSeverity } from '@backstage/plugin-notifications-common'; const ThrottleDelayMs = 2000; -export const NotificationsPage = () => { +/** @public */ +export type NotificationsPageProps = { + title?: string; + themeId?: string; + subtitle?: string; + tooltip?: string; + type?: string; + typeLink?: string; +}; + +export const NotificationsPage = (props?: NotificationsPageProps) => { + const { + title = 'Notifications', + themeId = 'tool', + subtitle, + tooltip, + type, + typeLink, + } = props ?? {}; + const [refresh, setRefresh] = React.useState(false); const { lastSignal } = useSignal('notifications'); const [unreadOnly, setUnreadOnly] = React.useState(true); @@ -113,7 +132,14 @@ export const NotificationsPage = () => { } return ( - + diff --git a/plugins/notifications/src/components/index.ts b/plugins/notifications/src/components/index.ts index 3bca70a215..022ae07198 100644 --- a/plugins/notifications/src/components/index.ts +++ b/plugins/notifications/src/components/index.ts @@ -15,3 +15,4 @@ */ export * from './NotificationsSideBarItem'; export * from './NotificationsTable'; +export type { NotificationsPageProps } from './NotificationsPage'; From 504ca2d99c19ba9b8cea8d8b9614341ffbbb4f22 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Mon, 22 Apr 2024 17:23:40 +0200 Subject: [PATCH 074/234] Rename the boolean visibility to isPrivate Signed-off-by: cmoulliard --- .../src/actions/gitea.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts index deda1b77bb..6b29ff2a4c 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts @@ -113,15 +113,15 @@ const createGiteaProject = async ( This is the default scenario that we support currently */ let response: Response; - let visibility: boolean; + let isPrivate: boolean; if (repoVisibility === 'private') { - visibility = true; + isPrivate = true; } else if (repoVisibility === 'public') { - visibility = false; + isPrivate = false; } else { // Provide a default value if repoVisibility is neither "private" nor "public" - visibility = false; + isPrivate = false; } const postOptions: RequestInit = { @@ -129,7 +129,7 @@ const createGiteaProject = async ( body: JSON.stringify({ name: projectName, description, - private: visibility, + private: isPrivate, }), headers: { ...getGiteaRequestOptions(config).headers, From 8c1ab9e7915e8f68b3c13636a331fe8b2df6b43e Mon Sep 17 00:00:00 2001 From: thinkmo Date: Mon, 22 Apr 2024 21:45:37 +0800 Subject: [PATCH 075/234] fix: Maximum call stack size exceeded Signed-off-by: thinkmo --- .changeset/fix-stackoverflow.md | 5 +++++ .../src/database/IncrementalIngestionDatabaseManager.ts | 8 +++----- .../src/engine/IncrementalIngestionEngine.ts | 4 +++- 3 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 .changeset/fix-stackoverflow.md diff --git a/.changeset/fix-stackoverflow.md b/.changeset/fix-stackoverflow.md new file mode 100644 index 0000000000..9047b8183a --- /dev/null +++ b/.changeset/fix-stackoverflow.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-backend-module-incremental-ingestion': patch +--- + +Fix plugin/incremental-ingestion 'Maximum call stack size exceeded' error when ingest large entities. diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts index e47954f47e..818a93fc6d 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.ts @@ -336,11 +336,9 @@ export class IncrementalIngestionDatabaseManager { .join('ingestions', 'ingestions.id', 'ingestion_marks.ingestion_id') .where('ingestions.id', previousIngestion.id); - removed.push( - ...stale.map(e => { - return { entityRef: e.ref }; - }), - ); + for (const entityRef of stale) { + removed.push({ entityRef: entityRef.ref }); + } } return { total, removed }; diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/engine/IncrementalIngestionEngine.ts b/plugins/catalog-backend-module-incremental-ingestion/src/engine/IncrementalIngestionEngine.ts index 0262dfd418..7a7b1d681d 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/src/engine/IncrementalIngestionEngine.ts +++ b/plugins/catalog-backend-module-incremental-ingestion/src/engine/IncrementalIngestionEngine.ts @@ -322,7 +322,9 @@ export class IncrementalIngestionEngine } } if (doRemoval) { - removed.push(...result.removed); + for (const entityRef of result.removed) { + removed.push(entityRef); + } } } From 074ef90692a5283512086adff7b3c8f19a622633 Mon Sep 17 00:00:00 2001 From: Niall Thomson Date: Mon, 22 Apr 2024 21:33:59 -0600 Subject: [PATCH 076/234] docs: Updating AWS plugins in marketplace Signed-off-by: Niall Thomson --- microsite/data/plugins/aws-amazon-ecs.yaml | 10 ++++++++++ microsite/data/plugins/aws-codebuild.yaml | 10 ++++++++++ microsite/data/plugins/aws-codepipeline.yaml | 10 ++++++++++ microsite/data/plugins/aws-codeservices.yaml | 10 ---------- microsite/data/plugins/aws-proton.yaml | 10 ---------- 5 files changed, 30 insertions(+), 20 deletions(-) create mode 100644 microsite/data/plugins/aws-amazon-ecs.yaml create mode 100644 microsite/data/plugins/aws-codebuild.yaml create mode 100644 microsite/data/plugins/aws-codepipeline.yaml delete mode 100644 microsite/data/plugins/aws-codeservices.yaml delete mode 100644 microsite/data/plugins/aws-proton.yaml diff --git a/microsite/data/plugins/aws-amazon-ecs.yaml b/microsite/data/plugins/aws-amazon-ecs.yaml new file mode 100644 index 0000000000..a94779babd --- /dev/null +++ b/microsite/data/plugins/aws-amazon-ecs.yaml @@ -0,0 +1,10 @@ +--- +title: Amazon Elastic Container Service +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: Infrastructure +description: View resources related to Amazon Elastic Container Service for your components in Backstage. +documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/ecs#readme +iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/amazon-ecs-logo.png +npmPackageName: 'https://www.npmjs.com/package/@aws/amazon-ecs-plugin-for-backstage' +addedDate: '2024-04-22' diff --git a/microsite/data/plugins/aws-codebuild.yaml b/microsite/data/plugins/aws-codebuild.yaml new file mode 100644 index 0000000000..2c12f92dce --- /dev/null +++ b/microsite/data/plugins/aws-codebuild.yaml @@ -0,0 +1,10 @@ +--- +title: AWS CodeBuild +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: CI/CD +description: View resources related to AWS CodeBuild for your components in Backstage. +documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/codebuild#readme +iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/aws-codebuild-logo.png +npmPackageName: 'https://www.npmjs.com/package/@aws/aws-codebuild-plugin-for-backstage' +addedDate: '2024-04-22' diff --git a/microsite/data/plugins/aws-codepipeline.yaml b/microsite/data/plugins/aws-codepipeline.yaml new file mode 100644 index 0000000000..16986b6718 --- /dev/null +++ b/microsite/data/plugins/aws-codepipeline.yaml @@ -0,0 +1,10 @@ +--- +title: AWS CodePipeline +author: Amazon Web Services +authorUrl: https://aws.amazon.com/ +category: CI/CD +description: View resources related to AWS CodePipeline for your components in Backstage. +documentation: https://github.com/awslabs/backstage-plugins-for-aws/tree/main/plugins/codepipeline#readme +iconUrl: https://raw.githubusercontent.com/awslabs/backstage-plugins-for-aws/main/docs/images/logos/aws-codepipeline-logo.png +npmPackageName: 'https://www.npmjs.com/package/@aws/aws-codepipeline-plugin-for-backstage' +addedDate: '2024-04-22' diff --git a/microsite/data/plugins/aws-codeservices.yaml b/microsite/data/plugins/aws-codeservices.yaml deleted file mode 100644 index 34fa335923..0000000000 --- a/microsite/data/plugins/aws-codeservices.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: AWS Code Services -author: Amazon Web Services -authorUrl: https://aws.amazon.com/ -category: CI/CD -description: View resources related to AWS CodePipeline, AWS CodeBuild and AWS CodeDeploy for your components in Backstage. -documentation: https://github.com/awslabs/aws-codeservices-plugins-for-backstage#readme -iconUrl: https://github.com/awslabs/aws-codeservices-plugins-for-backstage/blob/main/docs/images/codepipeline-logo.png?raw=true -npmPackageName: '@aws/aws-codeservices-plugin-for-backstage' -addedDate: '2023-01-17' diff --git a/microsite/data/plugins/aws-proton.yaml b/microsite/data/plugins/aws-proton.yaml deleted file mode 100644 index f66b556d42..0000000000 --- a/microsite/data/plugins/aws-proton.yaml +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: AWS Proton -author: Amazon Web Services -authorUrl: https://aws.amazon.com/ -category: Infrastructure -description: Create and view AWS Proton services for your components in Backstage. -documentation: https://github.com/awslabs/aws-proton-plugins-for-backstage#readme -iconUrl: https://github.com/awslabs/aws-proton-plugins-for-backstage/blob/main/docs/images/proton-logo.png?raw=true -npmPackageName: '@aws/aws-proton-plugin-for-backstage' -addedDate: '2022-06-21' From a2ee4df20a6884d00328556948c81448d1487dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Fri, 22 Mar 2024 08:46:49 +0100 Subject: [PATCH 077/234] feat: Allow GaugeCard to handle multi-line titles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By adding a fullHeightFixedContent variant. Also, add support for a small version. Signed-off-by: Gustaf Räntilä --- .changeset/selfish-walls-visit.md | 5 ++ packages/core-components/api-report.md | 7 +- .../src/components/ProgressBars/Gauge.tsx | 14 +++- .../ProgressBars/GaugeCard.stories.tsx | 76 +++++++++++++++++++ .../src/components/ProgressBars/GaugeCard.tsx | 17 ++++- .../src/layout/InfoCard/InfoCard.tsx | 25 +++++- 6 files changed, 137 insertions(+), 7 deletions(-) create mode 100644 .changeset/selfish-walls-visit.md diff --git a/.changeset/selfish-walls-visit.md b/.changeset/selfish-walls-visit.md new file mode 100644 index 0000000000..8748b3717e --- /dev/null +++ b/.changeset/selfish-walls-visit.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Add a fullHeightFixedContent variant of the GaugeCard, and a small size version. Fixed content will vertically align the gauge in the cards, even when the card titles span across multiple lines. diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 7fd706db43..1e5c1b892f 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -448,6 +448,7 @@ export type GaugeProps = { inverse?: boolean; unit?: string; max?: number; + size?: 'normal' | 'small'; description?: ReactNode; getColor?: GaugePropsGetColor; }; @@ -609,7 +610,11 @@ export type InfoCardClassKey = | 'headerContent'; // @public (undocumented) -export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; +export type InfoCardVariants = + | 'flex' + | 'fullHeight' + | 'fullHeightFixedContent' + | 'gridItem'; // Warning: (ae-forgotten-export) The symbol "ItemCardProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ItemCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/packages/core-components/src/components/ProgressBars/Gauge.tsx b/packages/core-components/src/components/ProgressBars/Gauge.tsx index 088e369e62..87d2bf4f58 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.tsx @@ -19,6 +19,7 @@ import { makeStyles, useTheme } from '@material-ui/core/styles'; import { Circle } from 'rc-progress'; import React, { ReactNode, useEffect, useState } from 'react'; import Box from '@material-ui/core/Box'; +import classNames from 'classnames'; /** @public */ export type GaugeClassKey = @@ -43,6 +44,9 @@ const useStyles = makeStyles( fontWeight: theme.typography.fontWeightBold, color: theme.palette.textContrast, }, + overlaySmall: { + fontSize: theme.typography.pxToRem(25), + }, description: { fontSize: '100%', top: '50%', @@ -68,6 +72,7 @@ export type GaugeProps = { inverse?: boolean; unit?: string; max?: number; + size?: 'normal' | 'small'; description?: ReactNode; getColor?: GaugePropsGetColor; }; @@ -121,7 +126,7 @@ export const getProgressColor: GaugePropsGetColor = ({ export function Gauge(props: GaugeProps) { const [hoverRef, setHoverRef] = useState(null); - const { getColor = getProgressColor } = props; + const { getColor = getProgressColor, size = 'normal' } = props; const classes = useStyles(props); const { palette } = useTheme(); const { value, fractional, inverse, unit, max, description } = { @@ -165,7 +170,12 @@ export function Gauge(props: GaugeProps) { {description && isHovering ? ( {description} ) : ( - + {isNaN(value) ? 'N/A' : `${asActual}${unit}`} )} diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index a0b6faea60..21d023999e 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -175,6 +175,82 @@ export const InfoMessage = () => ( ); +export const AlignedBottom = () => ( + + + + + + + + + + + + + + +); + +export const Small = () => ( + + + + + + + + + + + + + + +); + export const HoverMessage = () => ( diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx index 3442395380..bbb870141b 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx @@ -27,6 +27,7 @@ type Props = { variant?: InfoCardVariants; /** Progress in % specified as decimal, e.g. "0.23" */ progress: number; + size?: 'normal' | 'small'; description?: ReactNode; icon?: ReactNode; inverse?: boolean; @@ -43,6 +44,10 @@ const useStyles = makeStyles( height: '100%', width: 250, }, + rootSmall: { + height: '100%', + width: 160, + }, }, { name: 'BackstageGaugeCard' }, ); @@ -64,6 +69,7 @@ export function GaugeCard(props: Props) { description, icon, variant, + size = 'normal', getColor, } = props; @@ -75,15 +81,22 @@ export function GaugeCard(props: Props) { }; return ( - + - + ); diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index c80e71b609..9fd72e50b6 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -46,6 +46,10 @@ const useStyles = makeStyles( header: { padding: theme.spacing(2, 2, 2, 2.5), }, + headerFixedContent: { + flexGrow: 1, + alignItems: 'flex-start', + }, headerTitle: { fontWeight: theme.typography.fontWeightBold, }, @@ -87,6 +91,11 @@ const VARIANT_STYLES = { flexDirection: 'column', height: '100%', }, + fullHeightFixedContent: { + display: 'flex', + flexDirection: 'column', + height: '100%', + }, gridItem: { display: 'flex', flexDirection: 'column', @@ -102,6 +111,9 @@ const VARIANT_STYLES = { fullHeight: { flex: 1, }, + fullHeightFixedContent: { + flex: '0 1 0%', + }, gridItem: { flex: 1, }, @@ -109,7 +121,11 @@ const VARIANT_STYLES = { }; /** @public */ -export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; +export type InfoCardVariants = + | 'flex' + | 'fullHeight' + | 'fullHeightFixedContent' + | 'gridItem'; /** * InfoCard is used to display a paper-styled block on the screen, similar to a panel. @@ -228,7 +244,12 @@ export function InfoCard(props: Props): JSX.Element { {title && ( Date: Fri, 22 Mar 2024 16:09:05 +0100 Subject: [PATCH 078/234] Changed to not having a fixed/full sized header, but allow the content to grow, with an option to align the content to the bottom MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- .changeset/selfish-walls-visit.md | 3 +- packages/core-components/api-report.md | 6 +--- .../ProgressBars/GaugeCard.stories.tsx | 24 +++++++++----- .../src/components/ProgressBars/GaugeCard.tsx | 3 ++ .../src/layout/InfoCard/InfoCard.tsx | 32 ++++++------------- 5 files changed, 31 insertions(+), 37 deletions(-) diff --git a/.changeset/selfish-walls-visit.md b/.changeset/selfish-walls-visit.md index 8748b3717e..64c87a64f5 100644 --- a/.changeset/selfish-walls-visit.md +++ b/.changeset/selfish-walls-visit.md @@ -2,4 +2,5 @@ '@backstage/core-components': patch --- -Add a fullHeightFixedContent variant of the GaugeCard, and a small size version. Fixed content will vertically align the gauge in the cards, even when the card titles span across multiple lines. +Add `alignGauge` prop to the `GaugeCard`, and a small size version. When `alignGauge` is `'bottom'` the gauge will vertically align the gauge in the cards, even when the card titles span across multiple lines. +Add `alignContent` prop to the `InfoCard`, defaulting to `'normal'` with the option of `'bottom'` which vertically aligns the content to the bottom of the card. diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 1e5c1b892f..293695db0c 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -610,11 +610,7 @@ export type InfoCardClassKey = | 'headerContent'; // @public (undocumented) -export type InfoCardVariants = - | 'flex' - | 'fullHeight' - | 'fullHeightFixedContent' - | 'gridItem'; +export type InfoCardVariants = 'flex' | 'fullHeight' | 'gridItem'; // Warning: (ae-forgotten-export) The symbol "ItemCardProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ItemCard" is part of the package's API, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index 21d023999e..12cdef52cb 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -179,7 +179,8 @@ export const AlignedBottom = () => ( ( ( ( ( ( ( ( From 0b9d63a02519628a5a3546376451b6dc3ff33966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Sat, 6 Apr 2024 08:55:14 +0200 Subject: [PATCH 079/234] fix: Minor refactoring of styling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- .../core-components/src/components/ProgressBars/Gauge.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core-components/src/components/ProgressBars/Gauge.tsx b/packages/core-components/src/components/ProgressBars/Gauge.tsx index 87d2bf4f58..e72172fd59 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.tsx @@ -171,10 +171,9 @@ export function Gauge(props: GaugeProps) { {description} ) : ( {isNaN(value) ? 'N/A' : `${asActual}${unit}`} From a7648561962ac8d80c1145aa3b4e488e3063d427 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Sat, 6 Apr 2024 09:28:13 +0200 Subject: [PATCH 080/234] feat: Added subheaderTypographyProps prop to InfoCard, allowing it to be used from GaugeCard. Also made GaugeCard 'small' variant to have smaller text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- .../components/ProgressBars/GaugeCard.stories.tsx | 1 + .../src/components/ProgressBars/GaugeCard.tsx | 13 ++++++------- .../src/layout/InfoCard/InfoCard.tsx | 3 +++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index 12cdef52cb..f7e2eb69c6 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -234,6 +234,7 @@ export const Small = () => ( alignGauge="bottom" size="small" title="Progress" + subheader="With a subheader" progress={0.57} /> diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx index 4e73487983..9f6de8eafe 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx @@ -91,13 +91,12 @@ export function GaugeCard(props: Props) { variant={variant} alignContent={alignGauge} icon={icon} - titleTypographyProps={ - size === 'small' - ? { - variant: 'h6', - } - : undefined - } + titleTypographyProps={{ + ...(size === 'small' ? { variant: 'subtitle2' } : undefined), + }} + subheaderTypographyProps={{ + ...(size === 'small' ? { variant: 'body2' } : undefined), + }} > diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index 1f41a315b8..e80c0d3384 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -155,6 +155,7 @@ export type Props = { className?: string; noPadding?: boolean; titleTypographyProps?: object; + subheaderTypographyProps?: object; }; /** @@ -185,6 +186,7 @@ export function InfoCard(props: Props): JSX.Element { className, noPadding, titleTypographyProps, + subheaderTypographyProps, } = props; const classes = useStyles(); /** @@ -246,6 +248,7 @@ export function InfoCard(props: Props): JSX.Element { action={action} style={{ ...headerStyle }} titleTypographyProps={titleTypographyProps} + subheaderTypographyProps={subheaderTypographyProps} {...headerProps} /> )} From f72f3a076dd911a2f836b5a8b534e6e0fbe0c885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= Date: Sat, 6 Apr 2024 09:32:23 +0200 Subject: [PATCH 081/234] fix: Made the subhead in InfoCard not duplicate top padding props twice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä --- packages/core-components/src/layout/InfoCard/InfoCard.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/core-components/src/layout/InfoCard/InfoCard.tsx b/packages/core-components/src/layout/InfoCard/InfoCard.tsx index e80c0d3384..a08a816c2e 100644 --- a/packages/core-components/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core-components/src/layout/InfoCard/InfoCard.tsx @@ -217,10 +217,7 @@ export function InfoCard(props: Props): JSX.Element { } return ( -
+
{subheader &&
{subheader}
} {icon}
From a36d37447d889921510fd323210c5dc4a1f94abb Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 23 Apr 2024 09:22:05 +0200 Subject: [PATCH 082/234] fix(explore): use new auth service on the search collator Signed-off-by: Camila Belo --- plugins/search-backend-module-explore/src/alpha.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/search-backend-module-explore/src/alpha.ts b/plugins/search-backend-module-explore/src/alpha.ts index 5a990b19f6..21c5901885 100644 --- a/plugins/search-backend-module-explore/src/alpha.ts +++ b/plugins/search-backend-module-explore/src/alpha.ts @@ -44,6 +44,7 @@ export default createBackendModule({ discovery: coreServices.discovery, scheduler: coreServices.scheduler, tokenManager: coreServices.tokenManager, + auth: coreServices.auth, indexRegistry: searchIndexRegistryExtensionPoint, }, async init({ @@ -51,8 +52,9 @@ export default createBackendModule({ logger, discovery, scheduler, - indexRegistry, tokenManager, + auth, + indexRegistry, }) { const defaultSchedule = { frequency: { minutes: 10 }, @@ -71,6 +73,7 @@ export default createBackendModule({ factory: ToolDocumentCollatorFactory.fromConfig(config, { discovery, logger, + auth, tokenManager, }), }); From ca6e2e0bec2c3fe4822c44eaf466aac2d28783c0 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Tue, 23 Apr 2024 09:24:24 +0200 Subject: [PATCH 083/234] docs: add changeset files Signed-off-by: Camila Belo --- .changeset/real-crabs-obey.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/real-crabs-obey.md diff --git a/.changeset/real-crabs-obey.md b/.changeset/real-crabs-obey.md new file mode 100644 index 0000000000..9a52f0e5a6 --- /dev/null +++ b/.changeset/real-crabs-obey.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-explore': patch +--- + +Migrate search collator to use the new auth services. From c150e7ced4298974171dde1359ec9c86cc2450d6 Mon Sep 17 00:00:00 2001 From: cmoulliard Date: Tue, 23 Apr 2024 10:11:21 +0200 Subject: [PATCH 084/234] Document how to play with app next and backend. Disable the extensions not available. #23979 Signed-off-by: cmoulliard --- packages/app-next/README.md | 6 +++++- packages/app-next/app-config.yaml | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/app-next/README.md b/packages/app-next/README.md index 21645609a9..c9ad7ac536 100644 --- a/packages/app-next/README.md +++ b/packages/app-next/README.md @@ -1,3 +1,7 @@ # example-app-next -This package is an example of a Backstage application. +This package is an example of a Backstage application using the [new frontend](../../docs/frontend-system/index.md). + +To play with it, open a terminal and run the command: `yarn start` + +**Note**: Don't forget to open a second terminal and to launch the backend or [backend-next](../../docs/backend-system/index.md) application using `yarn start` too command ! diff --git a/packages/app-next/app-config.yaml b/packages/app-next/app-config.yaml index 8bfd79be54..271040ecec 100644 --- a/packages/app-next/app-config.yaml +++ b/packages/app-next/app-config.yaml @@ -10,7 +10,7 @@ app: extensions: # - apis.plugin.graphiql.browse.gitlab: true - - graphiql-endpoint:graphiql/gitlab: true + # - graphiql-endpoint:graphiql/gitlab: true # Entity page cards - entity-card:catalog/about @@ -18,7 +18,7 @@ app: - entity-card:catalog/links: config: filter: kind:component has:links - - entity-card:linguist/languages + # - entity-card:linguist/languages - entity-card:catalog-graph/relations: config: height: 300 From 8fa9cb6a08204a81cfa8ab3476fae8cb94ef514d Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Apr 2024 12:56:02 +0200 Subject: [PATCH 085/234] chore: make sure to redact secrets in meta fields Signed-off-by: blam --- packages/backend-app-api/src/logging/WinstonLogger.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts index 8a3eb28100..95730df742 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.ts @@ -87,11 +87,12 @@ export class WinstonLogger implements RootLoggerService { return { format: format(info => { - if (redactionPattern && typeof info.message === 'string') { - info.message = info.message.replace(redactionPattern, '[REDACTED]'); - } - if (redactionPattern && typeof info.stack === 'string') { - info.stack = info.stack.replace(redactionPattern, '[REDACTED]'); + if (redactionPattern) { + for (const [key, value] of Object.entries(info)) { + if (typeof value === 'string') { + info[key] = value.replace(redactionPattern, '[REDACTED]'); + } + } } return info; })(), From 93f28237d85172beec7f9a2c0ab7d5adc330e8a0 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Apr 2024 13:25:18 +0200 Subject: [PATCH 086/234] chore: fix redactions Signed-off-by: blam --- plugins/scaffolder-backend/package.json | 1 + .../tasks/NunjucksWorkflowRunner.ts | 12 +++- .../src/scaffolder/tasks/logger.ts | 56 +++++++++++++++---- yarn.lock | 1 + 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index b967580154..e092470bab 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -95,6 +95,7 @@ "prom-client": "^15.0.0", "uuid": "^9.0.0", "winston": "^3.2.1", + "winston-transport": "^4.7.0", "yaml": "^2.0.0", "zen-observable": "^0.10.0", "zod": "^3.22.4" diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 81897a96f8..5d24ac7cc7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -55,7 +55,7 @@ import { actionExecutePermission } from '@backstage/plugin-scaffolder-common/alp import { TaskRecovery } from '@backstage/plugin-scaffolder-common'; import { PermissionsService } from '@backstage/backend-plugin-api'; import { loggerToWinstonLogger } from '@backstage/backend-common'; -import { WinstonLogger } from './logger'; +import { BackstageLoggerTransport, WinstonLogger } from './logger'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -98,9 +98,11 @@ const isValidTaskSpec = (taskSpec: TaskSpec): taskSpec is TaskSpecV1beta3 => { const createStepLogger = ({ task, step, + rootLogger, }: { task: TaskContext; step: TaskStep; + rootLogger: winston.Logger; }) => { const stepLogStream = new PassThrough(); stepLogStream.on('data', async data => { @@ -117,8 +119,8 @@ const createStepLogger = ({ winston.format.simple(), ), transports: [ - new winston.transports.Console(), new winston.transports.Stream({ stream: stepLogStream }), + new BackstageLoggerTransport(rootLogger), ], }); @@ -262,7 +264,11 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const action: TemplateAction = this.options.actionRegistry.get(step.action); - const { taskLogger, streamLogger } = createStepLogger({ task, step }); + const { taskLogger, streamLogger } = createStepLogger({ + task, + step, + rootLogger: this.options.logger, + }); if (task.isDryRun) { const redactedSecrets = Object.fromEntries( diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts index b96b747077..9a318d66b7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts @@ -19,13 +19,8 @@ import { } from '@backstage/backend-plugin-api'; import { JsonObject } from '@backstage/types'; import { Format, TransformableInfo } from 'logform'; -import { - Logger, - format, - createLogger, - transports, - transport as Transport, -} from 'winston'; +import Transport, { TransportStreamOptions } from 'winston-transport'; +import { Logger, format, createLogger, transports } from 'winston'; /** * Escapes a given string to be used inside a RegExp. @@ -43,6 +38,42 @@ interface WinstonLoggerOptions { transports: Transport[]; } +// This is a workaround for being able to preserve the log format of the root logger. +// Will revisit all of this implementation once we can break the router to use only `LoggerService`. +export class BackstageLoggerTransport extends Transport { + constructor( + private readonly backstageLogger: LoggerService, + opts?: TransportStreamOptions, + ) { + super(opts); + } + + log(info: unknown, callback: VoidFunction) { + if (typeof info !== 'object' || info === null) { + callback(); + return; + } + const { level, message, ...meta } = info as JsonObject; + switch (level) { + case 'error': + this.backstageLogger.error(String(message), meta); + break; + case 'warn': + this.backstageLogger.warn(String(message), meta); + break; + case 'info': + this.backstageLogger.info(String(message), meta); + break; + case 'debug': + this.backstageLogger.debug(String(message), meta); + break; + default: + this.backstageLogger.info(String(message), meta); + } + callback(); + } +} + export class WinstonLogger implements RootLoggerService { #winston: Logger; #addRedactions?: (redactions: Iterable) => void; @@ -78,11 +109,12 @@ export class WinstonLogger implements RootLoggerService { return { format: format(info => { - if (redactionPattern && typeof info.message === 'string') { - info.message = info.message.replace(redactionPattern, '[REDACTED]'); - } - if (redactionPattern && typeof info.stack === 'string') { - info.stack = info.stack.replace(redactionPattern, '[REDACTED]'); + if (redactionPattern) { + for (const [key, value] of Object.entries(info)) { + if (typeof value === 'string') { + info[key] = value.replace(redactionPattern, '[REDACTED]'); + } + } } return info; })(), diff --git a/yarn.lock b/yarn.lock index af96b924bd..e92793be01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6591,6 +6591,7 @@ __metadata: uuid: ^9.0.0 wait-for-expect: ^3.0.2 winston: ^3.2.1 + winston-transport: ^4.7.0 yaml: ^2.0.0 zen-observable: ^0.10.0 zod: ^3.22.4 From 1bc3b86858411a1e422108d98e1305009124c26d Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Sun, 21 Apr 2024 21:42:15 +0300 Subject: [PATCH 087/234] fix: web notifications to show in foreground + duplicates prevented Signed-off-by: Heikki Hellgren --- .changeset/tasty-apes-learn.md | 5 +++ plugins/notifications/api-report.md | 3 +- plugins/notifications/dev/index.tsx | 4 +- plugins/notifications/package.json | 1 + .../NotificationsSideBarItem.tsx | 3 +- .../src/hooks/useWebNotifications.ts | 37 ++++++++++++------- yarn.lock | 1 + 7 files changed, 37 insertions(+), 17 deletions(-) create mode 100644 .changeset/tasty-apes-learn.md diff --git a/.changeset/tasty-apes-learn.md b/.changeset/tasty-apes-learn.md new file mode 100644 index 0000000000..7a4545cf10 --- /dev/null +++ b/.changeset/tasty-apes-learn.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Fix to show web notifications even when browser is on foreground. Fix duplicate notifications with multiple tabs. diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index f1369a882d..e998d76ea4 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -175,8 +175,9 @@ export function useTitleCounter(): { }; // @public (undocumented) -export function useWebNotifications(): { +export function useWebNotifications(enabled: boolean): { sendWebNotification: (options: { + id: string; title: string; description: string; link?: string; diff --git a/plugins/notifications/dev/index.tsx b/plugins/notifications/dev/index.tsx index b601c4b9a4..a21170aec0 100644 --- a/plugins/notifications/dev/index.tsx +++ b/plugins/notifications/dev/index.tsx @@ -20,9 +20,11 @@ import { notificationsPlugin, NotificationsSidebarItem, } from '../src'; +import { signalsPlugin } from '@backstage/plugin-signals'; createDevApp() .registerPlugin(notificationsPlugin) + .registerPlugin(signalsPlugin) .addPage({ element: ( ) + .addSidebarItem() .render(); diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index ee2bdc885a..8c3a90c092 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -50,6 +50,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/plugin-signals": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^6.0.0", "@testing-library/react": "^15.0.0", diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index d143344a9a..b15e5a34c6 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -51,7 +51,7 @@ export const NotificationsSidebarItem = (props?: { const notificationsRoute = useRouteRef(rootRouteRef); // TODO: Do we want to add long polling in case signals are not available const { lastSignal } = useSignal('notifications'); - const { sendWebNotification } = useWebNotifications(); + const { sendWebNotification } = useWebNotifications(webNotificationsEnabled); const [refresh, setRefresh] = React.useState(false); const { setNotificationCount } = useTitleCounter(); @@ -75,6 +75,7 @@ export const NotificationsSidebarItem = (props?: { return; } sendWebNotification({ + id: notification.id, title: notification.payload.title, description: notification.payload.description ?? '', link: notification.payload.link, diff --git a/plugins/notifications/src/hooks/useWebNotifications.ts b/plugins/notifications/src/hooks/useWebNotifications.ts index bf3b5269f5..728d992130 100644 --- a/plugins/notifications/src/hooks/useWebNotifications.ts +++ b/plugins/notifications/src/hooks/useWebNotifications.ts @@ -14,49 +14,58 @@ * limitations under the License. */ import { useCallback, useEffect, useState } from 'react'; +import { rootRouteRef } from '../routes'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import { useNavigate } from 'react-router-dom'; /** @public */ -export function useWebNotifications() { +export function useWebNotifications(enabled: boolean) { const [webNotificationPermission, setWebNotificationPermission] = useState('default'); - const [webNotifications, setWebNotifications] = useState([]); + const notificationsRoute = useRouteRef(rootRouteRef); + const navigate = useNavigate(); useEffect(() => { - if ('Notification' in window && webNotificationPermission === 'default') { + if ( + enabled && + 'Notification' in window && + webNotificationPermission === 'default' + ) { window.Notification.requestPermission().then(permission => { setWebNotificationPermission(permission); }); } - }, [webNotificationPermission]); - - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible') { - webNotifications.forEach(n => n.close()); - setWebNotifications([]); - } - }); + }, [enabled, webNotificationPermission]); const sendWebNotification = useCallback( - (options: { title: string; description: string; link?: string }) => { + (options: { + id: string; + title: string; + description: string; + link?: string; + }) => { if (webNotificationPermission !== 'granted') { return null; } const notification = new Notification(options.title, { body: options.description, + tag: options.id, // Prevent duplicates from multiple tabs }); notification.onclick = event => { event.preventDefault(); - notification.close(); if (options.link) { window.open(options.link, '_blank'); + } else { + navigate(notificationsRoute()); } + notification.close(); }; return notification; }, - [webNotificationPermission], + [webNotificationPermission, navigate, notificationsRoute], ); return { sendWebNotification }; diff --git a/yarn.lock b/yarn.lock index af96b924bd..a07e9249f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6057,6 +6057,7 @@ __metadata: "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-notifications-common": "workspace:^" + "@backstage/plugin-signals": "workspace:^" "@backstage/plugin-signals-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" From e1c7d6e4b79752a7566fb0bfa320838c38b5e3c5 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Sun, 21 Apr 2024 21:21:49 +0300 Subject: [PATCH 088/234] fix: infinite loop in notification title counter Signed-off-by: Heikki Hellgren --- .changeset/unlucky-days-play.md | 5 ++ plugins/notifications/dev/index.tsx | 2 + plugins/notifications/package.json | 1 + .../NotificationsSideBarItem.tsx | 11 +++-- .../src/hooks/useTitleCounter.ts | 48 ++++++++++++------- yarn.lock | 1 + 6 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 .changeset/unlucky-days-play.md diff --git a/.changeset/unlucky-days-play.md b/.changeset/unlucky-days-play.md new file mode 100644 index 0000000000..b847ebd2fc --- /dev/null +++ b/.changeset/unlucky-days-play.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Fix infinite loop in the notification title counter diff --git a/plugins/notifications/dev/index.tsx b/plugins/notifications/dev/index.tsx index b601c4b9a4..c35ac7250f 100644 --- a/plugins/notifications/dev/index.tsx +++ b/plugins/notifications/dev/index.tsx @@ -20,9 +20,11 @@ import { notificationsPlugin, NotificationsSidebarItem, } from '../src'; +import { signalsPlugin } from '@backstage/plugin-signals'; createDevApp() .registerPlugin(notificationsPlugin) + .registerPlugin(signalsPlugin) .addPage({ element: ( { if (!loading && !error && value) { setUnreadCount(value.unread); - if (titleCounterEnabled) { - setNotificationCount(value.unread); - } } - }, [loading, error, value, titleCounterEnabled, setNotificationCount]); + }, [loading, error, value]); + + useEffect(() => { + if (titleCounterEnabled) { + setNotificationCount(unreadCount); + } + }, [titleCounterEnabled, unreadCount, setNotificationCount]); // TODO: Figure out if the count can be added to hasNotifications return ( diff --git a/plugins/notifications/src/hooks/useTitleCounter.ts b/plugins/notifications/src/hooks/useTitleCounter.ts index d4ba22b946..d794678ea6 100644 --- a/plugins/notifications/src/hooks/useTitleCounter.ts +++ b/plugins/notifications/src/hooks/useTitleCounter.ts @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; /** @public */ export function useTitleCounter() { const [title, setTitle] = useState(document.title); const [count, setCount] = useState(0); + const titleTimer = useRef(undefined); const getPrefix = (value: number) => { return value === 0 ? '' : `(${value}) `; @@ -28,28 +29,43 @@ export function useTitleCounter() { return currentTitle.replace(/^\(\d+\)\s/, ''); }; - useEffect(() => { - document.title = title; - }, [title]); - useEffect(() => { const baseTitle = cleanTitle(title); - setTitle(`${getPrefix(count)}${baseTitle}`); + const shownTitle = `${getPrefix(count)}${baseTitle}`; + if (document.title !== shownTitle) { + window.clearTimeout(titleTimer.current); + document.title = shownTitle; + // Need to do this in timeout as the React Helmet overrides the title after this effect + titleTimer.current = window.setTimeout(() => { + document.title = shownTitle; + }, 50); + } return () => { + window.clearTimeout(titleTimer.current); document.title = cleanTitle(title); }; }, [title, count]); - const titleElement = document.querySelector('title'); - if (titleElement) { - new MutationObserver(() => { - setTitle(document.title); - }).observe(titleElement, { - subtree: true, - characterData: true, - childList: true, - }); - } + useEffect(() => { + const titleElement = document.querySelector('title'); + let observer: MutationObserver | undefined; + if (titleElement) { + observer = new MutationObserver(mutations => { + if (mutations?.[0]?.target?.textContent) { + setTitle(mutations[0].target.textContent); + } + }); + observer.observe(titleElement, { + characterData: true, + childList: true, + }); + } + return () => { + if (observer) { + observer.disconnect(); + } + }; + }, []); const setNotificationCount = useCallback( (newCount: number) => setCount(newCount), diff --git a/yarn.lock b/yarn.lock index af96b924bd..a07e9249f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6057,6 +6057,7 @@ __metadata: "@backstage/dev-utils": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/plugin-notifications-common": "workspace:^" + "@backstage/plugin-signals": "workspace:^" "@backstage/plugin-signals-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/theme": "workspace:^" From 7c860c37e0a506bd86bfaa9c1d54ede4712ef96b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 23 Apr 2024 13:55:41 +0200 Subject: [PATCH 089/234] workflows: do not stale issues with bep label Signed-off-by: Patrik Oldsberg --- .github/workflows/automate_stale.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automate_stale.yml b/.github/workflows/automate_stale.yml index 691066fb61..c7674e853d 100644 --- a/.github/workflows/automate_stale.yml +++ b/.github/workflows/automate_stale.yml @@ -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 + exempt-issue-labels: plugin,after-vacations,will-fix,bep stale-issue-label: stale stale-pr-message: > This PR has been automatically marked as stale because it has not had From ffa6e7b4d9f679edbba662c69167ad7b25eb8634 Mon Sep 17 00:00:00 2001 From: Charles Moulliard Date: Tue, 23 Apr 2024 14:06:10 +0200 Subject: [PATCH 090/234] Update packages/app-next/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Charles Moulliard --- packages/app-next/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-next/README.md b/packages/app-next/README.md index c9ad7ac536..360e5d5545 100644 --- a/packages/app-next/README.md +++ b/packages/app-next/README.md @@ -4,4 +4,4 @@ This package is an example of a Backstage application using the [new frontend](. To play with it, open a terminal and run the command: `yarn start` -**Note**: Don't forget to open a second terminal and to launch the backend or [backend-next](../../docs/backend-system/index.md) application using `yarn start` too command ! +**NOTE:** Don't forget to open a second terminal and to launch the backend or [backend-next](../../docs/backend-system/index.md) there, using `yarn start`! The frontend requires a backend to connect to. From bfcb2f1281029fbf3e05dcbcdc1c6212e8240f3a Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Sun, 21 Apr 2024 19:39:03 +0300 Subject: [PATCH 091/234] feat: allow showing notifications as snackbars in the UI Signed-off-by: Heikki Hellgren --- .changeset/hot-forks-train.md | 5 + plugins/notifications/api-report.md | 1 + plugins/notifications/package.json | 1 + .../NotificationsSideBarItem.tsx | 165 ++++++++++++++++-- yarn.lock | 25 ++- 5 files changed, 177 insertions(+), 20 deletions(-) create mode 100644 .changeset/hot-forks-train.md diff --git a/.changeset/hot-forks-train.md b/.changeset/hot-forks-train.md new file mode 100644 index 0000000000..a5300085b6 --- /dev/null +++ b/.changeset/hot-forks-train.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Allow showing notifications as snackbars in the UI diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index e998d76ea4..9ffcf0d727 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -100,6 +100,7 @@ export const notificationsPlugin: BackstagePlugin< export const NotificationsSidebarItem: (props?: { webNotificationsEnabled?: boolean; titleCounterEnabled?: boolean; + snackbarEnabled?: boolean; className?: string; icon?: IconComponent; text?: string; diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 8c3a90c092..0c724e3d02 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -43,6 +43,7 @@ "@material-ui/lab": "^4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "lodash": "^4.17.21", + "notistack": "^3.0.1", "react-relative-time": "^0.0.9", "react-use": "^17.2.4" }, diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 6daea5ee34..9851ff8a66 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -13,22 +13,72 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useEffect } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { useNotificationsApi } from '../../hooks'; import { SidebarItem } from '@backstage/core-components'; import NotificationsIcon from '@material-ui/icons/Notifications'; import { IconComponent, useApi, useRouteRef } from '@backstage/core-plugin-api'; import { rootRouteRef } from '../../routes'; import { useSignal } from '@backstage/plugin-signals-react'; -import { NotificationSignal } from '@backstage/plugin-notifications-common'; +import { + Notification, + NotificationSignal, +} from '@backstage/plugin-notifications-common'; import { useWebNotifications } from '../../hooks/useWebNotifications'; import { useTitleCounter } from '../../hooks/useTitleCounter'; import { notificationsApiRef } from '../../api'; +import { + closeSnackbar, + enqueueSnackbar, + MaterialDesignContent, + OptionsWithExtraProps, + SnackbarKey, + SnackbarProvider, + VariantType, +} from 'notistack'; +import { SeverityIcon } from '../NotificationsTable/SeverityIcon'; +import { useNavigate } from 'react-router-dom'; +import OpenInNew from '@material-ui/icons/OpenInNew'; +import MarkAsReadIcon from '@material-ui/icons/CheckCircle'; +import IconButton from '@material-ui/core/IconButton'; +import { styled } from '@material-ui/core/styles'; + +const StyledMaterialDesignContent = styled(MaterialDesignContent)( + ({ theme }) => ({ + '&.notistack-MuiContent-low': { + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + }, + '&.notistack-MuiContent-normal': { + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + }, + '&.notistack-MuiContent-high': { + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + }, + '&.notistack-MuiContent-critical': { + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + }, + }), +); + +declare module 'notistack' { + interface VariantOverrides { + // Custom variants for the snackbar + low: true; + normal: true; + high: true; + critical: true; + } +} /** @public */ export const NotificationsSidebarItem = (props?: { webNotificationsEnabled?: boolean; titleCounterEnabled?: boolean; + snackbarEnabled?: boolean; className?: string; icon?: IconComponent; text?: string; @@ -38,14 +88,20 @@ export const NotificationsSidebarItem = (props?: { const { webNotificationsEnabled = false, titleCounterEnabled = true, + snackbarEnabled = true, icon = NotificationsIcon, text = 'Notifications', ...restProps - } = props ?? { webNotificationsEnabled: false, titleCounterEnabled: true }; + } = props ?? { + webNotificationsEnabled: false, + titleCounterEnabled: true, + snackbarEnabled: true, + }; const { loading, error, value, retry } = useNotificationsApi(api => api.getStatus(), ); + const navigate = useNavigate(); const notificationsApi = useApi(notificationsApiRef); const [unreadCount, setUnreadCount] = React.useState(0); const notificationsRoute = useRouteRef(rootRouteRef); @@ -55,6 +111,40 @@ export const NotificationsSidebarItem = (props?: { const [refresh, setRefresh] = React.useState(false); const { setNotificationCount } = useTitleCounter(); + const getSnackbarProperties = useCallback( + (notification: Notification) => { + const action = (snackBarId: SnackbarKey) => ( + <> + { + if (notification.payload.link) { + window.open(notification.payload.link, '_blank'); + } + navigate(notificationsRoute()); + closeSnackbar(snackBarId); + }} + > + + + { + notificationsApi.updateNotifications({ + ids: [notification.id], + read: true, + }); + closeSnackbar(snackBarId); + }} + > + + + + ); + + return { action }; + }, + [notificationsRoute, navigate, notificationsApi], + ); + useEffect(() => { if (refresh) { retry(); @@ -63,8 +153,11 @@ export const NotificationsSidebarItem = (props?: { }, [refresh, retry]); useEffect(() => { - const handleWebNotification = (signal: NotificationSignal) => { - if (!webNotificationsEnabled || signal.action !== 'new_notification') { + const handleNotificationSignal = (signal: NotificationSignal) => { + if ( + (!webNotificationsEnabled && !snackbarEnabled) || + signal.action !== 'new_notification' + ) { return; } @@ -74,24 +167,40 @@ export const NotificationsSidebarItem = (props?: { if (!notification) { return; } - sendWebNotification({ - id: notification.id, - title: notification.payload.title, - description: notification.payload.description ?? '', - link: notification.payload.link, - }); + if (webNotificationsEnabled) { + sendWebNotification({ + id: notification.id, + title: notification.payload.title, + description: notification.payload.description ?? '', + link: notification.payload.link, + }); + } + if (snackbarEnabled) { + const { action } = getSnackbarProperties(notification); + const snackBarText = + notification.payload.title.length > 50 + ? `${notification.payload.title.substring(0, 50)}...` + : notification.payload.title; + enqueueSnackbar(snackBarText, { + variant: notification.payload.severity, + anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, + action, + } as OptionsWithExtraProps); + } }); }; if (lastSignal && lastSignal.action) { - handleWebNotification(lastSignal); + handleNotificationSignal(lastSignal); setRefresh(true); } }, [ lastSignal, sendWebNotification, webNotificationsEnabled, + snackbarEnabled, notificationsApi, + getSnackbarProperties, ]); useEffect(() => { @@ -108,12 +217,30 @@ export const NotificationsSidebarItem = (props?: { // TODO: Figure out if the count can be added to hasNotifications return ( - + <> + {snackbarEnabled && ( + , + critical: , + high: , + low: , + }} + Components={{ + normal: StyledMaterialDesignContent, + critical: StyledMaterialDesignContent, + high: StyledMaterialDesignContent, + low: StyledMaterialDesignContent, + }} + /> + )} + + ); }; diff --git a/yarn.lock b/yarn.lock index a07e9249f7..0ee0b47946 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6071,6 +6071,7 @@ __metadata: "@types/react": ^16.13.1 || ^17.0.0 lodash: ^4.17.21 msw: ^1.0.0 + notistack: ^3.0.1 react-relative-time: ^0.0.9 react-use: ^17.2.4 peerDependencies: @@ -20124,7 +20125,7 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^1.0.2, clsx@npm:^1.0.4, clsx@npm:^1.1.1, clsx@npm:^1.2.1": +"clsx@npm:^1.0.2, clsx@npm:^1.0.4, clsx@npm:^1.1.0, clsx@npm:^1.1.1, clsx@npm:^1.2.1": version: 1.2.1 resolution: "clsx@npm:1.2.1" checksum: 30befca8019b2eb7dbad38cff6266cf543091dae2825c856a62a8ccf2c3ab9c2907c4d12b288b73101196767f66812365400a227581484a05f968b0307cfaf12 @@ -25650,6 +25651,15 @@ __metadata: languageName: node linkType: hard +"goober@npm:^2.0.33": + version: 2.1.14 + resolution: "goober@npm:2.1.14" + peerDependencies: + csstype: ^3.0.10 + checksum: 78978b7192d6a1af5cfbf1fd64b661b5f53ee6c733554b1f1b2ad3e1e2c979847fc080434390647640bb8358c0b193895d0007432c0886d12001f02f8f56b5e6 + languageName: node + linkType: hard + "google-auth-library@npm:^9.0.0, google-auth-library@npm:^9.6.3": version: 9.8.0 resolution: "google-auth-library@npm:9.8.0" @@ -32361,6 +32371,19 @@ __metadata: languageName: node linkType: hard +"notistack@npm:^3.0.1": + version: 3.0.1 + resolution: "notistack@npm:3.0.1" + dependencies: + clsx: ^1.1.0 + goober: ^2.0.33 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 421c970308690b8c8cb2e275e7f66020db7c1955e104f638e7fa562396a6b9322ff95f0e62492b07f3d36b0ef72adb4de2c2ce9803089c1c8f028d1a3b088e01 + languageName: node + linkType: hard + "npm-bundled@npm:^1.1.1": version: 1.1.2 resolution: "npm-bundled@npm:1.1.2" From 64e089e5b3ec6f8fdba09cbf0be059f7dd9caeee Mon Sep 17 00:00:00 2001 From: Heikki Hellgren Date: Tue, 23 Apr 2024 14:47:06 +0300 Subject: [PATCH 092/234] fix: review comments on snackbar handling Signed-off-by: Heikki Hellgren --- .../config/vocabularies/Backstage/accept.txt | 2 + .../NotificationsSideBarItem.tsx | 46 +++++++++++++------ 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index b525d7e48e..026e5a9b18 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -362,6 +362,8 @@ SIG SIGs siloed Sinon +snackbars +snackbar Snyk Sonarqube sourcemaps diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 9851ff8a66..000eabcae6 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -15,9 +15,14 @@ */ import React, { useCallback, useEffect } from 'react'; import { useNotificationsApi } from '../../hooks'; -import { SidebarItem } from '@backstage/core-components'; +import { Link, SidebarItem } from '@backstage/core-components'; import NotificationsIcon from '@material-ui/icons/Notifications'; -import { IconComponent, useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { + alertApiRef, + IconComponent, + useApi, + useRouteRef, +} from '@backstage/core-plugin-api'; import { rootRouteRef } from '../../routes'; import { useSignal } from '@backstage/plugin-signals-react'; import { @@ -37,7 +42,6 @@ import { VariantType, } from 'notistack'; import { SeverityIcon } from '../NotificationsTable/SeverityIcon'; -import { useNavigate } from 'react-router-dom'; import OpenInNew from '@material-ui/icons/OpenInNew'; import MarkAsReadIcon from '@material-ui/icons/CheckCircle'; import IconButton from '@material-ui/core/IconButton'; @@ -101,8 +105,8 @@ export const NotificationsSidebarItem = (props?: { const { loading, error, value, retry } = useNotificationsApi(api => api.getStatus(), ); - const navigate = useNavigate(); const notificationsApi = useApi(notificationsApiRef); + const alertApi = useApi(alertApiRef); const [unreadCount, setUnreadCount] = React.useState(0); const notificationsRoute = useRouteRef(rootRouteRef); // TODO: Do we want to add long polling in case signals are not available @@ -116,11 +120,9 @@ export const NotificationsSidebarItem = (props?: { const action = (snackBarId: SnackbarKey) => ( <> { - if (notification.payload.link) { - window.open(notification.payload.link, '_blank'); - } - navigate(notificationsRoute()); closeSnackbar(snackBarId); }} > @@ -128,11 +130,20 @@ export const NotificationsSidebarItem = (props?: { { - notificationsApi.updateNotifications({ - ids: [notification.id], - read: true, - }); - closeSnackbar(snackBarId); + notificationsApi + .updateNotifications({ + ids: [notification.id], + read: true, + }) + .then(() => { + closeSnackbar(snackBarId); + }) + .catch(() => { + alertApi.post({ + message: 'Failed to mark notification as read', + severity: 'error', + }); + }); }} > @@ -142,7 +153,7 @@ export const NotificationsSidebarItem = (props?: { return { action }; }, - [notificationsRoute, navigate, notificationsApi], + [notificationsRoute, notificationsApi, alertApi], ); useEffect(() => { @@ -187,6 +198,12 @@ export const NotificationsSidebarItem = (props?: { action, } as OptionsWithExtraProps); } + }) + .catch(() => { + alertApi.post({ + message: 'Failed to fetch notification', + severity: 'error', + }); }); }; @@ -200,6 +217,7 @@ export const NotificationsSidebarItem = (props?: { webNotificationsEnabled, snackbarEnabled, notificationsApi, + alertApi, getSnackbarProperties, ]); From a42a19b087ecb994371769cf1ceae645cdcffb2f Mon Sep 17 00:00:00 2001 From: Marek Libra Date: Tue, 23 Apr 2024 14:07:31 +0200 Subject: [PATCH 093/234] patch(notifications): skip rendering empty descriptions Signed-off-by: Marek Libra --- .changeset/stupid-onions-know.md | 5 +++++ .../components/NotificationsTable/NotificationsTable.tsx | 8 +++++--- 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 .changeset/stupid-onions-know.md diff --git a/.changeset/stupid-onions-know.md b/.changeset/stupid-onions-know.md new file mode 100644 index 0000000000..3d028557be --- /dev/null +++ b/.changeset/stupid-onions-know.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Empty descriptions are not rendered to improve the look&feel. diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx index 3b34f9766b..f1d6e58b51 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx @@ -181,9 +181,11 @@ export const NotificationsTable = ({ notification.payload.title )} - - {notification.payload.description} - + {notification.payload.description ? ( + + {notification.payload.description} + + ) : null} {notification.origin && ( <>{notification.origin} •  From 5f0c46fa2368a3b8f35525b2741cb28989ab814f Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Apr 2024 15:01:09 +0200 Subject: [PATCH 094/234] feat: reworking Signed-off-by: blam --- .../src/logging/WinstonLogger.ts | 21 +++++++++++-------- .../src/scaffolder/tasks/logger.ts | 21 +++++++++++-------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/backend-app-api/src/logging/WinstonLogger.ts b/packages/backend-app-api/src/logging/WinstonLogger.ts index 95730df742..8d4b37bb8d 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.ts @@ -85,17 +85,20 @@ export class WinstonLogger implements RootLoggerService { let redactionPattern: RegExp | undefined = undefined; - return { - format: format(info => { - if (redactionPattern) { - for (const [key, value] of Object.entries(info)) { - if (typeof value === 'string') { - info[key] = value.replace(redactionPattern, '[REDACTED]'); - } + const replace = (obj: TransformableInfo) => { + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + if (typeof obj[key] === 'object') { + obj[key] = replace(obj[key] as TransformableInfo); + } else if (typeof obj[key] === 'string') { + obj[key] = obj[key]?.replace(redactionPattern, '[REDACTED]'); } } - return info; - })(), + } + return obj; + }; + return { + format: format(replace)(), add(newRedactions) { let added = 0; for (const redactionToTrim of newRedactions) { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts index 9a318d66b7..064eb69349 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/logger.ts @@ -107,17 +107,20 @@ export class WinstonLogger implements RootLoggerService { let redactionPattern: RegExp | undefined = undefined; - return { - format: format(info => { - if (redactionPattern) { - for (const [key, value] of Object.entries(info)) { - if (typeof value === 'string') { - info[key] = value.replace(redactionPattern, '[REDACTED]'); - } + const replace = (obj: TransformableInfo) => { + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + if (typeof obj[key] === 'object') { + obj[key] = replace(obj[key] as TransformableInfo); + } else if (typeof obj[key] === 'string') { + obj[key] = obj[key]?.replace(redactionPattern, '[REDACTED]'); } } - return info; - })(), + } + return obj; + }; + return { + format: format(replace)(), add(newRedactions) { let added = 0; for (const redactionToTrim of newRedactions) { From 025641bfd97ad26998a34046e2ef345ba9f11550 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 23 Apr 2024 15:14:13 +0200 Subject: [PATCH 095/234] chore: add changesets Signed-off-by: blam --- .changeset/cold-rats-leave.md | 5 +++++ .changeset/itchy-keys-wonder.md | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 .changeset/cold-rats-leave.md create mode 100644 .changeset/itchy-keys-wonder.md diff --git a/.changeset/cold-rats-leave.md b/.changeset/cold-rats-leave.md new file mode 100644 index 0000000000..6fe28d6371 --- /dev/null +++ b/.changeset/cold-rats-leave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Fix issue with the log format not being respected when logging from actions diff --git a/.changeset/itchy-keys-wonder.md b/.changeset/itchy-keys-wonder.md new file mode 100644 index 0000000000..879dc096a4 --- /dev/null +++ b/.changeset/itchy-keys-wonder.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +'@backstage/backend-app-api': patch +--- + +Redact `meta` fields too with the logger From 8a96409d53f592d2951ccbbf147019e0c4218a93 Mon Sep 17 00:00:00 2001 From: Chase Bennett Date: Tue, 27 Feb 2024 15:11:42 -0800 Subject: [PATCH 096/234] Add expanded field to SidebarSubmenuItem Signed-off-by: Chase Bennett --- .../core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx index 0503f58f1c..da00b0a367 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx @@ -131,6 +131,7 @@ export type SidebarSubmenuItemProps = { icon?: IconComponent; dropdownItems?: SidebarSubmenuItemDropdownItem[]; exact?: boolean; + expanded?: boolean; }; /** @@ -149,7 +150,7 @@ export const SidebarSubmenuItem = (props: SidebarSubmenuItemProps) => { const currentLocation = useLocation(); let isActive = isLocationMatch(currentLocation, toLocation, exact); - const [showDropDown, setShowDropDown] = useState(false); + const [showDropDown, setShowDropDown] = useState(props.expanded ?? false); const handleClickDropdown = () => { setShowDropDown(!showDropDown); }; From 71e4229db5e76881fde9f2502995234253e9c40e Mon Sep 17 00:00:00 2001 From: Chase Bennett Date: Tue, 27 Feb 2024 15:20:09 -0800 Subject: [PATCH 097/234] Add changeset Signed-off-by: Chase Bennett --- .changeset/rare-fireants-tickle.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/rare-fireants-tickle.md diff --git a/.changeset/rare-fireants-tickle.md b/.changeset/rare-fireants-tickle.md new file mode 100644 index 0000000000..76c6189d85 --- /dev/null +++ b/.changeset/rare-fireants-tickle.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Added expanded property to allow optionally have sub menu items automatically expand their contents on the sidebar From a809c05d82a0240529693ff5941c06cc561eb679 Mon Sep 17 00:00:00 2001 From: Chase Bennett Date: Wed, 28 Feb 2024 08:16:43 -0800 Subject: [PATCH 098/234] Update API reports Signed-off-by: Chase Bennett --- packages/core-components/api-report.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 7fd706db43..5fe484c4b7 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -1128,6 +1128,7 @@ export type SidebarSubmenuItemProps = { icon?: IconComponent; dropdownItems?: SidebarSubmenuItemDropdownItem[]; exact?: boolean; + expanded?: boolean; }; // @public From 87ad7c153ac017159a4f885d27e3c5c658ad560e Mon Sep 17 00:00:00 2001 From: Chase Bennett Date: Wed, 28 Feb 2024 13:49:27 -0800 Subject: [PATCH 099/234] Update property name Signed-off-by: Chase Bennett --- .../src/layout/Sidebar/SidebarSubmenuItem.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx index da00b0a367..58c215e9d6 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx @@ -131,7 +131,7 @@ export type SidebarSubmenuItemProps = { icon?: IconComponent; dropdownItems?: SidebarSubmenuItemDropdownItem[]; exact?: boolean; - expanded?: boolean; + initialShowDropDown?: boolean; }; /** @@ -150,7 +150,9 @@ export const SidebarSubmenuItem = (props: SidebarSubmenuItemProps) => { const currentLocation = useLocation(); let isActive = isLocationMatch(currentLocation, toLocation, exact); - const [showDropDown, setShowDropDown] = useState(props.expanded ?? false); + const [showDropDown, setShowDropDown] = useState( + props.initialShowDropDown ?? false, + ); const handleClickDropdown = () => { setShowDropDown(!showDropDown); }; From f39257d454f64a0178da2996e84ffd92d8fbe266 Mon Sep 17 00:00:00 2001 From: Chase Bennett Date: Wed, 28 Feb 2024 13:54:15 -0800 Subject: [PATCH 100/234] Update api reports to reflect property name change Signed-off-by: Chase Bennett --- packages/core-components/api-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 5fe484c4b7..8b181cc3be 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -1128,7 +1128,7 @@ export type SidebarSubmenuItemProps = { icon?: IconComponent; dropdownItems?: SidebarSubmenuItemDropdownItem[]; exact?: boolean; - expanded?: boolean; + initialShowDropDown?: boolean; }; // @public From 3f541b116384b655b219c9ce724ff75610670049 Mon Sep 17 00:00:00 2001 From: Chase Bennett Date: Thu, 29 Feb 2024 11:48:53 -0800 Subject: [PATCH 101/234] Formatting Signed-off-by: Chase Bennett --- .../core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx index 58c215e9d6..19834268f9 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx @@ -131,7 +131,7 @@ export type SidebarSubmenuItemProps = { icon?: IconComponent; dropdownItems?: SidebarSubmenuItemDropdownItem[]; exact?: boolean; - initialShowDropDown?: boolean; + initialShowDropdown?: boolean; }; /** @@ -151,7 +151,7 @@ export const SidebarSubmenuItem = (props: SidebarSubmenuItemProps) => { let isActive = isLocationMatch(currentLocation, toLocation, exact); const [showDropDown, setShowDropDown] = useState( - props.initialShowDropDown ?? false, + props.initialShowDropdown ?? false, ); const handleClickDropdown = () => { setShowDropDown(!showDropDown); From 4bff30aeb5126a73b5cc170b45db9629020112e6 Mon Sep 17 00:00:00 2001 From: Chase Bennett Date: Thu, 29 Feb 2024 12:15:11 -0800 Subject: [PATCH 102/234] +Update api report Signed-off-by: Chase Bennett --- packages/core-components/api-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 8b181cc3be..2a146e70e0 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -1128,7 +1128,7 @@ export type SidebarSubmenuItemProps = { icon?: IconComponent; dropdownItems?: SidebarSubmenuItemDropdownItem[]; exact?: boolean; - initialShowDropDown?: boolean; + initialShowDropdown?: boolean; }; // @public From 76c437800a140fe14b13a6703ddb40374421b13f Mon Sep 17 00:00:00 2001 From: carnageX Date: Fri, 1 Mar 2024 13:28:17 -0800 Subject: [PATCH 103/234] Update .changeset/rare-fireants-tickle.md Co-authored-by: Vincenzo Scamporlino Signed-off-by: carnageX --- .changeset/rare-fireants-tickle.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/rare-fireants-tickle.md b/.changeset/rare-fireants-tickle.md index 76c6189d85..8668d4c287 100644 --- a/.changeset/rare-fireants-tickle.md +++ b/.changeset/rare-fireants-tickle.md @@ -2,4 +2,4 @@ '@backstage/core-components': patch --- -Added expanded property to allow optionally have sub menu items automatically expand their contents on the sidebar +Added optional `initialShowDropDown` prop to `SidebarSubmenuItem` to internally manage the initial display state of the dropdown items. From c297afd968d5257393a26b0f6dea3f0cdaf74709 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 23 Apr 2024 14:29:56 +0200 Subject: [PATCH 104/234] auth-react: handle missing cookie auth endpoint Signed-off-by: Patrik Oldsberg --- .changeset/lovely-games-cry.md | 5 +++ .../useCookieAuthRefresh.test.tsx | 33 +++++++++++++++++++ .../useCookieAuthRefresh.tsx | 9 +++++ 3 files changed, 47 insertions(+) create mode 100644 .changeset/lovely-games-cry.md diff --git a/.changeset/lovely-games-cry.md b/.changeset/lovely-games-cry.md new file mode 100644 index 0000000000..5209b93496 --- /dev/null +++ b/.changeset/lovely-games-cry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-react': patch +--- + +When using `CookieAuthRefreshProvider` or `useCookieAuthRefresh`, a 404 response from the cookie endpoint will now be treated as if cookie auth is disabled and is not needed. diff --git a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx index df5c833953..422f392e93 100644 --- a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx +++ b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.test.tsx @@ -217,6 +217,39 @@ describe('useCookieAuthRefresh', () => { ); }); + it('should handle 404 as disabled cookie auth', async () => { + const { result } = renderHook( + () => useCookieAuthRefresh({ pluginId: 'techdocs' }), + { + wrapper: ({ children }) => ( + + {children} + + ), + }, + ); + + await waitFor(() => + expect(result.current).toEqual({ + status: 'success', + data: { expiresAt: expect.any(Date) }, + }), + ); + }); + it('should call the api to get the cookie and use it', async () => { const { result } = renderHook( () => useCookieAuthRefresh({ pluginId: 'techdocs' }), diff --git a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx index 244491f7e6..a6860ad185 100644 --- a/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx +++ b/plugins/auth-react/src/hooks/useCookieAuthRefresh/useCookieAuthRefresh.tsx @@ -24,6 +24,7 @@ import { useAsync, useMountEffect } from '@react-hookz/web'; import { ResponseError } from '@backstage/errors'; const COOKIE_PATH = '/.backstage/auth/v1/cookie'; +const ONE_YEAR_MS = 365 * 24 * 3600_000; /** * @public @@ -54,6 +55,14 @@ export function useCookieAuthRefresh(options: { credentials: 'include', }); 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 + // 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) { + return { expiresAt: new Date(Date.now() + ONE_YEAR_MS) }; + } throw await ResponseError.fromResponse(response); } const data = await response.json(); From 3d355ea3fa607c4ca6f5bdcf8cd79908725d1d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 23 Apr 2024 16:41:07 +0200 Subject: [PATCH 105/234] unbreak changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/fix-stackoverflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/fix-stackoverflow.md b/.changeset/fix-stackoverflow.md index 9047b8183a..6ce9f15737 100644 --- a/.changeset/fix-stackoverflow.md +++ b/.changeset/fix-stackoverflow.md @@ -1,5 +1,5 @@ --- -'@backstage/catalog-backend-module-incremental-ingestion': patch +'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch --- Fix plugin/incremental-ingestion 'Maximum call stack size exceeded' error when ingest large entities. From 46046059b4f5b8e6b269dfa09c8975e1ed6145d8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 23 Apr 2024 14:53:09 +0000 Subject: [PATCH 106/234] Version Packages (next) --- .changeset/pre.json | 54 +- docs/releases/v1.27.0-next.0-changelog.md | 2064 +++++++++++++++++ package.json | 2 +- packages/app-defaults/CHANGELOG.md | 11 + packages/app-defaults/package.json | 2 +- packages/app-next-example-plugin/CHANGELOG.md | 8 + packages/app-next-example-plugin/package.json | 2 +- packages/app-next/CHANGELOG.md | 43 + packages/app-next/package.json | 2 +- packages/app/CHANGELOG.md | 41 + packages/app/package.json | 2 +- packages/backend-app-api/CHANGELOG.md | 22 + packages/backend-app-api/package.json | 2 +- packages/backend-common/CHANGELOG.md | 18 + packages/backend-common/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 8 + packages/backend-defaults/package.json | 2 +- .../CHANGELOG.md | 27 + .../package.json | 2 +- packages/backend-next/CHANGELOG.md | 37 + packages/backend-next/package.json | 2 +- packages/backend-openapi-utils/CHANGELOG.md | 8 + packages/backend-openapi-utils/package.json | 2 +- packages/backend-plugin-api/CHANGELOG.md | 12 + packages/backend-plugin-api/package.json | 2 +- packages/backend-tasks/CHANGELOG.md | 11 + packages/backend-tasks/package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 13 + packages/backend-test-utils/package.json | 2 +- packages/backend/CHANGELOG.md | 42 + packages/backend/package.json | 2 +- packages/catalog-client/CHANGELOG.md | 8 + packages/catalog-client/package.json | 2 +- packages/catalog-model/CHANGELOG.md | 12 + packages/catalog-model/package.json | 2 +- packages/cli/CHANGELOG.md | 19 + packages/cli/package.json | 2 +- packages/core-compat-api/CHANGELOG.md | 10 + packages/core-compat-api/package.json | 2 +- packages/core-components/CHANGELOG.md | 12 + packages/core-components/package.json | 2 +- packages/create-app/CHANGELOG.md | 10 + packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 14 + packages/dev-utils/package.json | 2 +- packages/e2e-test/CHANGELOG.md | 9 + packages/e2e-test/package.json | 2 +- packages/frontend-app-api/CHANGELOG.md | 15 + packages/frontend-app-api/package.json | 2 +- packages/frontend-plugin-api/CHANGELOG.md | 10 + packages/frontend-plugin-api/package.json | 2 +- packages/frontend-test-utils/CHANGELOG.md | 10 + packages/frontend-test-utils/package.json | 2 +- packages/repo-tools/CHANGELOG.md | 12 + packages/repo-tools/package.json | 2 +- .../techdocs-cli-embedded-app/CHANGELOG.md | 19 + .../techdocs-cli-embedded-app/package.json | 2 +- packages/techdocs-cli/CHANGELOG.md | 12 + packages/techdocs-cli/package.json | 2 +- packages/test-utils/CHANGELOG.md | 13 + packages/test-utils/package.json | 2 +- packages/theme/CHANGELOG.md | 6 + packages/theme/package.json | 2 +- plugins/api-docs/CHANGELOG.md | 16 + plugins/api-docs/package.json | 2 +- plugins/app-backend/CHANGELOG.md | 14 + plugins/app-backend/package.json | 2 +- plugins/app-node/CHANGELOG.md | 8 + plugins/app-node/package.json | 2 +- plugins/app-visualizer/CHANGELOG.md | 9 + plugins/app-visualizer/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/auth-backend/CHANGELOG.md | 30 + plugins/auth-backend/package.json | 2 +- plugins/auth-node/CHANGELOG.md | 14 + plugins/auth-node/package.json | 2 +- plugins/auth-react/CHANGELOG.md | 10 + plugins/auth-react/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 17 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 13 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../CHANGELOG.md | 17 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../catalog-backend-module-gcp/CHANGELOG.md | 13 + .../catalog-backend-module-gcp/package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 17 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 16 + .../package.json | 2 +- .../CHANGELOG.md | 17 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 13 + .../catalog-backend-module-ldap/package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 15 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- plugins/catalog-backend/CHANGELOG.md | 27 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-common/CHANGELOG.md | 9 + plugins/catalog-common/package.json | 2 +- plugins/catalog-graph/CHANGELOG.md | 14 + plugins/catalog-graph/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 18 + plugins/catalog-import/package.json | 2 +- plugins/catalog-node/CHANGELOG.md | 14 + plugins/catalog-node/package.json | 2 +- plugins/catalog-react/CHANGELOG.md | 18 + plugins/catalog-react/package.json | 2 +- .../catalog-unprocessed-entities/CHANGELOG.md | 10 + .../catalog-unprocessed-entities/package.json | 2 +- plugins/catalog/CHANGELOG.md | 22 + plugins/catalog/package.json | 2 +- plugins/config-schema/CHANGELOG.md | 10 + plugins/config-schema/package.json | 2 +- plugins/devtools-backend/CHANGELOG.md | 16 + plugins/devtools-backend/package.json | 2 +- plugins/devtools/CHANGELOG.md | 13 + plugins/devtools/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../events-backend-module-azure/CHANGELOG.md | 8 + .../events-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../events-backend-module-gerrit/CHANGELOG.md | 8 + .../events-backend-module-gerrit/package.json | 2 +- .../events-backend-module-github/CHANGELOG.md | 9 + .../events-backend-module-github/package.json | 2 +- .../events-backend-module-gitlab/CHANGELOG.md | 9 + .../events-backend-module-gitlab/package.json | 2 +- .../events-backend-test-utils/CHANGELOG.md | 7 + .../events-backend-test-utils/package.json | 2 +- plugins/events-backend/CHANGELOG.md | 10 + plugins/events-backend/package.json | 2 +- plugins/events-node/CHANGELOG.md | 7 + plugins/events-node/package.json | 2 +- .../example-todo-list-backend/CHANGELOG.md | 10 + .../example-todo-list-backend/package.json | 2 +- plugins/example-todo-list/CHANGELOG.md | 8 + plugins/example-todo-list/package.json | 2 +- plugins/home-react/CHANGELOG.md | 12 + plugins/home-react/package.json | 2 +- plugins/home/CHANGELOG.md | 22 + plugins/home/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 20 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-cluster/CHANGELOG.md | 12 + plugins/kubernetes-cluster/package.json | 2 +- plugins/kubernetes-common/CHANGELOG.md | 9 + plugins/kubernetes-common/package.json | 2 +- plugins/kubernetes-node/CHANGELOG.md | 10 + plugins/kubernetes-node/package.json | 2 +- plugins/kubernetes-react/CHANGELOG.md | 12 + plugins/kubernetes-react/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 12 + plugins/kubernetes/package.json | 2 +- plugins/notifications-backend/CHANGELOG.md | 18 + plugins/notifications-backend/package.json | 2 +- plugins/notifications-node/CHANGELOG.md | 12 + plugins/notifications-node/package.json | 2 +- plugins/notifications/CHANGELOG.md | 19 + plugins/notifications/package.json | 2 +- plugins/org-react/CHANGELOG.md | 11 + plugins/org-react/package.json | 2 +- plugins/org/CHANGELOG.md | 13 + plugins/org/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/permission-backend/CHANGELOG.md | 13 + plugins/permission-backend/package.json | 2 +- plugins/permission-node/CHANGELOG.md | 12 + plugins/permission-node/package.json | 2 +- plugins/proxy-backend/CHANGELOG.md | 9 + plugins/proxy-backend/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 13 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 32 + plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder-common/CHANGELOG.md | 9 + plugins/scaffolder-common/package.json | 2 +- .../scaffolder-node-test-utils/CHANGELOG.md | 10 + .../scaffolder-node-test-utils/package.json | 2 +- plugins/scaffolder-node/CHANGELOG.md | 13 + plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-react/CHANGELOG.md | 19 + plugins/scaffolder-react/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 25 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 19 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 14 + .../package.json | 2 +- plugins/search-backend-module-pg/CHANGELOG.md | 11 + plugins/search-backend-module-pg/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 18 + .../package.json | 2 +- plugins/search-backend-node/CHANGELOG.md | 14 + plugins/search-backend-node/package.json | 2 +- plugins/search-backend/CHANGELOG.md | 18 + plugins/search-backend/package.json | 2 +- plugins/search-react/CHANGELOG.md | 13 + plugins/search-react/package.json | 2 +- plugins/search/CHANGELOG.md | 16 + plugins/search/package.json | 2 +- plugins/signals-backend/CHANGELOG.md | 13 + plugins/signals-backend/package.json | 2 +- plugins/signals-node/CHANGELOG.md | 12 + plugins/signals-node/package.json | 2 +- plugins/signals/CHANGELOG.md | 11 + plugins/signals/package.json | 2 +- .../techdocs-addons-test-utils/CHANGELOG.md | 16 + .../techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 17 + plugins/techdocs-backend/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 14 + plugins/techdocs-node/package.json | 2 +- plugins/techdocs-react/CHANGELOG.md | 11 + plugins/techdocs-react/package.json | 2 +- plugins/techdocs/CHANGELOG.md | 22 + plugins/techdocs/package.json | 2 +- plugins/user-settings-backend/CHANGELOG.md | 12 + plugins/user-settings-backend/package.json | 2 +- plugins/user-settings/CHANGELOG.md | 15 + plugins/user-settings/package.json | 2 +- yarn.lock | 282 ++- 306 files changed, 4550 insertions(+), 216 deletions(-) create mode 100644 docs/releases/v1.27.0-next.0-changelog.md create mode 100644 plugins/catalog-backend-module-gitlab-org/CHANGELOG.md diff --git a/.changeset/pre.json b/.changeset/pre.json index 87adddcad3..809cd51567 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -281,7 +281,57 @@ "@backstage/plugin-vault": "0.1.29", "@backstage/plugin-vault-backend": "0.4.10", "@backstage/plugin-vault-node": "0.1.10", - "@backstage/plugin-xcmetrics": "0.2.52" + "@backstage/plugin-xcmetrics": "0.2.52", + "@backstage/plugin-catalog-backend-module-gitlab-org": "0.0.0" }, - "changesets": [] + "changesets": [ + "afraid-needles-divide", + "bright-pumpkins-rule", + "chatty-cycles-unite", + "chilly-adults-sing", + "chilly-fireants-roll", + "cold-rats-leave", + "cool-elephants-march", + "cuddly-chairs-kick", + "curvy-planes-flash", + "dry-sloths-impress", + "eighty-apricots-kneel", + "eighty-bats-stare", + "eleven-pandas-divide", + "fix-stackoverflow", + "fluffy-hotels-wait", + "fresh-crews-impress", + "gold-waves-bake", + "gorgeous-cameras-cross", + "green-adults-push", + "hip-carrots-drive", + "itchy-gorillas-hope", + "itchy-keys-wonder", + "late-planes-fix", + "loud-frogs-eat", + "loud-vans-greet", + "lovely-games-cry", + "lucky-news-guess", + "orange-numbers-think", + "perfect-points-hope", + "proud-doors-cheat", + "purple-parents-sin", + "purple-waves-smile", + "rare-fireants-tickle", + "real-crabs-obey", + "renovate-0d0bd5c", + "selfish-pigs-glow", + "shy-students-clap", + "smart-avocados-invent", + "smooth-garlics-behave", + "sour-socks-approve", + "stupid-onions-know", + "tasty-apes-learn", + "thick-llamas-itch", + "tricky-cougars-shout", + "unlucky-days-play", + "unlucky-rivers-collect", + "warm-fans-promise", + "young-olives-drop" + ] } diff --git a/docs/releases/v1.27.0-next.0-changelog.md b/docs/releases/v1.27.0-next.0-changelog.md new file mode 100644 index 0000000000..04ef3c3c2f --- /dev/null +++ b/docs/releases/v1.27.0-next.0-changelog.md @@ -0,0 +1,2064 @@ +# Release v1.27.0-next.0 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.27.0-next.0](https://backstage.github.io/upgrade-helper/?to=1.27.0-next.0) + +## @backstage/catalog-model@1.5.0-next.0 + +### Minor Changes + +- 79025f3: Introduce a domain attribute to the domain entity to allow a hierarchy of domains to exist. + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend@1.22.0-next.0 + +### Minor Changes + +- 8d14475: Emit well known relationships for the Domain entity kind. + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the catalog processing +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.23-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-openapi-utils@0.1.11-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + +## @backstage/app-defaults@1.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/plugin-permission-react@0.4.22 + +## @backstage/backend-app-api@0.7.1-next.0 + +### Patch Changes + +- 4cd5ff0: Add ability to configure the Node.js HTTP Server when configuring the root HTTP Router service +- e8199b1: Move the JWKS registration outside of the lifecycle middleware +- dc8c5dd: The default `TokenManager` implementation no longer requires keys to be configured in production, but it will throw an errors when generating or authenticating tokens. The default `AuthService` implementation will now also provide additional context if such an error is throw when falling back to using the `TokenManager` service to generate tokens for outgoing requests. +- 025641b: Redact `meta` fields too with the logger +- 5863e02: Internal refactor to only create one external token handler +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.5 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.7.29-next.0 + +## @backstage/backend-common@0.21.8-next.0 + +### Patch Changes + +- ba0b8b4: Added option to `ServerTokenManager.fromConfig` that allows it to be instantiated in production without any configured keys. +- Updated dependencies + - @backstage/backend-app-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + +## @backstage/backend-defaults@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.1-next.0 + - @backstage/backend-common@0.21.8-next.0 + +## @backstage/backend-dynamic-feature-service@0.2.10-next.0 + +### Patch Changes + +- b611fd0: Updates the `scanRoot` method in the `PluginScanner` class to specifically ignore the `lost+found` directory, which is a system-generated directory used for file recovery on Unix-like systems. Skipping this directory avoids unnecessary errors. +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/backend-app-api@0.7.1-next.0 + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.5 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.18-next.0 + - @backstage/plugin-events-backend@0.3.5-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/backend-openapi-utils@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + +## @backstage/backend-plugin-api@0.6.18-next.0 + +### Patch Changes + +- 1fedf0c: Removed explicit `toString()` method from `ServiceRef` type. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/backend-tasks@0.5.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/backend-test-utils@0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/catalog-client@1.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/errors@1.2.4 + +## @backstage/cli@0.26.5-next.0 + +### Patch Changes + +- fcd3462: Both the target and types library have been bumped from ES2021 to ES2022 in `@backstage/cli/config/tsconfig.json`. +- 0cc5ed3: Add support for `versions:migrate` to do code changes. Can be skipped with `--no-code-changes` +- f97ad04: Add preserveModules to rollup, which allows better async loading and tree-shaking in webpack +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.5 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.7 + - @backstage/integration@1.10.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + +## @backstage/core-compat-api@0.2.5-next.0 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- Updated dependencies + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/version-bridge@1.0.8 + +## @backstage/core-components@0.14.5-next.0 + +### Patch Changes + +- 71e4229: Added optional `initialShowDropDown` prop to `SidebarSubmenuItem` to internally manage the initial display state of the dropdown items. +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/version-bridge@1.0.8 + +## @backstage/create-app@0.5.15-next.0 + +### Patch Changes + +- c066c88: Removed `packages/backend/src/types.ts` from the template as it is unused. It was mistakenly left in after moving the template to the new backend system. +- 5d99272: Update local development dependencies. +- 8105aad: Removed the Tech Radar and GitHub Actions plugins from the template, which have been moved to the community plugins repository. +- Updated dependencies + - @backstage/cli-common@0.1.13 + +## @backstage/dev-utils@1.0.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/app-defaults@1.5.5-next.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.26 + +## @backstage/frontend-app-api@0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-plugin-api@0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/frontend-test-utils@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.6.5-next.0 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/test-utils@1.5.5-next.0 + - @backstage/types@1.1.1 + +## @backstage/repo-tools@0.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.5 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + +## @techdocs/cli@1.8.10-next.0 + +### Patch Changes + +- 1a0e009: Fix cookie endpoint mock for `serve` +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/plugin-techdocs-node@1.12.4-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + +## @backstage/test-utils@1.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-react@0.4.22 + +## @backstage/theme@0.5.4-next.0 + +### Patch Changes + +- f1462df: Fixed bug where scrollbars don't pick up the theme when in dark mode + +## @backstage/plugin-api-docs@0.11.5-next.0 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/plugin-catalog@1.19.1-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/plugin-permission-react@0.4.22 + +## @backstage/plugin-app-backend@0.3.66-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.18-next.0 + +## @backstage/plugin-app-node@0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config-loader@1.8.0 + +## @backstage/plugin-app-visualizer@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + +## @backstage/plugin-auth-backend@0.22.5-next.0 + +### Patch Changes + +- ea9262b: Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.9-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.1-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.10-next.0 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10-next.0 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.1-next.0 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.1-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.13-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.15-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.15-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.13-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.15-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.11-next.0 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.11-next.0 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.22.5-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.4-next.0 + +### Patch Changes + +- 07d8cca: Error if used outside of a development environment without explicit allowance +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.1.9-next.0 + +### Patch Changes + +- dd53bf3: Add nonce to authorize request to be added in ID token +- Updated dependencies + - @backstage/plugin-auth-backend@0.22.5-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + +## @backstage/plugin-auth-node@0.4.13-next.0 + +### Patch Changes + +- ea9262b: Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-auth-react@0.1.1-next.0 + +### Patch Changes + +- c297afd: When using `CookieAuthRefreshProvider` or `useCookieAuthRefresh`, a 404 response from the cookie endpoint will now be treated as if cookie auth is disabled and is not needed. +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog@1.19.1-next.0 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration-react@1.1.26 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/integration-aws-node@0.1.12 + +## @backstage/plugin-catalog-backend-module-azure@0.1.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1-next.0 + +### Patch Changes + +- f3f0281: Fix incorrect dependency import. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-openapi-utils@0.1.11-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.18 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.32-next.0 + +### Patch Changes + +- 062ffb1: Allow skipping archived repositories (`skipArchivedRepos` flag) on Bitbucket. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + +## @backstage/plugin-catalog-backend-module-github@0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-backend-module-github@0.6.1-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.15-next.2 + +### Patch Changes + +- a70377d: Added events support for `GitlabDiscoveryEntityProvider` and `GitlabOrgDiscoveryEntityProvider`. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.1-next.0 + +### Patch Changes + +- a70377d: Added a new `catalog-backend-module-gitlab-org` module which adds the `GitlabOrgDiscoveryEntityProvider` to the catalog's providers using the new backend system. +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.3.15-next.2 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.23-next.0 + +### Patch Changes + +- 8c1ab9e: Fix plugin/incremental-ingestion 'Maximum call stack size exceeded' error when ingest large entities. +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.1 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-catalog-common@1.0.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-catalog-graph@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-catalog-import@0.10.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration@1.10.0 + - @backstage/integration-react@1.1.26 + +## @backstage/plugin-catalog-node@1.11.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + +## @backstage/plugin-catalog-react@1.11.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration-react@1.1.26 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-react@0.4.22 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + +## @backstage/plugin-config-schema@0.1.55-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-devtools@0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/plugin-devtools-common@0.1.9 + - @backstage/plugin-permission-react@0.4.22 + +## @backstage/plugin-devtools-backend@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.9 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + +## @backstage/plugin-events-backend@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-events-backend-module-azure@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-events-backend-module-gerrit@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-events-backend-module-github@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-events-backend-module-gitlab@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-events-backend-test-utils@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-events-node@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + +## @backstage/plugin-home@0.7.4-next.0 + +### Patch Changes + +- 2196d3e: Use relative time when displaying visits from the same day +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-home-react@0.1.13-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + +## @backstage/plugin-home-react@0.1.13-next.0 + +### Patch Changes + +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + +## @backstage/plugin-kubernetes@0.11.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/plugin-kubernetes-react@0.3.5-next.0 + - @backstage/core-plugin-api@1.9.2 + +## @backstage/plugin-kubernetes-backend@0.17.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-kubernetes-node@0.1.12-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/plugin-kubernetes-react@0.3.5-next.0 + - @backstage/core-plugin-api@1.9.2 + +## @backstage/plugin-kubernetes-common@0.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-kubernetes-node@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-kubernetes-react@0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## @backstage/plugin-notifications@0.2.1-next.0 + +### Patch Changes + +- e6bf85f: Allow overriding `NotificationsPage` page properties +- a42a19b: Empty descriptions are not rendered to improve the look&feel. +- 1bc3b86: Fix to show web notifications even when browser is on foreground. Fix duplicate notifications with multiple tabs. +- f793112: Allow defining `className` and additional properties for `NotificationsSideBarItem` +- e1c7d6e: Fix infinite loop in the notification title counter +- fcda449: The rendered size of a notification is limited for very long descriptions. +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.3 + - @backstage/plugin-signals-react@0.0.3 + +## @backstage/plugin-notifications-backend@0.2.1-next.0 + +### Patch Changes + +- ee09dfc: Updated documentation for sending messages by external services. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-notifications-node@0.1.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-notifications-common@0.0.3 + - @backstage/plugin-signals-node@0.1.4-next.0 + +## @backstage/plugin-notifications-node@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-notifications-common@0.0.3 + - @backstage/plugin-signals-node@0.1.4-next.0 + +## @backstage/plugin-org@0.6.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + +## @backstage/plugin-org-react@0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-plugin-api@1.9.2 + +## @backstage/plugin-permission-backend@0.5.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + +## @backstage/plugin-permission-node@0.7.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-proxy-backend@0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + +## @backstage/plugin-scaffolder@1.19.4-next.0 + +### Patch Changes + +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-scaffolder-react@1.8.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration@1.10.0 + - @backstage/integration-react@1.1.26 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-react@0.4.22 + +## @backstage/plugin-scaffolder-backend@1.22.5-next.0 + +### Patch Changes + +- 025641b: Fix issue with the log format not being respected when logging from actions +- 025641b: Redact `meta` fields too with the logger +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.8-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.10-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.0 + +### Patch Changes + +- 24dd655: Add examples for `publish:bitbucketServer:pull-request` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.8-next.0 + +### Patch Changes + +- 554af73: Allow defining `repoVisibility` field for the action `publish:gitea` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + +## @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.0 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.0 + +### Patch Changes + +- ffc73ec: Add examples for `gitlab:repo:push` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.4-next.0 + +## @backstage/plugin-scaffolder-common@1.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-scaffolder-node@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-test-utils@0.3.8-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-scaffolder-react@1.8.5-next.0 + +### Patch Changes + +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-search@1.4.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + +## @backstage/plugin-search-backend@1.5.8-next.0 + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the search index registry +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/repo-tools@0.8.1-next.0 + - @backstage/backend-openapi-utils@0.1.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-catalog@0.1.23-next.0 + +### Patch Changes + +- d5fff66: Fix wiring of the module exported at the `/alpha` path, which was causing authentication failures. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-elasticsearch@1.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-explore@0.1.23-next.0 + +### Patch Changes + +- ca6e2e0: Migrate search collator to use the new auth services. +- 5d99272: Update README.md to point to explore plugin in community-plugins repository. +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-pg@0.5.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-module-techdocs@0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-techdocs-node@1.12.4-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-backend-node@1.2.22-next.0 + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the search index registry +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-search-react@1.7.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-signals@0.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.3 + +## @backstage/plugin-signals-backend@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-signals-node@0.1.4-next.0 + +## @backstage/plugin-signals-node@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.4-next.0 + +## @backstage/plugin-techdocs@1.10.5-next.0 + +### Patch Changes + +- d2cc139: Update path in Readme for Plugin Techdocs to show the correct setup information. +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-react@0.1.1-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration@1.10.0 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.32-next.0 + +### Patch Changes + +- 2f13862: Fix bug in TechDocsAddonTester when jest.resetAllMocks is called between tests +- Updated dependencies + - @backstage/plugin-catalog@1.19.1-next.0 + - @backstage/plugin-techdocs@1.10.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.26 + - @backstage/test-utils@1.5.5-next.0 + - @backstage/plugin-search-react@1.7.11-next.0 + +## @backstage/plugin-techdocs-backend@1.10.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.0 + - @backstage/plugin-techdocs-node@1.12.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/plugin-permission-common@0.7.13 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration@1.10.0 + - @backstage/integration-react@1.1.26 + +## @backstage/plugin-techdocs-node@1.12.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.11 + +## @backstage/plugin-techdocs-react@1.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/version-bridge@1.0.8 + +## @backstage/plugin-user-settings@0.8.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/types@1.1.1 + +## @backstage/plugin-user-settings-backend@0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + +## example-app@0.2.97-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications@0.2.1-next.0 + - @backstage/plugin-api-docs@0.11.5-next.0 + - @backstage/plugin-catalog@1.19.1-next.0 + - @backstage/plugin-home@0.7.4-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-techdocs@1.10.5-next.0 + - @backstage/cli@0.26.5-next.0 + - @backstage/plugin-auth-react@0.1.1-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-scaffolder-react@1.8.5-next.0 + - @backstage/plugin-scaffolder@1.19.4-next.0 + - @backstage/plugin-catalog-graph@0.4.5-next.0 + - @backstage/plugin-catalog-import@0.10.11-next.0 + - @backstage/plugin-devtools@0.1.14-next.0 + - @backstage/plugin-org@0.6.25-next.0 + - @backstage/plugin-search@1.4.11-next.0 + - @backstage/plugin-user-settings@0.8.6-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.0 + - @backstage/plugin-kubernetes@0.11.10-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/app-defaults@1.5.5-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-app-api@0.6.5-next.0 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + - @backstage/plugin-signals@0.0.6-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.0 + +## example-app-next@0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications@0.2.1-next.0 + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/plugin-api-docs@0.11.5-next.0 + - @backstage/plugin-catalog@1.19.1-next.0 + - @backstage/plugin-home@0.7.4-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-techdocs@1.10.5-next.0 + - @backstage/cli@0.26.5-next.0 + - @backstage/plugin-auth-react@0.1.1-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-scaffolder-react@1.8.5-next.0 + - @backstage/plugin-scaffolder@1.19.4-next.0 + - @backstage/plugin-catalog-graph@0.4.5-next.0 + - @backstage/plugin-catalog-import@0.10.11-next.0 + - @backstage/plugin-org@0.6.25-next.0 + - @backstage/plugin-search@1.4.11-next.0 + - @backstage/plugin-user-settings@0.8.6-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.0 + - @backstage/plugin-kubernetes@0.11.10-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/app-defaults@1.5.5-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-app-api@0.6.5-next.0 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-app-visualizer@0.1.6-next.0 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + - @backstage/plugin-signals@0.0.6-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.0 + +## app-next-example-plugin@0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/frontend-plugin-api@0.6.5-next.0 + +## example-backend@0.2.98-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/plugin-scaffolder-backend@1.22.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/plugin-search-backend@1.5.8-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.23-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.23-next.0 + - @backstage/plugin-auth-backend@0.22.5-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - example-app@0.2.97-next.0 + - @backstage/plugin-app-backend@0.3.66-next.0 + - @backstage/plugin-kubernetes-backend@0.17.1-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.0 + - @backstage/plugin-techdocs-backend@1.10.5-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + - @backstage/plugin-devtools-backend@0.3.4-next.0 + - @backstage/plugin-events-backend@0.3.5-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-permission-backend@0.5.42-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-proxy-backend@0.4.16-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.1-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.27-next.0 + - @backstage/plugin-signals-backend@0.1.4-next.0 + - @backstage/plugin-signals-node@0.1.4-next.0 + +## example-backend-next@0.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.0 + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/plugin-scaffolder-backend@1.22.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/plugin-search-backend@1.5.8-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.23-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.4-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.23-next.0 + - @backstage/plugin-auth-backend@0.22.5-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/plugin-notifications-backend@0.2.1-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.36-next.0 + - @backstage/backend-defaults@0.2.18-next.0 + - @backstage/plugin-app-backend@0.3.66-next.0 + - @backstage/plugin-kubernetes-backend@0.17.1-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.0 + - @backstage/plugin-techdocs-backend@1.10.5-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.0 + - @backstage/plugin-devtools-backend@0.3.4-next.0 + - @backstage/plugin-permission-backend@0.5.42-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-proxy-backend@0.4.16-next.0 + - @backstage/plugin-signals-backend@0.1.4-next.0 + +## e2e-test@0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.15-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.4 + +## techdocs-cli-embedded-app@0.2.96-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.19.1-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-techdocs@1.10.5-next.0 + - @backstage/cli@0.26.5-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/app-defaults@1.5.5-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.26 + - @backstage/test-utils@1.5.5-next.0 + +## @internal/plugin-todo-list@1.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + +## @internal/plugin-todo-list-backend@1.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 diff --git a/package.json b/package.json index 7ccbce417d..abae1b54ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.26.0", + "version": "1.27.0-next.0", "private": true, "repository": { "type": "git", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 7d7e199aca..9efa40b6cc 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/app-defaults +## 1.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/plugin-permission-react@0.4.22 + ## 1.5.4 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index ac11707277..ad9986a29a 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.5.4", + "version": "1.5.5-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 6a10864bd5..33f564936a 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.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/frontend-plugin-api@0.6.5-next.0 + ## 0.0.10 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index dba0613d16..acd85fc7cb 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.10", + "version": "0.0.11-next.0", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin" diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 8b24a1e2d1..1188aff451 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,48 @@ # example-app-next +## 0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications@0.2.1-next.0 + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/plugin-api-docs@0.11.5-next.0 + - @backstage/plugin-catalog@1.19.1-next.0 + - @backstage/plugin-home@0.7.4-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-techdocs@1.10.5-next.0 + - @backstage/cli@0.26.5-next.0 + - @backstage/plugin-auth-react@0.1.1-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-scaffolder-react@1.8.5-next.0 + - @backstage/plugin-scaffolder@1.19.4-next.0 + - @backstage/plugin-catalog-graph@0.4.5-next.0 + - @backstage/plugin-catalog-import@0.10.11-next.0 + - @backstage/plugin-org@0.6.25-next.0 + - @backstage/plugin-search@1.4.11-next.0 + - @backstage/plugin-user-settings@0.8.6-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.0 + - @backstage/plugin-kubernetes@0.11.10-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/app-defaults@1.5.5-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-app-api@0.6.5-next.0 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-app-visualizer@0.1.6-next.0 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + - @backstage/plugin-signals@0.0.6-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.0 + ## 0.0.10 ### Patch Changes diff --git a/packages/app-next/package.json b/packages/app-next/package.json index fdb84f3e81..065aa43322 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.10", + "version": "0.0.11-next.0", "private": true, "repository": { "type": "git", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 1d298af752..10c2873bc4 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,46 @@ # example-app +## 0.2.97-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-notifications@0.2.1-next.0 + - @backstage/plugin-api-docs@0.11.5-next.0 + - @backstage/plugin-catalog@1.19.1-next.0 + - @backstage/plugin-home@0.7.4-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-techdocs@1.10.5-next.0 + - @backstage/cli@0.26.5-next.0 + - @backstage/plugin-auth-react@0.1.1-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-scaffolder-react@1.8.5-next.0 + - @backstage/plugin-scaffolder@1.19.4-next.0 + - @backstage/plugin-catalog-graph@0.4.5-next.0 + - @backstage/plugin-catalog-import@0.10.11-next.0 + - @backstage/plugin-devtools@0.1.14-next.0 + - @backstage/plugin-org@0.6.25-next.0 + - @backstage/plugin-search@1.4.11-next.0 + - @backstage/plugin-user-settings@0.8.6-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.0 + - @backstage/plugin-kubernetes@0.11.10-next.0 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/app-defaults@1.5.5-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-app-api@0.6.5-next.0 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + - @backstage/plugin-signals@0.0.6-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.0 + ## 0.2.96 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 809bf35313..b0e3a8fee3 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.96", + "version": "0.2.97-next.0", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 15d50cac23..30515c50ff 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/backend-app-api +## 0.7.1-next.0 + +### Patch Changes + +- 4cd5ff0: Add ability to configure the Node.js HTTP Server when configuring the root HTTP Router service +- e8199b1: Move the JWKS registration outside of the lifecycle middleware +- dc8c5dd: The default `TokenManager` implementation no longer requires keys to be configured in production, but it will throw an errors when generating or authenticating tokens. The default `AuthService` implementation will now also provide additional context if such an error is throw when falling back to using the `TokenManager` service to generate tokens for outgoing requests. +- 025641b: Redact `meta` fields too with the logger +- 5863e02: Internal refactor to only create one external token handler +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.5 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-node@0.7.29-next.0 + ## 0.7.0 ### Minor Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 072efa7de8..f38dfb20d2 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-app-api", "description": "Core API used by Backstage backend apps", - "version": "0.7.0", + "version": "0.7.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 61b95f211f..fdeaf6d137 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/backend-common +## 0.21.8-next.0 + +### Patch Changes + +- ba0b8b4: Added option to `ServerTokenManager.fromConfig` that allows it to be instantiated in production without any configured keys. +- Updated dependencies + - @backstage/backend-app-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-dev-utils@0.1.4 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + ## 0.21.7 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 9897afcf5a..f6003d9a0e 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.21.7", + "version": "0.21.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index a939df4eba..fd311f7d2b 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-defaults +## 0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.1-next.0 + - @backstage/backend-common@0.21.8-next.0 + ## 0.2.17 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 953449913e..8b061b15f4 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", "description": "Backend defaults used by Backstage backend apps", - "version": "0.2.17", + "version": "0.2.18-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index 8eee7f5dcb..95b6097025 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/backend-dynamic-feature-service +## 0.2.10-next.0 + +### Patch Changes + +- b611fd0: Updates the `scanRoot` method in the `PluginScanner` class to specifically ignore the `lost+found` directory, which is a system-generated directory used for file recovery on Unix-like systems. Skipping this directory avoids unnecessary errors. +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/backend-app-api@0.7.1-next.0 + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.5 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.18-next.0 + - @backstage/plugin-events-backend@0.3.5-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-search-common@1.2.11 + ## 0.2.9 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 593dbf155b..07a559c748 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-dynamic-feature-service", "description": "Backstage dynamic feature service", - "version": "0.2.9", + "version": "0.2.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-next/CHANGELOG.md b/packages/backend-next/CHANGELOG.md index 70dac3ac7f..3c48343ee4 100644 --- a/packages/backend-next/CHANGELOG.md +++ b/packages/backend-next/CHANGELOG.md @@ -1,5 +1,42 @@ # example-backend-next +## 0.0.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.0 + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/plugin-scaffolder-backend@1.22.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/plugin-search-backend@1.5.8-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.23-next.0 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1-next.0 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.4-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.23-next.0 + - @backstage/plugin-auth-backend@0.22.5-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/plugin-notifications-backend@0.2.1-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-backend-module-openapi@0.1.36-next.0 + - @backstage/backend-defaults@0.2.18-next.0 + - @backstage/plugin-app-backend@0.3.66-next.0 + - @backstage/plugin-kubernetes-backend@0.17.1-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.0 + - @backstage/plugin-techdocs-backend@1.10.5-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.0 + - @backstage/plugin-devtools-backend@0.3.4-next.0 + - @backstage/plugin-permission-backend@0.5.42-next.0 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-proxy-backend@0.4.16-next.0 + - @backstage/plugin-signals-backend@0.1.4-next.0 + ## 0.0.25 ### Patch Changes diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index 9de8bb1788..65416da7a3 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-next", - "version": "0.0.25", + "version": "0.0.26-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index 60df1440fa..2e0d578fdc 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-openapi-utils +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + ## 0.1.10 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index 65806fa949..c95e01bfd7 100644 --- a/packages/backend-openapi-utils/package.json +++ b/packages/backend-openapi-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-openapi-utils", "description": "OpenAPI typescript support.", - "version": "0.1.10", + "version": "0.1.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 168970dc6c..4dccd8d1bc 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/backend-plugin-api +## 0.6.18-next.0 + +### Patch Changes + +- 1fedf0c: Removed explicit `toString()` method from `ServiceRef` type. +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + ## 0.6.17 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 3a5b8bdb9d..7b0eb70cad 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-plugin-api", "description": "Core API used by Backstage backend plugins", - "version": "0.6.17", + "version": "0.6.18-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 7d7cb842c4..5fa17ac1fd 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/backend-tasks +## 0.5.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.5.22 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index a996207ece..77d4ff60c7 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-tasks", "description": "Common distributed task management library for Backstage backends", - "version": "0.5.22", + "version": "0.5.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 77abe491c2..03886c776d 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/backend-test-utils +## 0.3.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.1-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.3.7 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 6035e47a4b..1d43e24adf 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": "0.3.7", + "version": "0.3.8-next.0", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index b0bf1a8b91..ce08ade977 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,47 @@ # example-backend +## 0.2.98-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/plugin-scaffolder-backend@1.22.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/plugin-search-backend@1.5.8-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.23-next.0 + - @backstage/plugin-search-backend-module-explore@0.1.23-next.0 + - @backstage/plugin-auth-backend@0.22.5-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - example-app@0.2.97-next.0 + - @backstage/plugin-app-backend@0.3.66-next.0 + - @backstage/plugin-kubernetes-backend@0.17.1-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.0 + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.0 + - @backstage/plugin-techdocs-backend@1.10.5-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + - @backstage/plugin-devtools-backend@0.3.4-next.0 + - @backstage/plugin-events-backend@0.3.5-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-permission-backend@0.5.42-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-proxy-backend@0.4.16-next.0 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.1-next.0 + - @backstage/plugin-search-backend-module-pg@0.5.27-next.0 + - @backstage/plugin-signals-backend@0.1.4-next.0 + - @backstage/plugin-signals-node@0.1.4-next.0 + ## 0.2.97 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index ee2d70a09a..378661ae12 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.97", + "version": "0.2.98-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index b210a4c653..ef3ca6588e 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/catalog-client +## 1.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/errors@1.2.4 + ## 1.6.4 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 1a942e7182..39d8227fa5 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "1.6.4", + "version": "1.6.5-next.0", "description": "An isomorphic client for the catalog backend", "backstage": { "role": "common-library" diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 4a3633d5de..c04ac72136 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/catalog-model +## 1.5.0-next.0 + +### Minor Changes + +- 79025f3: Introduce a domain attribute to the domain entity to allow a hierarchy of domains to exist. + +### Patch Changes + +- Updated dependencies + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 1.4.5 ### Patch Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 2dd4001d00..5622a20941 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "1.4.5", + "version": "1.5.0-next.0", "description": "Types and validators that help describe the model of a Backstage Catalog", "backstage": { "role": "common-library" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 6a466591b2..6c37772f73 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/cli +## 0.26.5-next.0 + +### Patch Changes + +- fcd3462: Both the target and types library have been bumped from ES2021 to ES2022 in `@backstage/cli/config/tsconfig.json`. +- 0cc5ed3: Add support for `versions:migrate` to do code changes. Can be skipped with `--no-code-changes` +- f97ad04: Add preserveModules to rollup, which allows better async loading and tree-shaking in webpack +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.5 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/eslint-plugin@0.1.7 + - @backstage/integration@1.10.0 + - @backstage/release-manifests@0.0.11 + - @backstage/types@1.1.1 + ## 0.26.3 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 537aec421d..ecdfa590d6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.26.3", + "version": "0.26.5-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 6f7c38735a..29ebd0cc2b 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/core-compat-api +## 0.2.5-next.0 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- Updated dependencies + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/version-bridge@1.0.8 + ## 0.2.4 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 6dd2115b9c..a7bab7b52f 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.2.4", + "version": "0.2.5-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index da04ef9e2b..4e8f90b441 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/core-components +## 0.14.5-next.0 + +### Patch Changes + +- 71e4229: Added optional `initialShowDropDown` prop to `SidebarSubmenuItem` to internally manage the initial display state of the dropdown items. +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/version-bridge@1.0.8 + ## 0.14.4 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 9f632bcc7f..df637fa3a8 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.14.4", + "version": "0.14.5-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 1613f381dd..2706fc14b5 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/create-app +## 0.5.15-next.0 + +### Patch Changes + +- c066c88: Removed `packages/backend/src/types.ts` from the template as it is unused. It was mistakenly left in after moving the template to the new backend system. +- 5d99272: Update local development dependencies. +- 8105aad: Removed the Tech Radar and GitHub Actions plugins from the template, which have been moved to the community plugins repository. +- Updated dependencies + - @backstage/cli-common@0.1.13 + ## 0.5.14 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index e5ec68b219..28e2fd1087 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.5.14", + "version": "0.5.15-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index fdeaa1a203..4b29d5fc31 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/dev-utils +## 1.0.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/app-defaults@1.5.5-next.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.26 + ## 1.0.31 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 7946625ffa..e987314494 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.0.31", + "version": "1.0.32-next.0", "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 fc0bb08cdf..4aec992020 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,14 @@ # e2e-test +## 0.2.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.15-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/errors@1.2.4 + ## 0.2.15 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index bc25199cb6..ae4b15aec0 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,7 +1,7 @@ { "name": "e2e-test", "description": "E2E test for verifying Backstage packages", - "version": "0.2.15", + "version": "0.2.16-next.0", "private": true, "backstage": { "role": "cli" diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index 965c3e189d..f7d85d19cf 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/frontend-app-api +## 0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 0.6.4 ### Patch Changes diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index 07b5685a1e..c131e81b41 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.6.4", + "version": "0.6.5-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index 6a98ec9810..222732d4db 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/frontend-plugin-api +## 0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 0.6.4 ### Patch Changes diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index ca56db2970..802ab13d41 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.6.4", + "version": "0.6.5-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index c35b54f728..b0f052b3ee 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/frontend-test-utils +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.6.5-next.0 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/test-utils@1.5.5-next.0 + - @backstage/types@1.1.1 + ## 0.1.6 ### Patch Changes diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index d249878024..862d4c449f 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.1.6", + "version": "0.1.7-next.0", "backstage": { "role": "web-library" }, diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index a3af98d211..7b2646ad2b 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/repo-tools +## 0.8.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/cli-node@0.2.5 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + ## 0.8.0 ### Minor Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index e3ba518ee2..5d53a9cff6 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/repo-tools", "description": "CLI for Backstage repo tooling ", - "version": "0.8.0", + "version": "0.8.1-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 4f13e2c25a..5764b6e71e 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,24 @@ # techdocs-cli-embedded-app +## 0.2.96-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.19.1-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-techdocs@1.10.5-next.0 + - @backstage/cli@0.26.5-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/app-defaults@1.5.5-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.26 + - @backstage/test-utils@1.5.5-next.0 + ## 0.2.95 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 2a90a90246..00083edb6d 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.95", + "version": "0.2.96-next.0", "private": true, "backstage": { "role": "frontend" diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 5f7d2d2ddc..1525b13414 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,17 @@ # @techdocs/cli +## 1.8.10-next.0 + +### Patch Changes + +- 1a0e009: Fix cookie endpoint mock for `serve` +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/plugin-techdocs-node@1.12.4-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + ## 1.8.9 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 7525e53afc..4edb8f47a4 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.8.9", + "version": "1.8.10-next.0", "publishConfig": { "access": "public" }, diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 169886aaec..d28d0cac49 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/test-utils +## 1.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-react@0.4.22 + ## 1.5.4 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 70ff1206ee..2e46e3a9b0 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.5.4", + "version": "1.5.5-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 9f4007a2ac..20703a1d9d 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/theme +## 0.5.4-next.0 + +### Patch Changes + +- f1462df: Fixed bug where scrollbars don't pick up the theme when in dark mode + ## 0.5.3 ### Patch Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index f1c4f00d8f..07eefe756f 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/theme", - "version": "0.5.3", + "version": "0.5.4-next.0", "description": "material-ui theme for use with Backstage.", "backstage": { "role": "web-library" diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 7883dab3fb..370b221ea2 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-api-docs +## 0.11.5-next.0 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/plugin-catalog@1.19.1-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/plugin-permission-react@0.4.22 + ## 0.11.4 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index a686b952b5..5d0521f283 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.11.4", + "version": "0.11.5-next.0", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin" diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 8768724d13..b0629a2e92 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-app-backend +## 0.3.66-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-app-node@0.1.18-next.0 + ## 0.3.65 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index d7a3c4ee72..d9fba47760 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.65", + "version": "0.3.66-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index ad315d2581..58350a2337 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-app-node +## 0.1.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config-loader@1.8.0 + ## 0.1.17 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index ccb07f4d34..9edf577db2 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-node", "description": "Node.js library for the app plugin", - "version": "0.1.17", + "version": "0.1.18-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index 7d785cf830..f9a70ea08b 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-app-visualizer +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + ## 0.1.5 ### Patch Changes diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index b17ae9b9e4..c711a9f8b8 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.5", + "version": "0.1.6-next.0", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin" diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index d2c8ddcbad..ed1a3efc23 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + ## 0.1.9 ### Patch Changes diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index 45ac854f0c..386b7e6df1 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", "description": "The atlassian-provider backend module for the auth plugin.", - "version": "0.1.9", + "version": "0.1.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index 8280573fc1..c2c299a055 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.22.5-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + ## 0.1.9 ### 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 08cc174e13..9e1139c0a3 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", "description": "The aws-alb provider module for the Backstage auth backend.", - "version": "0.1.9", + "version": "0.1.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md index 07d9ac0d68..416ad5b0c0 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + ## 0.1.0 ### Minor Changes diff --git a/plugins/auth-backend-module-azure-easyauth-provider/package.json b/plugins/auth-backend-module-azure-easyauth-provider/package.json index 08d20ffb8e..8398f16910 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.1.0", + "version": "0.1.1-next.0", "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 188ec6445f..cdfbee09c5 100644 --- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-bitbucket-provider +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index 90279e0c49..37016f4e5c 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.1.0", + "version": "0.1.1-next.0", "description": "The bitbucket-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 8ea04f1bd0..d3d2fabdb4 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.0 ### Minor Changes diff --git a/plugins/auth-backend-module-cloudflare-access-provider/package.json b/plugins/auth-backend-module-cloudflare-access-provider/package.json index a7b6aab15c..52e67198a6 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.1.0", + "version": "0.1.1-next.0", "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 9d600c169b..d9d6a0f7f1 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.2.12 ### 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 156732fcb5..a67afd714e 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", "description": "A GCP IAP auth provider module for the Backstage auth backend", - "version": "0.2.12", + "version": "0.2.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index 73a7f93cca..960322f0b4 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + ## 0.1.14 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index 314cf23d2d..a1b5abd032 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", "description": "The github-provider backend module for the auth plugin.", - "version": "0.1.14", + "version": "0.1.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index 93739aaf82..38148c2d3b 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + ## 0.1.14 ### Patch Changes diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index 0f0ec79453..5226dbd481 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", "description": "The gitlab-provider backend module for the auth plugin.", - "version": "0.1.14", + "version": "0.1.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index 3cf73dfada..3f89f0a68b 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-google-provider +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + ## 0.1.14 ### Patch Changes diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index ecfd4b5a14..5b52825f00 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", "description": "A Google auth provider module for the Backstage auth backend", - "version": "0.1.14", + "version": "0.1.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index 759bb14463..befe66eb79 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.1.4-next.0 + +### Patch Changes + +- 07d8cca: Error if used outside of a development environment without explicit allowance +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + ## 0.1.3 ### Patch Changes diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index 67d7598a8b..827babe0f5 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.1.3", + "version": "0.1.4-next.0", "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 1865ee7fe1..49e733cdba 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + ## 0.1.12 ### Patch Changes diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index 0cf43b6166..217cc92e67 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", "description": "The microsoft-provider backend module for the auth plugin.", - "version": "0.1.12", + "version": "0.1.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index 029f3705f0..f41ccace1c 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + ## 0.1.14 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index ece072b69c..16a0eef71c 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", "description": "The oauth2-provider backend module for the auth plugin.", - "version": "0.1.14", + "version": "0.1.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index 28c75592b0..0fd1fa1d03 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-oauth2-proxy-provider +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + ## 0.1.10 ### 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 42b8e9d320..fdf6ebf59e 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", "description": "The oauth2-proxy-provider backend module for the auth plugin.", - "version": "0.1.10", + "version": "0.1.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 6b5a1195d2..981e1ed364 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.1.9-next.0 + +### Patch Changes + +- dd53bf3: Add nonce to authorize request to be added in ID token +- Updated dependencies + - @backstage/plugin-auth-backend@0.22.5-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + ## 0.1.8 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index 34be8ef931..2d82a36796 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", "description": "The oidc-provider backend module for the auth plugin.", - "version": "0.1.8", + "version": "0.1.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index cba0e66ed7..c8c50918dd 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + ## 0.0.10 ### Patch Changes diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index 595f99c933..cdf096745e 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-okta-provider", "description": "The okta-provider backend module for the auth plugin.", - "version": "0.0.10", + "version": "0.0.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index e78fc901b7..65477d4a1c 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + ## 0.1.11 ### Patch Changes diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index fe29cb2592..67999e1088 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", "description": "The pinniped-provider backend module for the auth plugin.", - "version": "0.1.11", + "version": "0.1.12-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md index 4ca95a085d..0105253b11 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + ## 0.1.9 ### 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 71813d653f..f09d750a1b 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.1.9", + "version": "0.1.10-next.0", "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 77e4ddd20a..af0be37f7b 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-auth-backend +## 0.22.5-next.0 + +### Patch Changes + +- ea9262b: Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.9-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.1-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.10-next.0 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10-next.0 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.1-next.0 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.1-next.0 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.13-next.0 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.0 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.15-next.0 + - @backstage/plugin-auth-backend-module-google-provider@0.1.15-next.0 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.13-next.0 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.15-next.0 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.11-next.0 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.11-next.0 + ## 0.22.4 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 91b28063d4..0c3948550c 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.22.4", + "version": "0.22.5-next.0", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin" diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index 563a7eda9c..61a00fa3cf 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-auth-node +## 0.4.13-next.0 + +### Patch Changes + +- ea9262b: Allow overriding default ownership resolving with the new `AuthOwnershipResolutionExtensionPoint` +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.4.12 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 183388e432..f1c2be92a5 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.4.12", + "version": "0.4.13-next.0", "backstage": { "role": "node-library" }, diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index eb21584bbc..370d2d2f47 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-react +## 0.1.1-next.0 + +### Patch Changes + +- c297afd: When using `CookieAuthRefreshProvider` or `useCookieAuthRefresh`, a 404 response from the cookie endpoint will now be treated as if cookie auth is disabled and is not needed. +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + ## 0.1.0 ### Minor Changes diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index 625798ee6e..c1be6d5f5a 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.0", + "version": "0.1.1-next.0", "description": "Web library for the auth plugin", "backstage": { "role": "web-library" diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index b371ddc2af..6264bfa38e 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.3.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/integration-aws-node@0.1.12 + ## 0.3.12 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 3b8fec8b1f..2bda0583f1 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.3.12", + "version": "0.3.13-next.0", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index e9a3e83a5e..38c29b1ade 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + ## 0.1.37 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index d1a7f9e851..d013c903b3 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.1.37", + "version": "0.1.38-next.0", "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 3a7b92c12e..fba33c49ad 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.2.1-next.0 + +### Patch Changes + +- f3f0281: Fix incorrect dependency import. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-openapi-utils@0.1.11-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.2.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 12933b5997..0acac7f75a 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.2.0", + "version": "0.2.1-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index 7040c696e8..5e3b4be51f 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.18 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.2.4 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index b32b03c63f..c0863627cf 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.2.4", + "version": "0.2.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index ecb2713666..ed4bbd6696 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.32-next.0 + +### Patch Changes + +- 062ffb1: Allow skipping archived repositories (`skipArchivedRepos` flag) on Bitbucket. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + ## 0.1.31 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 3247b326a4..a656cd93a3 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.1.31", + "version": "0.1.32-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 93f2e3eec5..8a66b028ca 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.1.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + ## 0.1.18 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 4769adfdb9..13a5f542fb 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.1.18", + "version": "0.1.19-next.0", "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 ce41f869f5..f4569fde80 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + ## 0.1.34 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 03c96bab63..674263fb32 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.1.34", + "version": "0.1.35-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index d0163c07c7..c5cddaefd5 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-backend-module-github@0.6.1-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.1.12 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index 0abc15c795..a04f926e7b 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.1.12", + "version": "0.1.13-next.0", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 471397861a..b9ca9815fd 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-backend-module-github +## 0.6.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.6.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 2067ec340a..8329ed16b8 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.6.0", + "version": "0.6.1-next.0", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md new file mode 100644 index 0000000000..fc2bd62881 --- /dev/null +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -0,0 +1,13 @@ +# @backstage/plugin-catalog-backend-module-gitlab-org + +## 0.0.1-next.0 + +### Patch Changes + +- a70377d: Added a new `catalog-backend-module-gitlab-org` module which adds the `GitlabOrgDiscoveryEntityProvider` to the catalog's providers using the new backend system. +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.3.15-next.2 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 7d92fda325..942486d30f 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.0.0", + "version": "0.0.1-next.0", "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 92f0ea7f21..5801ccd836 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.3.15-next.2 + +### Patch Changes + +- a70377d: Added events support for `GitlabDiscoveryEntityProvider` and `GitlabOrgDiscoveryEntityProvider`. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.3.15 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 99fdde8346..b60b87b544 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -72,5 +72,5 @@ ] } }, - "version": "0.3.15-next.1" + "version": "0.3.15-next.2" } diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index b922cf5f4f..06249b41ff 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.4.23-next.0 + +### Patch Changes + +- 8c1ab9e: Fix plugin/incremental-ingestion 'Maximum call stack size exceeded' error when ingest large entities. +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-permission-common@0.7.13 + ## 0.4.22 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index c8cc5456b0..8e428e19a6 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.4.22", + "version": "0.4.23-next.0", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index b8fad6f530..5deb8cfe2b 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.5.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.5.33 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index e95089c2dd..d9bcde8a2d 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.5.33", + "version": "0.5.34-next.0", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 5db50bd642..9632fb3e14 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.5.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + ## 0.5.25 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index f8f203849e..5978536c13 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.5.25", + "version": "0.5.26-next.0", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index 00b73a76d3..57a217ef1a 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.22.0-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/config@1.2.0 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + ## 0.1.35 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 595c9d355a..0cb5409a7b 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.1.35", + "version": "0.1.36-next.0", "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 2396f4b45b..5f120d1615 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.1.23 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 09996cd995..79decb21cf 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.1.23", + "version": "0.1.24-next.0", "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 fa0cede13b..c53f721504 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + ## 0.1.15 ### 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 994ee2dad9..1436ac1b96 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.1.15", + "version": "0.1.16-next.0", "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 4e4caa0559..f9f764f608 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-catalog-unprocessed-entities-common@0.0.1 + - @backstage/plugin-permission-common@0.7.13 + ## 0.4.4 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index b1236bf37e..6988f76d51 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.4.4", + "version": "0.4.5-next.0", "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 50920f8db6..62e517919f 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-catalog-backend +## 1.22.0-next.0 + +### Minor Changes + +- 8d14475: Emit well known relationships for the Domain entity kind. + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the catalog processing +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-search-backend-module-catalog@0.1.23-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-openapi-utils@0.1.11-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + ## 1.21.1 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 928881f5b4..1f09408802 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "1.21.1", + "version": "1.22.0-next.0", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin" diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index 41213ed60c..f070ca11e2 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-common +## 1.0.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + ## 1.0.22 ### Patch Changes diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 64544ade1c..59d0019512 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-common", - "version": "1.0.22", + "version": "1.0.23-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 cabd0b89fd..656af9172d 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-graph +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/types@1.1.1 + ## 0.4.4 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 45c519eff4..3872bccc6b 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.4", + "version": "0.4.5-next.0", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 70e199ff34..c5873263f3 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-import +## 0.10.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration@1.10.0 + - @backstage/integration-react@1.1.26 + ## 0.10.10 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 67215f6a74..3fe969ac7b 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.10.10", + "version": "0.10.11-next.0", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index 9f1f9a525a..563db2bf53 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-node +## 1.11.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + ## 1.11.1 ### Patch Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 37cc1accfb..6787e28d9d 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.11.1", + "version": "1.11.2-next.0", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library" diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 52892384ca..84236540e9 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog-react +## 1.11.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration-react@1.1.26 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-react@0.4.22 + ## 1.11.3 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index fc7654e3f2..43128252a6 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "1.11.3", + "version": "1.11.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index b6e807222f..67b2fc3d83 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + ## 0.2.3 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index af4eb53a43..713844426c 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.3", + "version": "0.2.4-next.0", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 0512679fa1..40f2c250f9 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-catalog +## 1.19.1-next.0 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration-react@1.1.26 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-react@0.4.22 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + ## 1.19.0 ### Minor Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 8ddfda02d5..346676a17f 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.19.0", + "version": "1.19.1-next.0", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 878d451197..614000fb5b 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-config-schema +## 0.1.55-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.1.54 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index f207c465f0..8ff10e1197 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.54", + "version": "0.1.55-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 8401cb0555..99532e3d19 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-devtools-backend +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/cli-common@0.1.13 + - @backstage/config@1.2.0 + - @backstage/config-loader@1.8.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-devtools-common@0.1.9 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index f1d48759ef..3b7dbe23f8 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.3.3", + "version": "0.3.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index 9ae62bd3bd..8d504c3004 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-devtools +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/plugin-devtools-common@0.1.9 + - @backstage/plugin-permission-react@0.4.22 + ## 0.1.13 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 306a5f4180..147991ce09 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.13", + "version": "0.1.14-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 6897609c22..3807e3771d 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index e5bcf184b1..d5655cead1 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.3.3", + "version": "0.3.4-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index feb1ea429f..f0c82485a4 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index ed45ca3923..613620d36c 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.3", + "version": "0.2.4-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index 820e31aad5..b77cd31c94 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 0085f2de2a..317fbaecbd 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.3", + "version": "0.2.4-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index 20ed29a1a3..d676451dca 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index 421cdbf5e6..bd7de4a33a 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.3", + "version": "0.2.4-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index 886e6e7103..111ac1ba80 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend-module-github +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 4cc5d0c456..440ca61b7d 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.2.3", + "version": "0.2.4-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index 0f7d9d1b0a..9a9f0a0956 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index ebc9d5f423..583f929fdd 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.2.3", + "version": "0.2.4-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index 2af39cc211..76e0e9b90a 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.1.27 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index 9108c4eda2..8c39378840 100644 --- a/plugins/events-backend-test-utils/package.json +++ b/plugins/events-backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-test-utils", - "version": "0.1.27", + "version": "0.1.28-next.0", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", "backstage": { "role": "node-library" diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index ec49ebfd9c..d839799775 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-events-backend +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.3.4 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index c618b164b0..ce694628d0 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.3.4", + "version": "0.3.5-next.0", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index 40b8350304..c21ffbcb1f 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-events-node +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index 77ab5e015d..deadde1f7f 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-node", - "version": "0.3.3", + "version": "0.3.4-next.0", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library" diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index c71f8a24b4..db22f83859 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @internal/plugin-todo-list-backend +## 1.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/errors@1.2.4 + ## 1.0.26 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 0c2c12bf04..ff4b20bee5 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.26", + "version": "1.0.27-next.0", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 092e5857e4..10f1a6ddcb 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/plugin-todo-list +## 1.0.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + ## 1.0.26 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index ff192a13b5..377277bfbe 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.26", + "version": "1.0.27-next.0", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index fee07a2458..60d7e72fda 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-home-react +## 0.1.13-next.0 + +### Patch Changes + +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + ## 0.1.12 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index a3e9d0f267..8e81326138 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-react", - "version": "0.1.12", + "version": "0.1.13-next.0", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library" diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index d7a72678c4..bfc7d8cce2 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-home +## 0.7.4-next.0 + +### Patch Changes + +- 2196d3e: Use relative time when displaying visits from the same day +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-home-react@0.1.13-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/config@1.2.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + ## 0.7.3 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index 8c5db6de8e..82a5401fe3 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.7.3", + "version": "0.7.4-next.0", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 4583cf68e2..ade81960a4 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-kubernetes-backend +## 0.17.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-kubernetes-node@0.1.12-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration-aws-node@0.1.12 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + ## 0.17.0 ### Minor Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 3b2560106a..c1240f6367 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.17.0", + "version": "0.17.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index 7bcffe9b9a..46dd8aa084 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/plugin-kubernetes-react@0.3.5-next.0 + - @backstage/core-plugin-api@1.9.2 + ## 0.0.10 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 1239156ffd..5381729509 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.10", + "version": "0.0.11-next.0", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index bb1b8664ee..55f3485ff4 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-kubernetes-common +## 0.7.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + ## 0.7.5 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index 2dad38c002..77201e4fbf 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-common", - "version": "0.7.5", + "version": "0.7.6-next.0", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", "backstage": { "role": "common-library" diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index d58ea12ce9..66fc5910aa 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kubernetes-node +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/types@1.1.1 + ## 0.1.11 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index 4d0e2d9d75..a2a1ce7a8a 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.1.11", + "version": "0.1.12-next.0", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library" diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index 8243bab8ae..d7159fab75 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-react +## 0.3.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.3.4 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index bc72ac27e9..d939dd4eac 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-react", "description": "Web library for the kubernetes-react plugin", - "version": "0.3.4", + "version": "0.3.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index a01fc187db..8426ffb55d 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes +## 0.11.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-kubernetes-common@0.7.6-next.0 + - @backstage/plugin-kubernetes-react@0.3.5-next.0 + - @backstage/core-plugin-api@1.9.2 + ## 0.11.9 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 94d21ff1d1..418f9957aa 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.11.9", + "version": "0.11.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index c6f9485286..b3fbe6ae69 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-notifications-backend +## 0.2.1-next.0 + +### Patch Changes + +- ee09dfc: Updated documentation for sending messages by external services. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-notifications-node@0.1.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-notifications-common@0.0.3 + - @backstage/plugin-signals-node@0.1.4-next.0 + ## 0.2.0 ### Minor Changes diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 73e359bbd4..1a16996e90 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.2.0", + "version": "0.2.1-next.0", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index 03eecc47bb..867ea0a79f 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-notifications-node +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-notifications-common@0.0.3 + - @backstage/plugin-signals-node@0.1.4-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 62b48dd58b..9d72b695a3 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.1.3", + "version": "0.1.4-next.0", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library" diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index bc2fdf4360..eb0e44064e 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-notifications +## 0.2.1-next.0 + +### Patch Changes + +- e6bf85f: Allow overriding `NotificationsPage` page properties +- a42a19b: Empty descriptions are not rendered to improve the look&feel. +- 1bc3b86: Fix to show web notifications even when browser is on foreground. Fix duplicate notifications with multiple tabs. +- f793112: Allow defining `className` and additional properties for `NotificationsSideBarItem` +- e1c7d6e: Fix infinite loop in the notification title counter +- fcda449: The rendered size of a notification is limited for very long descriptions. +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-notifications-common@0.0.3 + - @backstage/plugin-signals-react@0.0.3 + ## 0.2.0 ### Minor Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 8c3a90c092..7c98ecc31f 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.2.0", + "version": "0.2.1-next.0", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 8e386db176..1d25ba359c 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-org-react +## 0.1.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-plugin-api@1.9.2 + ## 0.1.23 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index b56d916e89..985e05b3c5 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.23", + "version": "0.1.24-next.0", "backstage": { "role": "web-library" }, diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 72402286ba..17cb3d14a9 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-org +## 0.6.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + ## 0.6.24 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 173495d846..c07ef33428 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.24", + "version": "0.6.25-next.0", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin" diff --git a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md index 31d745e2f2..7c402fa207 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.1.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + ## 0.1.14 ### Patch Changes diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index 6652418c00..1a8d44f652 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", - "version": "0.1.14", + "version": "0.1.15-next.0", "description": "Allow all policy backend module for the permission plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 95934c32be..d4b3072fe9 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-permission-backend +## 0.5.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + ## 0.5.41 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 320c52288a..6c7c00eb79 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.41", + "version": "0.5.42-next.0", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 2e83533229..9d93fbe268 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-permission-node +## 0.7.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + ## 0.7.28 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index edef0fd002..e367d0abd3 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.7.28", + "version": "0.7.29-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index c42522de43..0e3d8475b0 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-proxy-backend +## 0.4.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + ## 0.4.15 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index ad9dc04681..d6f113eaf1 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.4.15", + "version": "0.4.16-next.0", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index f0df53a3ce..a205da59c8 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + ## 0.1.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index 4689ad1130..46cb67fe74 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.1.9", + "version": "0.1.10-next.0", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index 4f4afff38b..67cbfa1af5 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index 03200fb368..9d92a7d4e6 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.1.7", + "version": "0.1.8-next.0", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index 9024c6c826..29b4d61ddc 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.1.8-next.0 + +### Patch Changes + +- 24dd655: Add examples for `publish:bitbucketServer:pull-request` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 1bd3a5a1e1..5213d00079 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.1.7", + "version": "0.1.8-next.0", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index be4e753d60..0203b51ac6 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.0 + ## 0.2.7 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index 4a1cfb0ee8..cd6d38643c 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.2.7", + "version": "0.2.8-next.0", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index cdf261d8d8..b001283394 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.2.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + ## 0.2.18 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 556fb2eb22..175f12a605 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.2.18", + "version": "0.2.19-next.0", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index dbda392208..825a90eae1 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + ## 0.2.41 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index d1b867cae7..0b46079953 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.2.41", + "version": "0.2.42-next.0", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index 2b176d24d7..9a2d2fd32a 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + ## 0.1.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 96a0ff2c47..78ebe2a2ed 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.1.9", + "version": "0.1.10-next.0", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index f3ae9326d6..09f5faafcd 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.1.8-next.0 + +### Patch Changes + +- 554af73: Allow defining `repoVisibility` field for the action `publish:gitea` +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 1751a1a6ce..6b8740f791 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.1.7", + "version": "0.1.8-next.0", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index 637cd5cb75..8fa18de2bb 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.2.8-next.0 + +### Patch Changes + +- 5d99272: Update local development dependencies. +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + ## 0.2.7 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index d1bec25994..925efa8c37 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.2.7", + "version": "0.2.8-next.0", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index 133703847a..f155b66762 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.3.4-next.0 + +### Patch Changes + +- ffc73ec: Add examples for `gitlab:repo:push` scaffolder action & improve related tests +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 32a2c64860..95059f2513 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.3.3", + "version": "0.3.4-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 607d3d11f7..541e175c11 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + ## 0.4.34 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 9d54373111..26fd91b1b6 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.4.34", + "version": "0.4.35-next.0", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index 54c6bb06f2..8119d5a446 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + ## 0.1.25 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index f57c38a003..9321e7ef1e 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.1.25", + "version": "0.1.26-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index f0f737aa82..4772114265 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.4-next.0 + ## 0.3.0 ### Minor Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index f75fba4195..c091dfbab9 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.3.0", + "version": "0.3.1-next.0", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 12ca699696..e9bb1c2b05 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-scaffolder-backend +## 1.22.5-next.0 + +### Patch Changes + +- 025641b: Fix issue with the log format not being respected when logging from actions +- 025641b: Redact `meta` fields too with the logger +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.0 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.8-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.0 + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.10-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8-next.0 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.0 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10-next.0 + ## 1.22.4 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index e092470bab..d107d578cb 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.22.4", + "version": "1.22.5-next.0", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index ed58b28338..4b6da80009 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-common +## 1.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + ## 1.5.1 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 9f99434371..d11e576d52 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-common", - "version": "1.5.1", + "version": "1.5.2-next.0", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", "backstage": { "role": "common-library" diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index 8033908b54..3fe4c45621 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-test-utils@0.3.8-next.0 + - @backstage/plugin-scaffolder-node@0.4.4-next.0 + - @backstage/types@1.1.1 + ## 0.1.3 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 7b8379642d..427bb5664a 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.1.3", + "version": "0.1.4-next.0", "backstage": { "role": "node-library" }, diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index ef7ff6f7f4..84cb98bf19 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-node +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/types@1.1.1 + ## 0.4.3 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 0c063cb0df..214fd07dd1 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.4.3", + "version": "0.4.4-next.0", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library" diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index 569b1215bd..2236048243 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-scaffolder-react +## 1.8.5-next.0 + +### Patch Changes + +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + ## 1.8.4 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 06fb9fe5a2..352478d99d 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.8.4", + "version": "1.8.5-next.0", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library" diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index a7be4be176..69b570e947 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-scaffolder +## 1.19.4-next.0 + +### Patch Changes + +- 0040ec2: Updated dependency `@rjsf/utils` to `5.18.2`. + Updated dependency `@rjsf/core` to `5.18.2`. + Updated dependency `@rjsf/material-ui` to `5.18.2`. + Updated dependency `@rjsf/validator-ajv8` to `5.18.2`. +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-scaffolder-react@1.8.5-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.2-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration@1.10.0 + - @backstage/integration-react@1.1.26 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-react@0.4.22 + ## 1.19.3 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index b8d0f21805..96227ed494 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.19.3", + "version": "1.19.4-next.0", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin" diff --git a/plugins/search-backend-module-catalog/CHANGELOG.md b/plugins/search-backend-module-catalog/CHANGELOG.md index 1d985231b2..51b9f5674a 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-search-backend-module-catalog +## 0.1.23-next.0 + +### Patch Changes + +- d5fff66: Fix wiring of the module exported at the `/alpha` path, which was causing authentication failures. +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + ## 0.1.22 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index 6aebb192d9..1d9a4a37c7 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-catalog", - "version": "0.1.22", + "version": "0.1.23-next.0", "description": "A module for the search backend that exports catalog modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 7ca82061d1..ca4d6ad599 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.11 + ## 1.4.0 ### Minor Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 1d34b60f59..f13616d60b 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "1.4.0", + "version": "1.4.1-next.0", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-explore/CHANGELOG.md b/plugins/search-backend-module-explore/CHANGELOG.md index 5a3adbbbf7..94519f4751 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-search-backend-module-explore +## 0.1.23-next.0 + +### Patch Changes + +- ca6e2e0: Migrate search collator to use the new auth services. +- 5d99272: Update README.md to point to explore plugin in community-plugins repository. +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + ## 0.1.21 ### Patch Changes diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index 4741e2fde2..612e42df10 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-explore", - "version": "0.1.21", + "version": "0.1.23-next.0", "description": "A module for the search backend that exports explore modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index c085ee456d..74e9bc9a1a 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-search-backend-module-pg +## 0.5.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + ## 0.5.26 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 505d2408f6..3666e85273 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-pg", - "version": "0.5.26", + "version": "0.5.27-next.0", "description": "A module for the search backend that implements search using PostgreSQL", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md index 604d458bc2..80447b1b78 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-module-stack-overflow-collator +## 0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-search-common@1.2.11 + ## 0.1.10 ### Patch Changes diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index f1c10b414f..ed9ff057aa 100644 --- a/plugins/search-backend-module-stack-overflow-collator/package.json +++ b/plugins/search-backend-module-stack-overflow-collator/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-stack-overflow-collator", - "version": "0.1.10", + "version": "0.1.11-next.0", "description": "A module for the search backend that exports stack overflow modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index 9b909f66f9..aeb6999a3c 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-catalog-node@1.11.2-next.0 + - @backstage/plugin-techdocs-node@1.12.4-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + ## 0.1.22 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 57a880b052..1dea62aa74 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.1.22", + "version": "0.1.23-next.0", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 568c04889a..9e79ded222 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-search-backend-node +## 1.2.22-next.0 + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the search index registry +- Updated dependencies + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/backend-tasks@0.5.23-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-search-common@1.2.11 + ## 1.2.21 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index c6444d0fef..ac59d7eab2 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-node", - "version": "1.2.21", + "version": "1.2.22-next.0", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", "backstage": { "role": "node-library" diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index c611e41c7a..f064499e07 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-search-backend +## 1.5.8-next.0 + +### Patch Changes + +- c6cb568: Add lifecycle monitoring for the search index registry +- Updated dependencies + - @backstage/plugin-search-backend-node@1.2.22-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/repo-tools@0.8.1-next.0 + - @backstage/backend-openapi-utils@0.1.11-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + - @backstage/plugin-permission-common@0.7.13 + - @backstage/plugin-permission-node@0.7.29-next.0 + - @backstage/plugin-search-common@1.2.11 + ## 1.5.7 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index d6f554e77e..34ea122db9 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "1.5.7", + "version": "1.5.8-next.0", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin" diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index f9f8c0bc30..b7ef7b85fc 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-search-react +## 1.7.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.11 + ## 1.7.10 ### Patch Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 7fcb34801c..00cc8032f2 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.7.10", + "version": "1.7.11-next.0", "backstage": { "role": "web-library" }, diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 6846a947c4..bd07286578 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search +## 1.4.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/types@1.1.1 + - @backstage/version-bridge@1.0.8 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + ## 1.4.10 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 65539ce949..5aeeecbfd3 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.10", + "version": "1.4.11-next.0", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin" diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index 4fd519b225..a787465282 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-signals-backend +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.4-next.0 + - @backstage/plugin-signals-node@0.1.4-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index 3995adb0dc..ac1e74332b 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.1.3", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index 3eddc74c02..3599e86aaa 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-signals-node +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/types@1.1.1 + - @backstage/plugin-events-node@0.3.4-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index 3101f0baa4..ba454635ac 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-signals-node", "description": "Node.js library for the signals plugin", - "version": "0.1.3", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/signals/CHANGELOG.md b/plugins/signals/CHANGELOG.md index 3e3623db6e..3f0e646944 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-signals +## 0.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/types@1.1.1 + - @backstage/plugin-signals-react@0.0.3 + ## 0.0.5 ### Patch Changes diff --git a/plugins/signals/package.json b/plugins/signals/package.json index ec529e164f..9f248def27 100644 --- a/plugins/signals/package.json +++ b/plugins/signals/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals", - "version": "0.0.5", + "version": "0.0.6-next.0", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 2b9872d5da..4ce9d18415 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.32-next.0 + +### Patch Changes + +- 2f13862: Fix bug in TechDocsAddonTester when jest.resetAllMocks is called between tests +- Updated dependencies + - @backstage/plugin-catalog@1.19.1-next.0 + - @backstage/plugin-techdocs@1.10.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration-react@1.1.26 + - @backstage/test-utils@1.5.5-next.0 + - @backstage/plugin-search-react@1.7.11-next.0 + ## 1.0.31 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 869ab58154..ac7d3cb752 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.31", + "version": "1.0.32-next.0", "backstage": { "role": "web-library" }, diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index a3aefe34a1..86056032a6 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-techdocs-backend +## 1.10.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/catalog-client@1.6.5-next.0 + - @backstage/plugin-catalog-common@1.0.23-next.0 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.0 + - @backstage/plugin-techdocs-node@1.12.4-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/plugin-permission-common@0.7.13 + ## 1.10.4 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index a620169515..bd3cee3ad6 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "1.10.4", + "version": "1.10.5-next.0", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin" diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index 140ad1caa1..d577b465b5 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/integration@1.10.0 + - @backstage/integration-react@1.1.26 + ## 1.1.9 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 385a9e0caf..793018b1e1 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", - "version": "1.1.9", + "version": "1.1.10-next.0", "description": "Plugin module for contributed TechDocs Addons", "backstage": { "role": "frontend-plugin-module" diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index 7ecdec30bc..73fae3afe0 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-techdocs-node +## 1.12.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/integration@1.10.0 + - @backstage/integration-aws-node@0.1.12 + - @backstage/plugin-search-common@1.2.11 + ## 1.12.3 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index d8b91c5a98..0c590816e7 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.12.3", + "version": "1.12.4-next.0", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library" diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index afae8eefb1..95f7bd8eaa 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-react +## 1.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/version-bridge@1.0.8 + ## 1.2.3 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index bf2dd4de0a..40a3731a75 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-react", - "version": "1.2.3", + "version": "1.2.4-next.0", "description": "Shared frontend utilities for TechDocs and Addons", "backstage": { "role": "web-library" diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 5151259629..a1d4d9c7ee 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-techdocs +## 1.10.5-next.0 + +### Patch Changes + +- d2cc139: Update path in Readme for Plugin Techdocs to show the correct setup information. +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/catalog-model@1.5.0-next.0 + - @backstage/plugin-auth-react@0.1.1-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/plugin-techdocs-react@1.2.4-next.0 + - @backstage/config@1.2.0 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/integration@1.10.0 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-search-common@1.2.11 + - @backstage/plugin-search-react@1.7.11-next.0 + ## 1.10.4 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index e303bd9459..46e24c8a44 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.10.4", + "version": "1.10.5-next.0", "description": "The Backstage plugin that renders technical documentation for your components", "backstage": { "role": "frontend-plugin" diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 9cc7292bd2..7aa3b634d1 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-user-settings-backend +## 0.2.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.0 + - @backstage/backend-common@0.21.8-next.0 + - @backstage/backend-plugin-api@0.6.18-next.0 + - @backstage/config@1.2.0 + - @backstage/errors@1.2.4 + - @backstage/types@1.1.1 + ## 0.2.16 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 35eded315f..54bb8a5e5d 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-backend", - "version": "0.2.16", + "version": "0.2.17-next.0", "description": "The Backstage backend plugin to manage user settings", "backstage": { "role": "backend-plugin" diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 9e2b639714..95fbfa85eb 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-user-settings +## 0.8.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-compat-api@0.2.5-next.0 + - @backstage/theme@0.5.4-next.0 + - @backstage/core-components@0.14.5-next.0 + - @backstage/plugin-catalog-react@1.11.4-next.0 + - @backstage/core-app-api@1.12.4 + - @backstage/core-plugin-api@1.9.2 + - @backstage/errors@1.2.4 + - @backstage/frontend-plugin-api@0.6.5-next.0 + - @backstage/types@1.1.1 + ## 0.8.5 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index fce7f86612..40c1b0772f 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.8.5", + "version": "0.8.6-next.0", "description": "A Backstage plugin that provides a settings page", "backstage": { "role": "frontend-plugin" diff --git a/yarn.lock b/yarn.lock index e600a7477d..f7c8413328 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3520,6 +3520,18 @@ __metadata: languageName: unknown linkType: soft +"@backstage/catalog-client@npm:^1.6.4": + version: 1.6.4 + resolution: "@backstage/catalog-client@npm:1.6.4" + dependencies: + "@backstage/catalog-model": ^1.4.5 + "@backstage/errors": ^1.2.4 + cross-fetch: ^4.0.0 + uri-template: ^2.0.0 + checksum: af3537d04f0abd6e6f3e49c7623994cc83db6efb2776fff5d59faee26c598840486e42a99c2bbb4a1b6ff97ad97ac857e913830cad62cda7cd71eef74cf2e179 + languageName: node + linkType: hard + "@backstage/catalog-client@workspace:^, @backstage/catalog-client@workspace:packages/catalog-client": version: 0.0.0-use.local resolution: "@backstage/catalog-client@workspace:packages/catalog-client" @@ -3533,7 +3545,19 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@^1.4.3, @backstage/catalog-model@^1.4.5, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": +"@backstage/catalog-model@npm:^1.4.3, @backstage/catalog-model@npm:^1.4.5": + version: 1.4.5 + resolution: "@backstage/catalog-model@npm:1.4.5" + dependencies: + "@backstage/errors": ^1.2.4 + "@backstage/types": ^1.1.1 + ajv: ^8.10.0 + lodash: ^4.17.21 + checksum: 34aaa4b82d29bf3b0a4b52552f8eb8189041df826f87a7bbdef5107a1efb8ce1f7eb1c1a343868718ca2af9be76d9f5184f6a92076d893d3a3951d16881647b7 + languageName: node + linkType: hard + +"@backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": version: 0.0.0-use.local resolution: "@backstage/catalog-model@workspace:packages/catalog-model" dependencies: @@ -3785,7 +3809,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/config@^1.1.1, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": +"@backstage/config@^1.1.1, @backstage/config@^1.2.0, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": version: 0.0.0-use.local resolution: "@backstage/config@workspace:packages/config" dependencies: @@ -3857,7 +3881,109 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@^0.14.4, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": +"@backstage/core-components@npm:^0.13.10": + version: 0.13.10 + resolution: "@backstage/core-components@npm:0.13.10" + dependencies: + "@backstage/config": ^1.1.1 + "@backstage/core-plugin-api": ^1.8.2 + "@backstage/errors": ^1.2.3 + "@backstage/theme": ^0.5.0 + "@backstage/version-bridge": ^1.0.7 + "@date-io/core": ^1.3.13 + "@material-table/core": ^3.1.0 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.61 + "@react-hookz/web": ^23.0.0 + "@types/react": ^16.13.1 || ^17.0.0 + "@types/react-sparklines": ^1.7.0 + "@types/react-text-truncate": ^0.14.0 + ansi-regex: ^6.0.1 + classnames: ^2.2.6 + d3-selection: ^3.0.0 + d3-shape: ^3.0.0 + d3-zoom: ^3.0.0 + dagre: ^0.8.5 + linkify-react: 4.1.3 + linkifyjs: 4.1.3 + lodash: ^4.17.21 + pluralize: ^8.0.0 + qs: ^6.9.4 + rc-progress: 3.5.1 + react-helmet: 6.1.0 + react-hook-form: ^7.12.2 + react-idle-timer: 5.6.2 + react-markdown: ^8.0.0 + react-sparklines: ^1.7.0 + react-syntax-highlighter: ^15.4.5 + react-text-truncate: ^0.19.0 + react-use: ^17.3.2 + react-virtualized-auto-sizer: ^1.0.11 + react-window: ^1.8.6 + remark-gfm: ^3.0.1 + zen-observable: ^0.10.0 + zod: ^3.22.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: ec2a0d0a27bc4d6b9d4da97f0b4df600148529ee51bf2c8e7d3adc45c3950adac662535d3beabc451db346f2c7e3c412ceaa756fc774012b43dff5e2695f2271 + languageName: node + linkType: hard + +"@backstage/core-components@npm:^0.14.4": + version: 0.14.4 + resolution: "@backstage/core-components@npm:0.14.4" + dependencies: + "@backstage/config": ^1.2.0 + "@backstage/core-plugin-api": ^1.9.2 + "@backstage/errors": ^1.2.4 + "@backstage/theme": ^0.5.3 + "@backstage/version-bridge": ^1.0.8 + "@date-io/core": ^1.3.13 + "@material-table/core": ^3.1.0 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.61 + "@react-hookz/web": ^24.0.0 + "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + "@types/react-sparklines": ^1.7.0 + "@types/react-text-truncate": ^0.14.0 + ansi-regex: ^6.0.1 + classnames: ^2.2.6 + d3-selection: ^3.0.0 + d3-shape: ^3.0.0 + d3-zoom: ^3.0.0 + dagre: ^0.8.5 + linkify-react: 4.1.3 + linkifyjs: 4.1.3 + lodash: ^4.17.21 + pluralize: ^8.0.0 + qs: ^6.9.4 + rc-progress: 3.5.1 + react-helmet: 6.1.0 + react-hook-form: ^7.12.2 + react-idle-timer: 5.7.2 + react-markdown: ^8.0.0 + react-sparklines: ^1.7.0 + react-syntax-highlighter: ^15.4.5 + react-text-truncate: ^0.19.0 + react-use: ^17.3.2 + react-virtualized-auto-sizer: ^1.0.11 + react-window: ^1.8.6 + remark-gfm: ^3.0.1 + zen-observable: ^0.10.0 + zod: ^3.22.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: b96721b267daeec5a73a8b487f96ab5b5b3a85b8b113b2e45c5b733f3201af0d5cda3d7a325f5f68a4057ccbe78beac9f239b51ceb3099d3b0001709ec16928b + languageName: node + linkType: hard + +"@backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": version: 0.0.0-use.local resolution: "@backstage/core-components@workspace:packages/core-components" dependencies: @@ -3930,57 +4056,6 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@npm:^0.13.10": - version: 0.13.10 - resolution: "@backstage/core-components@npm:0.13.10" - dependencies: - "@backstage/config": ^1.1.1 - "@backstage/core-plugin-api": ^1.8.2 - "@backstage/errors": ^1.2.3 - "@backstage/theme": ^0.5.0 - "@backstage/version-bridge": ^1.0.7 - "@date-io/core": ^1.3.13 - "@material-table/core": ^3.1.0 - "@material-ui/core": ^4.12.2 - "@material-ui/icons": ^4.9.1 - "@material-ui/lab": 4.0.0-alpha.61 - "@react-hookz/web": ^23.0.0 - "@types/react": ^16.13.1 || ^17.0.0 - "@types/react-sparklines": ^1.7.0 - "@types/react-text-truncate": ^0.14.0 - ansi-regex: ^6.0.1 - classnames: ^2.2.6 - d3-selection: ^3.0.0 - d3-shape: ^3.0.0 - d3-zoom: ^3.0.0 - dagre: ^0.8.5 - linkify-react: 4.1.3 - linkifyjs: 4.1.3 - lodash: ^4.17.21 - pluralize: ^8.0.0 - qs: ^6.9.4 - rc-progress: 3.5.1 - react-helmet: 6.1.0 - react-hook-form: ^7.12.2 - react-idle-timer: 5.6.2 - react-markdown: ^8.0.0 - react-sparklines: ^1.7.0 - react-syntax-highlighter: ^15.4.5 - react-text-truncate: ^0.19.0 - react-use: ^17.3.2 - react-virtualized-auto-sizer: ^1.0.11 - react-window: ^1.8.6 - remark-gfm: ^3.0.1 - zen-observable: ^0.10.0 - zod: ^3.22.4 - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 - react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: ec2a0d0a27bc4d6b9d4da97f0b4df600148529ee51bf2c8e7d3adc45c3950adac662535d3beabc451db346f2c7e3c412ceaa756fc774012b43dff5e2695f2271 - languageName: node - linkType: hard - "@backstage/core-plugin-api@^1.8.2, @backstage/core-plugin-api@^1.9.2, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": version: 0.0.0-use.local resolution: "@backstage/core-plugin-api@workspace:packages/core-plugin-api" @@ -4128,6 +4203,26 @@ __metadata: languageName: unknown linkType: soft +"@backstage/frontend-plugin-api@npm:^0.6.4": + version: 0.6.4 + resolution: "@backstage/frontend-plugin-api@npm:0.6.4" + dependencies: + "@backstage/core-components": ^0.14.4 + "@backstage/core-plugin-api": ^1.9.2 + "@backstage/types": ^1.1.1 + "@backstage/version-bridge": ^1.0.8 + "@material-ui/core": ^4.12.4 + "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + lodash: ^4.17.21 + zod: ^3.22.4 + zod-to-json-schema: ^3.21.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: 2ae2919147dcfd8a5b4379059ecb76461a27f38159c398915384db7a7396bc65451cf696d955e4ba23eb6dfb549395624d09412d0c9c776072d3f003e5f1cba8 + languageName: node + linkType: hard + "@backstage/frontend-plugin-api@workspace:^, @backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api": version: 0.0.0-use.local resolution: "@backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api" @@ -4191,7 +4286,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": +"@backstage/integration-react@^1.1.26, @backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": version: 0.0.0-use.local resolution: "@backstage/integration-react@workspace:packages/integration-react" dependencies: @@ -5267,7 +5362,18 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-common@^1.0.20, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": +"@backstage/plugin-catalog-common@npm:^1.0.20, @backstage/plugin-catalog-common@npm:^1.0.22": + version: 1.0.22 + resolution: "@backstage/plugin-catalog-common@npm:1.0.22" + dependencies: + "@backstage/catalog-model": ^1.4.5 + "@backstage/plugin-permission-common": ^0.7.13 + "@backstage/plugin-search-common": ^1.2.11 + checksum: f468ade184d5e535cc27cbb27a9dbd6cd21c1601b5a84167d2ea1004f471180ef8bf148df5561b5557c332bdc01480d020e93f62915f029cc728802cebf8e255 + languageName: node + linkType: hard + +"@backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: @@ -5375,7 +5481,43 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@^1.11.3, @backstage/plugin-catalog-react@^1.9.3, @backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": +"@backstage/plugin-catalog-react@npm:^1.11.3, @backstage/plugin-catalog-react@npm:^1.9.3": + version: 1.11.3 + resolution: "@backstage/plugin-catalog-react@npm:1.11.3" + dependencies: + "@backstage/catalog-client": ^1.6.4 + "@backstage/catalog-model": ^1.4.5 + "@backstage/core-components": ^0.14.4 + "@backstage/core-plugin-api": ^1.9.2 + "@backstage/errors": ^1.2.4 + "@backstage/frontend-plugin-api": ^0.6.4 + "@backstage/integration-react": ^1.1.26 + "@backstage/plugin-catalog-common": ^1.0.22 + "@backstage/plugin-permission-common": ^0.7.13 + "@backstage/plugin-permission-react": ^0.4.22 + "@backstage/types": ^1.1.1 + "@backstage/version-bridge": ^1.0.8 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.61 + "@react-hookz/web": ^24.0.0 + "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + classnames: ^2.2.6 + lodash: ^4.17.21 + material-ui-popup-state: ^1.9.3 + qs: ^6.9.4 + react-use: ^17.2.4 + yaml: ^2.0.0 + zen-observable: ^0.10.0 + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: d04919bff692094bb145d8479aae47368911f3adc92644b4742e5db1296af9a2673cf4341995b9c65e660cd608d3b59a136b9c164ab2df51bdc3ccbaf5af71fd + languageName: node + linkType: hard + +"@backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-react@workspace:plugins/catalog-react" dependencies: @@ -6188,7 +6330,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": +"@backstage/plugin-permission-common@^0.7.13, @backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-common@workspace:plugins/permission-common" dependencies: @@ -6226,7 +6368,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": +"@backstage/plugin-permission-react@^0.4.22, @backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-react@workspace:plugins/permission-react" dependencies: @@ -6942,7 +7084,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-search-common@workspace:^, @backstage/plugin-search-common@workspace:plugins/search-common": +"@backstage/plugin-search-common@^1.2.11, @backstage/plugin-search-common@workspace:^, @backstage/plugin-search-common@workspace:plugins/search-common": version: 0.0.0-use.local resolution: "@backstage/plugin-search-common@workspace:plugins/search-common" dependencies: @@ -7477,7 +7619,23 @@ __metadata: languageName: unknown linkType: soft -"@backstage/theme@^0.5.0, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": +"@backstage/theme@npm:^0.5.0, @backstage/theme@npm:^0.5.3": + version: 0.5.3 + resolution: "@backstage/theme@npm:0.5.3" + dependencies: + "@emotion/react": ^11.10.5 + "@emotion/styled": ^11.10.5 + "@mui/material": ^5.12.2 + peerDependencies: + "@material-ui/core": ^4.12.2 + "@types/react": ^16.13.1 || ^17.0.0 || ^18.0.0 + react: ^16.13.1 || ^17.0.0 || ^18.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 + checksum: ac6c3bbd73294385f73aa91e04f8bf3a1bb78cadc0e43034760ebf19e86814ed2d679f2641bb086aa9e305a24d923d8c3480b2de3bff55189c19bc5dfdce1814 + languageName: node + linkType: hard + +"@backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": version: 0.0.0-use.local resolution: "@backstage/theme@workspace:packages/theme" dependencies: @@ -7508,7 +7666,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/version-bridge@^1.0.7, @backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": +"@backstage/version-bridge@^1.0.7, @backstage/version-bridge@^1.0.8, @backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": version: 0.0.0-use.local resolution: "@backstage/version-bridge@workspace:packages/version-bridge" dependencies: From 7aae28fde885173cf5c7f41e264ea2dca5316cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 23 Apr 2024 17:28:14 +0200 Subject: [PATCH 107/234] remove plugin suggestion template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .github/ISSUE_TEMPLATE/plugin.yaml | 52 ------------------------------ 1 file changed, 52 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/plugin.yaml diff --git a/.github/ISSUE_TEMPLATE/plugin.yaml b/.github/ISSUE_TEMPLATE/plugin.yaml deleted file mode 100644 index a7f5216631..0000000000 --- a/.github/ISSUE_TEMPLATE/plugin.yaml +++ /dev/null @@ -1,52 +0,0 @@ -name: 🔌 Plugin -description: 'Submit a proposal for a new Plugin' -title: '🔌 Plugin: ' -labels: [plugin] -body: - - type: markdown - attributes: - value: | - We value your time and efforts to submit this Plugin request form. 🙏 - - type: textarea - id: plugin-summary - validations: - required: true - attributes: - label: '🔖 Summary' - description: 'Provide a general summary of the plugin and how it should work' - placeholder: 'You should add ...' - - type: textarea - id: website - attributes: - label: '🌐 Project website (if applicable)' - description: 'Add a link to the open source project or product this plugin will integrate with, if existing' - placeholder: 'Website Link is ...' - - type: textarea - id: context - attributes: - label: '✌️ Context' - description: 'A clear and concise description about the Plugin.' - placeholder: 'Providing additional context' - - type: checkboxes - id: no-duplicate-issues - attributes: - label: '👀 Have you spent some time to check if this plugin 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 From 4a6741b1ba6ecb2b894b0c95c95928b5466475b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Tue, 23 Apr 2024 17:42:52 +0200 Subject: [PATCH 108/234] docs too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- docs/contribute/getting-involved.md | 6 +++--- docs/faq/technical.md | 4 ++-- docs/getting-started/index.md | 2 +- docs/plugins/index.md | 2 +- microsite/blog/2020-09-30-plugin-marketplace.mdx | 2 +- plugins/README.md | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/contribute/getting-involved.md b/docs/contribute/getting-involved.md index 7c0be46273..c11e870752 100644 --- a/docs/contribute/getting-involved.md +++ b/docs/contribute/getting-involved.md @@ -17,15 +17,15 @@ Look through the GitHub issues for [bugs](https://github.com/backstage/backstage ### Build a plugin -The value of Backstage grows with every new plugin that gets added. Wouldn't it be fantastic if there was a plugin for every infrastructure project out there? We think so. And we would love your help. +The value of Backstage grows with every new plugin that gets added. Wouldn't it be fantastic if there was a plugin for every infrastructure project out there? We think so. And we would love your help. Head over to [the community plugins repo](https://github.com/backstage/community-plugins) and get started! A great reference example of a plugin can be found on [our blog](https://backstage.io/blog/2020/04/06/lighthouse-plugin) (thanks [@fastfrwrd](https://github.com/fastfrwrd)!) -What kind of plugins should/could be created? Some inspiration from the 120+ plugins that we have developed inside Spotify can be found [here](https://backstage.io/demos), but we will keep a running list of suggestions labeled with [[plugin]](https://github.com/backstage/backstage/labels/plugin). +What kind of plugins should/could be created? Some inspiration from the 120+ plugins that we have developed inside Spotify can be found [here](https://backstage.io/demos), but we will keep a running list of suggestions labeled with [[plugin]](https://github.com/backstage/community-plugins/labels/plugin) in our [community plugins repo](https://github.com/backstage/community-plugins). ### Suggesting a plugin -If you start developing a plugin that you aim to release as open source, we suggest that you create a [new Issue](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME). This helps the community know what plugins are in development. +If you start developing a plugin that you aim to release as open source, we suggest that you create a [new Issue](https://github.com/backstage/community-plugins/issues/new/choose) in the community plugins repo. This helps the community know what plugins are in development. You can also use this process if you have an idea for a good plugin but you hope that someone else will pick up the work. diff --git a/docs/faq/technical.md b/docs/faq/technical.md index 630e516a35..ffd99dadcd 100644 --- a/docs/faq/technical.md +++ b/docs/faq/technical.md @@ -100,10 +100,10 @@ You can browse and search for all available plugins in the [Plugin Directory](https://backstage.io/plugins). If you can't find it in the directory, before you write a plugin -[search the plugin issues](https://github.com/backstage/backstage/issues?q=is%3Aissue+label%3Aplugin+) +[search the plugin issues on the community plugins repo](https://github.com/backstage/community-plugins/issues?q=is%3Aissue+is%3Aopen+label%3Aplugin) to see if is in the works. If no one's thought of it yet, great! Open a new issue as -[a plugin suggestion](https://github.com/backstage/backstage/issues/new/choose) +[a plugin suggestion](https://github.com/backstage/community-plugins/issues/new/choose) and describe what your plugin will do. This will help coordinate our contributors' efforts and avoid duplicating existing functionality. diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index 1690035b9b..99fdb3d8ed 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -164,7 +164,7 @@ Share your experiences, comments, or suggestions with us: [on discord](https://discord.gg/backstage-687207715902193673), file issues for any [feature](https://github.com/backstage/backstage/issues/new?labels=help+wanted&template=feature_template.md) or -[plugin suggestions](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME), +[plugin suggestions](https://github.com/backstage/community-plugins/issues/new/choose), or [bugs](https://github.com/backstage/backstage/issues/new?labels=bug&template=bug_template.md) you have, and feel free to diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 755c1b8a7d..94de3bc482 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -22,7 +22,7 @@ To create a plugin, follow the steps outlined [here](create-a-plugin.md). If you start developing a plugin that you aim to release as open source, we suggest that you create a -[new Issue](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME). +[new Issue in the community plugins repo](https://github.com/backstage/community-plugins/issues/new/choose). This helps the community know what plugins are in development. You can also use this process if you have an idea for a good plugin but you hope diff --git a/microsite/blog/2020-09-30-plugin-marketplace.mdx b/microsite/blog/2020-09-30-plugin-marketplace.mdx index 2f74b0200e..f97c744263 100644 --- a/microsite/blog/2020-09-30-plugin-marketplace.mdx +++ b/microsite/blog/2020-09-30-plugin-marketplace.mdx @@ -34,7 +34,7 @@ This grand vision is actually not that far off. Already today there is a growing Not all plugins you need will be open source. Every company has their own homegrown tooling. Building internal plugins lets you tailor your version of Backstage to be a perfect fit for your infrastructure and software development needs. If you end up [building plugins](https://backstage.io/docs/plugins/create-a-plugin) that could be useful for other companies, please consider releasing them as open source and [add them to the Marketplace](https://backstage.io/docs/plugins/add-to-marketplace). -If you start developing a plugin that you aim to release as open source, we suggest that you create a [new plugin Issue](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME). This helps the community know what plugins are in development and opens up opportunities for collaboration. You can also use this process if you have an idea for a good plugin, but you need help building it. +If you start developing a plugin that you aim to release as open source, we suggest that you create a [new plugin Issue on the community plugins repo](https://github.com/backstage/community-plugins/issues/new/choose). This helps the community know what plugins are in development and opens up opportunities for collaboration. You can also use this process if you have an idea for a good plugin, but you need help building it. We are really excited to see all the amazing plugins that have already been built, and look forward to seeing even more ideas and collaboration as the Backstage community continues to grow. diff --git a/plugins/README.md b/plugins/README.md index 385e71b6d2..346db0d786 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -1,6 +1,6 @@ # Plugins -Backstage is a single-page application composed of a set of plugins. This folder holds numerous plugins that are managed by this repository. +Backstage is a single-page application composed of a set of plugins. This folder holds numerous plugins that are managed by this repository. However, most plugins are [in the community plugins repo](https://github.com/backstage/community-plugins) - hop over there if you want to contribute! For more information about the plugin ecosystem, see the documentation here: @@ -10,6 +10,6 @@ You can also see the [Plugin Marketplace](https://backstage.io/plugins) for othe ## Suggesting a plugin -If you start developing a plugin that you aim to release as open source, we suggest that you create a [new Issue](https://github.com/backstage/backstage/issues/new?template=plugin_template.md). This helps the community know what plugins are in development. +If you start developing a plugin that you aim to release as open source, we suggest that you create a [new Issue on the community plugins repo](https://github.com/backstage/community-plugins/issues/new/choose). This helps the community know what plugins are in development. You can also use this process if you have an idea for a good plugin but you hope that someone else will pick up the work. From 5b7b49b8977a2d2586f180ef2cf8491e41074dc7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Tue, 23 Apr 2024 19:36:02 +0200 Subject: [PATCH 109/234] core-components: allow guest auth without backend Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- .changeset/six-scissors-smile.md | 5 +++++ .../src/layout/SignInPage/guestProvider.tsx | 12 ++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 .changeset/six-scissors-smile.md diff --git a/.changeset/six-scissors-smile.md b/.changeset/six-scissors-smile.md new file mode 100644 index 0000000000..7680edbba6 --- /dev/null +++ b/.changeset/six-scissors-smile.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +The `SignInPage` guest provider will now fall back to legacy guest auth if the backend request fails, allowing guest auth without a running backend. diff --git a/packages/core-components/src/layout/SignInPage/guestProvider.tsx b/packages/core-components/src/layout/SignInPage/guestProvider.tsx index 3867caed5a..145b653a82 100644 --- a/packages/core-components/src/layout/SignInPage/guestProvider.tsx +++ b/packages/core-components/src/layout/SignInPage/guestProvider.tsx @@ -60,7 +60,11 @@ const Component: ProviderComponent = ({ discoveryApi, }); - const identityResponse = await getIdentity(identity); + const identityResponse = await getIdentity(identity).catch(error => { + // eslint-disable-next-line no-console + console.warn(`Failed to sign in as a guest, ${error}`); + return undefined; + }); if (!identityResponse) { // eslint-disable-next-line no-alert @@ -108,7 +112,11 @@ const loader: ProviderLoader = async apis => { provider: 'guest', discoveryApi: apis.get(discoveryApiRef)!, }); - const identityResponse = await getIdentity(identity); + const identityResponse = await getIdentity(identity).catch(error => { + // eslint-disable-next-line no-console + console.warn(`Failed to sign in as a guest, ${error}`); + return undefined; + }); if (!identityResponse && !useLegacyGuestToken) { return undefined; From eee0518d6d1da6a781fef5d6e74e75d64e62f33a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 24 Apr 2024 04:14:47 +0000 Subject: [PATCH 110/234] Update dependency @swc/core to v1.5.0 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- microsite/yarn.lock | 86 ++++++++++++++++++++++----------------------- storybook/yarn.lock | 86 ++++++++++++++++++++++----------------------- yarn.lock | 86 ++++++++++++++++++++++----------------------- 3 files changed, 129 insertions(+), 129 deletions(-) diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 3b56f4c9da..259bbca7b0 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -2689,90 +2689,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-darwin-arm64@npm:1.4.15" +"@swc/core-darwin-arm64@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-darwin-arm64@npm:1.5.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-darwin-x64@npm:1.4.15" +"@swc/core-darwin-x64@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-darwin-x64@npm:1.5.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.4.15" +"@swc/core-linux-arm-gnueabihf@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.5.0" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-arm64-gnu@npm:1.4.15" +"@swc/core-linux-arm64-gnu@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-arm64-gnu@npm:1.5.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-arm64-musl@npm:1.4.15" +"@swc/core-linux-arm64-musl@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-arm64-musl@npm:1.5.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-x64-gnu@npm:1.4.15" +"@swc/core-linux-x64-gnu@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-x64-gnu@npm:1.5.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-x64-musl@npm:1.4.15" +"@swc/core-linux-x64-musl@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-x64-musl@npm:1.5.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-win32-arm64-msvc@npm:1.4.15" +"@swc/core-win32-arm64-msvc@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-win32-arm64-msvc@npm:1.5.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-win32-ia32-msvc@npm:1.4.15" +"@swc/core-win32-ia32-msvc@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-win32-ia32-msvc@npm:1.5.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-win32-x64-msvc@npm:1.4.15" +"@swc/core-win32-x64-msvc@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-win32-x64-msvc@npm:1.5.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.3.46": - version: 1.4.15 - resolution: "@swc/core@npm:1.4.15" + version: 1.5.0 + resolution: "@swc/core@npm:1.5.0" dependencies: - "@swc/core-darwin-arm64": 1.4.15 - "@swc/core-darwin-x64": 1.4.15 - "@swc/core-linux-arm-gnueabihf": 1.4.15 - "@swc/core-linux-arm64-gnu": 1.4.15 - "@swc/core-linux-arm64-musl": 1.4.15 - "@swc/core-linux-x64-gnu": 1.4.15 - "@swc/core-linux-x64-musl": 1.4.15 - "@swc/core-win32-arm64-msvc": 1.4.15 - "@swc/core-win32-ia32-msvc": 1.4.15 - "@swc/core-win32-x64-msvc": 1.4.15 + "@swc/core-darwin-arm64": 1.5.0 + "@swc/core-darwin-x64": 1.5.0 + "@swc/core-linux-arm-gnueabihf": 1.5.0 + "@swc/core-linux-arm64-gnu": 1.5.0 + "@swc/core-linux-arm64-musl": 1.5.0 + "@swc/core-linux-x64-gnu": 1.5.0 + "@swc/core-linux-x64-musl": 1.5.0 + "@swc/core-win32-arm64-msvc": 1.5.0 + "@swc/core-win32-ia32-msvc": 1.5.0 + "@swc/core-win32-x64-msvc": 1.5.0 "@swc/counter": ^0.1.2 "@swc/types": ^0.1.5 peerDependencies: @@ -2801,7 +2801,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: b5aad99711b56ac14bb05674a54f487cffbe7fb902c14af5102c0d790be3a9a5b37bded8759d6917df37a2767bdc60133322b67ea72c5dc2796d2959dd678dd3 + checksum: 1abab0b57755020b60ebf2557629c23c65c3546e4cd80f9d02fbdf208d4169c9300a0229c088081b43a01e630c9d86d02f8c240c9fc2842eaed2a517770fcd1d languageName: node linkType: hard diff --git a/storybook/yarn.lock b/storybook/yarn.lock index 0b19785288..14a0fc7ee9 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -2842,90 +2842,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-darwin-arm64@npm:1.4.15" +"@swc/core-darwin-arm64@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-darwin-arm64@npm:1.5.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-darwin-x64@npm:1.4.15" +"@swc/core-darwin-x64@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-darwin-x64@npm:1.5.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.4.15" +"@swc/core-linux-arm-gnueabihf@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.5.0" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-arm64-gnu@npm:1.4.15" +"@swc/core-linux-arm64-gnu@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-arm64-gnu@npm:1.5.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-arm64-musl@npm:1.4.15" +"@swc/core-linux-arm64-musl@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-arm64-musl@npm:1.5.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-x64-gnu@npm:1.4.15" +"@swc/core-linux-x64-gnu@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-x64-gnu@npm:1.5.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-x64-musl@npm:1.4.15" +"@swc/core-linux-x64-musl@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-x64-musl@npm:1.5.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-win32-arm64-msvc@npm:1.4.15" +"@swc/core-win32-arm64-msvc@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-win32-arm64-msvc@npm:1.5.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-win32-ia32-msvc@npm:1.4.15" +"@swc/core-win32-ia32-msvc@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-win32-ia32-msvc@npm:1.5.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-win32-x64-msvc@npm:1.4.15" +"@swc/core-win32-x64-msvc@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-win32-x64-msvc@npm:1.5.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.3.46": - version: 1.4.15 - resolution: "@swc/core@npm:1.4.15" + version: 1.5.0 + resolution: "@swc/core@npm:1.5.0" dependencies: - "@swc/core-darwin-arm64": 1.4.15 - "@swc/core-darwin-x64": 1.4.15 - "@swc/core-linux-arm-gnueabihf": 1.4.15 - "@swc/core-linux-arm64-gnu": 1.4.15 - "@swc/core-linux-arm64-musl": 1.4.15 - "@swc/core-linux-x64-gnu": 1.4.15 - "@swc/core-linux-x64-musl": 1.4.15 - "@swc/core-win32-arm64-msvc": 1.4.15 - "@swc/core-win32-ia32-msvc": 1.4.15 - "@swc/core-win32-x64-msvc": 1.4.15 + "@swc/core-darwin-arm64": 1.5.0 + "@swc/core-darwin-x64": 1.5.0 + "@swc/core-linux-arm-gnueabihf": 1.5.0 + "@swc/core-linux-arm64-gnu": 1.5.0 + "@swc/core-linux-arm64-musl": 1.5.0 + "@swc/core-linux-x64-gnu": 1.5.0 + "@swc/core-linux-x64-musl": 1.5.0 + "@swc/core-win32-arm64-msvc": 1.5.0 + "@swc/core-win32-ia32-msvc": 1.5.0 + "@swc/core-win32-x64-msvc": 1.5.0 "@swc/counter": ^0.1.2 "@swc/types": ^0.1.5 peerDependencies: @@ -2954,7 +2954,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: b5aad99711b56ac14bb05674a54f487cffbe7fb902c14af5102c0d790be3a9a5b37bded8759d6917df37a2767bdc60133322b67ea72c5dc2796d2959dd678dd3 + checksum: 1abab0b57755020b60ebf2557629c23c65c3546e4cd80f9d02fbdf208d4169c9300a0229c088081b43a01e630c9d86d02f8c240c9fc2842eaed2a517770fcd1d languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index f7c8413328..c52934fa2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14872,90 +14872,90 @@ __metadata: languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-darwin-arm64@npm:1.4.15" +"@swc/core-darwin-arm64@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-darwin-arm64@npm:1.5.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-darwin-x64@npm:1.4.15" +"@swc/core-darwin-x64@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-darwin-x64@npm:1.5.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.4.15" +"@swc/core-linux-arm-gnueabihf@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.5.0" conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-arm64-gnu@npm:1.4.15" +"@swc/core-linux-arm64-gnu@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-arm64-gnu@npm:1.5.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-arm64-musl@npm:1.4.15" +"@swc/core-linux-arm64-musl@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-arm64-musl@npm:1.5.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-x64-gnu@npm:1.4.15" +"@swc/core-linux-x64-gnu@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-x64-gnu@npm:1.5.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-linux-x64-musl@npm:1.4.15" +"@swc/core-linux-x64-musl@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-linux-x64-musl@npm:1.5.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-win32-arm64-msvc@npm:1.4.15" +"@swc/core-win32-arm64-msvc@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-win32-arm64-msvc@npm:1.5.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-win32-ia32-msvc@npm:1.4.15" +"@swc/core-win32-ia32-msvc@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-win32-ia32-msvc@npm:1.5.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.4.15": - version: 1.4.15 - resolution: "@swc/core-win32-x64-msvc@npm:1.4.15" +"@swc/core-win32-x64-msvc@npm:1.5.0": + version: 1.5.0 + resolution: "@swc/core-win32-x64-msvc@npm:1.5.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.3.46": - version: 1.4.15 - resolution: "@swc/core@npm:1.4.15" + version: 1.5.0 + resolution: "@swc/core@npm:1.5.0" dependencies: - "@swc/core-darwin-arm64": 1.4.15 - "@swc/core-darwin-x64": 1.4.15 - "@swc/core-linux-arm-gnueabihf": 1.4.15 - "@swc/core-linux-arm64-gnu": 1.4.15 - "@swc/core-linux-arm64-musl": 1.4.15 - "@swc/core-linux-x64-gnu": 1.4.15 - "@swc/core-linux-x64-musl": 1.4.15 - "@swc/core-win32-arm64-msvc": 1.4.15 - "@swc/core-win32-ia32-msvc": 1.4.15 - "@swc/core-win32-x64-msvc": 1.4.15 + "@swc/core-darwin-arm64": 1.5.0 + "@swc/core-darwin-x64": 1.5.0 + "@swc/core-linux-arm-gnueabihf": 1.5.0 + "@swc/core-linux-arm64-gnu": 1.5.0 + "@swc/core-linux-arm64-musl": 1.5.0 + "@swc/core-linux-x64-gnu": 1.5.0 + "@swc/core-linux-x64-musl": 1.5.0 + "@swc/core-win32-arm64-msvc": 1.5.0 + "@swc/core-win32-ia32-msvc": 1.5.0 + "@swc/core-win32-x64-msvc": 1.5.0 "@swc/counter": ^0.1.2 "@swc/types": ^0.1.5 peerDependencies: @@ -14984,7 +14984,7 @@ __metadata: peerDependenciesMeta: "@swc/helpers": optional: true - checksum: b5aad99711b56ac14bb05674a54f487cffbe7fb902c14af5102c0d790be3a9a5b37bded8759d6917df37a2767bdc60133322b67ea72c5dc2796d2959dd678dd3 + checksum: 1abab0b57755020b60ebf2557629c23c65c3546e4cd80f9d02fbdf208d4169c9300a0229c088081b43a01e630c9d86d02f8c240c9fc2842eaed2a517770fcd1d languageName: node linkType: hard From dbf269686a5e68f4637289c7b285dfa69b2b76de Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Wed, 17 Apr 2024 13:51:53 +0300 Subject: [PATCH 111/234] feat: add email notification processor Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .changeset/green-boxes-rescue.md | 5 + .../.eslintrc.js | 1 + .../README.md | 26 ++ .../api-report.md | 11 + .../catalog-info.yaml | 12 + .../config.d.ts | 120 +++++++ .../package.json | 54 ++++ .../src/index.ts | 23 ++ .../src/module.ts | 57 ++++ .../NotificationsEmailProcessor.test.ts | 295 ++++++++++++++++++ .../processor/NotificationsEmailProcessor.ts | 229 ++++++++++++++ .../src/processor/index.ts | 16 + .../src/processor/transports/index.ts | 18 ++ .../src/processor/transports/sendmail.ts | 25 ++ .../src/processor/transports/ses.ts | 39 +++ .../src/processor/transports/smtp.ts | 30 ++ .../src/types.ts | 80 +++++ yarn.lock | 87 ++++++ 18 files changed, 1128 insertions(+) create mode 100644 .changeset/green-boxes-rescue.md create mode 100644 plugins/notifications-backend-module-email/.eslintrc.js create mode 100644 plugins/notifications-backend-module-email/README.md create mode 100644 plugins/notifications-backend-module-email/api-report.md create mode 100644 plugins/notifications-backend-module-email/catalog-info.yaml create mode 100644 plugins/notifications-backend-module-email/config.d.ts create mode 100644 plugins/notifications-backend-module-email/package.json create mode 100644 plugins/notifications-backend-module-email/src/index.ts create mode 100644 plugins/notifications-backend-module-email/src/module.ts create mode 100644 plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts create mode 100644 plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts create mode 100644 plugins/notifications-backend-module-email/src/processor/index.ts create mode 100644 plugins/notifications-backend-module-email/src/processor/transports/index.ts create mode 100644 plugins/notifications-backend-module-email/src/processor/transports/sendmail.ts create mode 100644 plugins/notifications-backend-module-email/src/processor/transports/ses.ts create mode 100644 plugins/notifications-backend-module-email/src/processor/transports/smtp.ts create mode 100644 plugins/notifications-backend-module-email/src/types.ts diff --git a/.changeset/green-boxes-rescue.md b/.changeset/green-boxes-rescue.md new file mode 100644 index 0000000000..146695a320 --- /dev/null +++ b/.changeset/green-boxes-rescue.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +--- + +Allow sending notifications by email with the new notifications module diff --git a/plugins/notifications-backend-module-email/.eslintrc.js b/plugins/notifications-backend-module-email/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/notifications-backend-module-email/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/notifications-backend-module-email/README.md b/plugins/notifications-backend-module-email/README.md new file mode 100644 index 0000000000..f87ce8c18e --- /dev/null +++ b/plugins/notifications-backend-module-email/README.md @@ -0,0 +1,26 @@ +# @backstage/plugin-notifications-backend-module-email + +Adds support for sending Backstage notifications as emails to users. + +Supports sending emails using SMTP, SES, or sendmail. + +## Example configuration: + +```yaml +notifications: + processors: + email: + transportConfig: + transport: 'smtp' + hostname: 'my-smtp-server' + port: 587 + secure: false + username: 'my-username' + password: 'my-password' + sender: 'sender@mycompany.com' + replyTo: 'no-reply@mycompany.com' + broadcastConfig: + receiver: 'user' +``` + +See `config.d.ts` for more options for configuration. diff --git a/plugins/notifications-backend-module-email/api-report.md b/plugins/notifications-backend-module-email/api-report.md new file mode 100644 index 0000000000..3f7c9246f5 --- /dev/null +++ b/plugins/notifications-backend-module-email/api-report.md @@ -0,0 +1,11 @@ +## API Report File for "@backstage/plugin-notifications-backend-module-email" + +> 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 (undocumented) +const notificationsModuleEmail: () => BackendFeature; +export default notificationsModuleEmail; +``` diff --git a/plugins/notifications-backend-module-email/catalog-info.yaml b/plugins/notifications-backend-module-email/catalog-info.yaml new file mode 100644 index 0000000000..6e9c0a044f --- /dev/null +++ b/plugins/notifications-backend-module-email/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-notifications-backend-module-email + title: '@backstage/plugin-notifications-backend-module-email' + description: >- + The email-notification-processor backend module for the notifications + plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts new file mode 100644 index 0000000000..503d381161 --- /dev/null +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -0,0 +1,120 @@ +/* + * 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. + */ + +export interface Config { + /** + * Configuration options for notifications-backend-module-email */ + notifications: { + processors: { + email: { + /** + * Transport to use for sending emails */ + transportConfig: + | { + transport: 'smtp'; + /** + * SMTP server hostname + */ + hostname: string; + /** + * SMTP server port + */ + port: number; + /** + * Use secure connection for SMTP, defaults to false + */ + secure?: boolean; + /** + * Require TLS for SMTP connection, defaults to false + */ + requireTls?: boolean; + /** + * SMTP username + */ + username?: string; + /** + * SMTP password + * @visibility secret + */ + password?: string; + } + | { + transport: 'ses'; + /** + * SES ApiVersion to use, defaults to 2010-12-01 + */ + apiVersion?: string; + /** + * SES region to use + */ + region?: string; + /** + * AWS access key id + */ + accessKeyId?: string; + /** + * AWS secret access key + * @visibility secret + */ + secretAccessKey?: string; + } + | { + transport: 'sendmail'; + /** + * Sendmail binary path, defaults to /usr/sbin/sendmail + */ + path?: string; + /** + * Newline style, defaults to 'unix' + */ + newline?: 'unix' | 'windows'; + }; + /** + * Sender email address + */ + sender: string; + /** + * Email format, defaults to HTML + */ + format?: 'html' | 'text'; + /** + * Optional reply-to address + */ + replyTo?: string; + /** + * Configuration for broadcast notifications + */ + broadcastConfig?: { + /** + * Receiver of the broadcast notifications: + * none - skips sending + * users - sends to all users in backstage, might have performance impact + * config - sends to the emails specified in the config + */ + receiver: 'none' | 'users' | 'config'; + /** + * Broadcast notification receivers when receiver is set to config + */ + receiverEmails?: string[]; + }; + /** + * Email cache TTL, defaults to 1 hour + */ + cacheTtl?: number; + }; + }; + }; +} diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json new file mode 100644 index 0000000000..0a5bf4ffa2 --- /dev/null +++ b/plugins/notifications-backend-module-email/package.json @@ -0,0 +1,54 @@ +{ + "name": "@backstage/plugin-notifications-backend-module-email", + "version": "0.0.0", + "description": "The email backend module for the notifications plugin.", + "backstage": { + "role": "backend-plugin-module" + }, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/notifications-backend-module-email" + }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist", + "config.d.ts" + ], + "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": { + "@aws-sdk/client-ses": "^3.556.0", + "@aws-sdk/credential-provider-node": "^3.350.0", + "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", + "@backstage/catalog-client": "workspace:^", + "@backstage/catalog-model": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/plugin-notifications-common": "workspace:^", + "@backstage/plugin-notifications-node": "workspace:^", + "@backstage/types": "workspace:^", + "lodash": "^4.17.21", + "nodemailer": "^6.9.13" + }, + "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", + "@backstage/cli": "workspace:^", + "@types/nodemailer": "^6.4.14" + }, + "configSchema": "config.d.ts" +} diff --git a/plugins/notifications-backend-module-email/src/index.ts b/plugins/notifications-backend-module-email/src/index.ts new file mode 100644 index 0000000000..816d5d3579 --- /dev/null +++ b/plugins/notifications-backend-module-email/src/index.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * The email backend module for the notifications plugin. + * + * @packageDocumentation + */ + +export { notificationsModuleEmail as default } from './module'; diff --git a/plugins/notifications-backend-module-email/src/module.ts b/plugins/notifications-backend-module-email/src/module.ts new file mode 100644 index 0000000000..9c8dfabd8c --- /dev/null +++ b/plugins/notifications-backend-module-email/src/module.ts @@ -0,0 +1,57 @@ +/* + * 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 { + coreServices, + createBackendModule, +} from '@backstage/backend-plugin-api'; +import { CatalogClient } from '@backstage/catalog-client'; +import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; +import { NotificationsEmailProcessor } from './processor'; + +/** + * @public + */ +export const notificationsModuleEmail = createBackendModule({ + pluginId: 'notifications', + moduleId: 'email', + register(reg) { + reg.registerInit({ + deps: { + config: coreServices.rootConfig, + notifications: notificationsProcessingExtensionPoint, + discovery: coreServices.discovery, + logger: coreServices.logger, + auth: coreServices.auth, + cache: coreServices.cache, + }, + async init({ config, notifications, discovery, logger, auth, cache }) { + const catalogClient = new CatalogClient({ + discoveryApi: discovery, + }); + + notifications.addProcessor( + new NotificationsEmailProcessor( + logger, + config, + catalogClient, + auth, + cache, + ), + ); + }, + }); + }, +}); diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts new file mode 100644 index 0000000000..13e1e912d8 --- /dev/null +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -0,0 +1,295 @@ +/* + * 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 { mockServices } from '@backstage/backend-test-utils'; +import { NotificationsEmailProcessor } from './NotificationsEmailProcessor'; +import { ConfigReader } from '@backstage/config'; +import { JsonArray } from '@backstage/types'; +import { CatalogClient } from '@backstage/catalog-client'; +import { createTransport } from 'nodemailer'; + +const sendmailMock = jest.fn(); +const mockTransport = { + sendMail: sendmailMock, +}; +jest.mock('nodemailer', () => ({ + ...jest.requireActual('nodemailer'), + createTransport: jest.fn(), +})); + +describe('NotificationsEmailProcessor', () => { + const logger = mockServices.logger.mock(); + const auth = mockServices.auth(); + + const getEntityRefMock = jest.fn(); + const getEntitiesMock = jest.fn(); + const mockCatalogClient: Partial<CatalogClient> = { + getEntityByRef: getEntityRefMock, + getEntities: getEntitiesMock, + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should create smtp transport', () => { + const processor = new NotificationsEmailProcessor( + logger, + new ConfigReader({ + notifications: { + email: { + transport: { + transport: 'smtp', + hostname: 'localhost', + port: 465, + secure: true, + requireTls: false, + }, + sender: 'backstage@backstage.io', + }, + }, + }), + mockCatalogClient as unknown as CatalogClient, + auth, + ); + + expect(processor).toBeInstanceOf(NotificationsEmailProcessor); + expect(createTransport as jest.Mock).toHaveBeenCalledWith({ + host: 'localhost', + port: 465, + requireTLS: false, + secure: true, + }); + }); + + it('should create ses transport', () => { + const processor = new NotificationsEmailProcessor( + logger, + new ConfigReader({ + notifications: { + email: { + transport: { + transport: 'ses', + region: 'us-west-2', + }, + sender: 'backstage@backstage.io', + }, + }, + }), + mockCatalogClient as unknown as CatalogClient, + auth, + ); + + expect(processor).toBeInstanceOf(NotificationsEmailProcessor); + expect(createTransport as jest.Mock).toHaveBeenCalledWith({ + SES: expect.anything(), + }); + }); + + it('should create sendmail transport', () => { + const processor = new NotificationsEmailProcessor( + logger, + new ConfigReader({ + notifications: { + email: { + transport: { + transport: 'sendmail', + path: '/usr/local/bin/sendmail', + }, + sender: 'backstage@backstage.io', + }, + }, + }), + mockCatalogClient as unknown as CatalogClient, + auth, + ); + + expect(processor).toBeInstanceOf(NotificationsEmailProcessor); + expect(createTransport as jest.Mock).toHaveBeenCalledWith({ + sendmail: true, + path: '/usr/local/bin/sendmail', + newline: 'unix', + }); + }); + + it('should send user email', async () => { + (createTransport as jest.Mock).mockReturnValue(mockTransport); + getEntityRefMock.mockResolvedValue({ + kind: 'User', + spec: { + profile: { + email: 'mock@backstage.io', + }, + }, + }); + const processor = new NotificationsEmailProcessor( + logger, + new ConfigReader({ + notifications: { + email: { + transport: { + transport: 'sendmail', + path: '/usr/local/bin/sendmail', + }, + sender: 'backstage@backstage.io', + }, + }, + }), + mockCatalogClient as unknown as CatalogClient, + auth, + ); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { title: 'notification' }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + + expect(sendmailMock).toHaveBeenCalledWith({ + from: 'backstage@backstage.io', + html: '<p></p>', + replyTo: undefined, + subject: 'notification', + text: undefined, + to: 'mock@backstage.io', + }); + }); + + it('should send email to all', async () => { + (createTransport as jest.Mock).mockReturnValue(mockTransport); + getEntitiesMock.mockResolvedValue({ + items: [ + { + kind: 'User', + spec: { + profile: { + email: 'mock@backstage.io', + }, + }, + }, + ], + }); + const processor = new NotificationsEmailProcessor( + logger, + new ConfigReader({ + notifications: { + email: { + transport: { + transport: 'sendmail', + path: '/usr/local/bin/sendmail', + }, + sender: 'backstage@backstage.io', + broadcastConfig: { + receiver: 'users', + }, + }, + }, + }), + mockCatalogClient as unknown as CatalogClient, + auth, + ); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: null, + created: new Date(), + payload: { title: 'notification' }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification' }, + }, + ); + + expect(sendmailMock).toHaveBeenCalledWith({ + from: 'backstage@backstage.io', + html: '<p></p>', + replyTo: undefined, + subject: 'notification', + text: undefined, + to: 'mock@backstage.io', + }); + }); + + it('should send email to configured addresses', async () => { + (createTransport as jest.Mock).mockReturnValue(mockTransport); + getEntitiesMock.mockResolvedValue({ + items: [ + { + kind: 'User', + spec: { + profile: { + email: 'mock@backstage.io', + }, + }, + }, + ], + }); + const processor = new NotificationsEmailProcessor( + logger, + new ConfigReader({ + notifications: { + email: { + transport: { + transport: 'sendmail', + path: '/usr/local/bin/sendmail', + }, + sender: 'backstage@backstage.io', + broadcastConfig: { + receiver: 'config', + receiverEmails: ['broadcast@backstage.io'] as JsonArray, + }, + }, + }, + }), + mockCatalogClient as unknown as CatalogClient, + auth, + ); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: null, + created: new Date(), + payload: { title: 'notification' }, + }, + { + recipients: { type: 'broadcast' }, + payload: { title: 'notification' }, + }, + ); + + expect(sendmailMock).toHaveBeenCalledWith({ + from: 'backstage@backstage.io', + html: '<p></p>', + replyTo: undefined, + subject: 'notification', + text: undefined, + to: 'broadcast@backstage.io', + }); + }); +}); diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts new file mode 100644 index 0000000000..8cf28917d5 --- /dev/null +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -0,0 +1,229 @@ +/* + * 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 { + NotificationProcessor, + NotificationSendOptions, +} from '@backstage/plugin-notifications-node'; +import { + AuthService, + CacheService, + LoggerService, +} from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; +import { JsonArray } from '@backstage/types'; +import { + CATALOG_FILTER_EXISTS, + CatalogClient, +} from '@backstage/catalog-client'; +import { Notification } from '@backstage/plugin-notifications-common'; +import { createSendmailTransport, createSmtpTransport } from './transports'; +import { createSesTransport } from './transports/ses'; +import { UserEntity } from '@backstage/catalog-model'; +import { compact } from 'lodash'; + +export class NotificationsEmailProcessor implements NotificationProcessor { + private readonly transportter: any; + private readonly broadcastConfig?: Config; + private readonly sender: string; + private readonly format: string; + private readonly replyTo?: string; + private readonly cacheTtl: number; + + constructor( + private readonly logger: LoggerService, + config: Config, + private readonly catalog: CatalogClient, + private readonly auth: AuthService, + private readonly cache?: CacheService, + ) { + const transportConfig = config.getConfig('notifications.email.transport'); + const transport = transportConfig.getString('transport'); + if (transport === 'smtp') { + this.transportter = createSmtpTransport({ + transport: 'smtp', + hostname: transportConfig.getString('hostname'), + port: transportConfig.getNumber('port'), + secure: transportConfig.getOptionalBoolean('secure'), + requireTls: transportConfig.getOptionalBoolean('requireTls'), + username: transportConfig.getOptionalString('username'), + password: transportConfig.getOptionalString('password'), + }); + } else if (transport === 'ses') { + this.transportter = createSesTransport({ + transport: 'ses', + apiVersion: transportConfig.getOptionalString('apiVersion'), + region: transportConfig.getOptionalString('region'), + accessKeyId: transportConfig.getOptionalString('accessKeyId'), + secretAccessKey: transportConfig.getOptionalString('secretAccessKey'), + }); + } else if (transport === 'sendmail') { + this.transportter = createSendmailTransport({ + transport: 'sendmail', + path: transportConfig.getOptionalString('path'), + newline: transportConfig.getOptionalString('newline'), + }); + } else { + throw new Error(`Unsupported transport: ${transport}`); + } + + this.broadcastConfig = config.getOptionalConfig( + 'notifications.email.broadcastConfig', + ); + this.sender = config.getString('notifications.email.sender'); + this.format = + config.getOptionalString('notifications.email.format') ?? 'html'; + this.replyTo = config.getOptionalString('notifications.email.replyTo'); + this.cacheTtl = + config.getOptionalNumber('notifications.email.cacheTtl') ?? 3_600_000; + } + + getName(): string { + return 'Email'; + } + + private async getBroadcastEmails(): Promise<string[]> { + if (!this.broadcastConfig) { + return []; + } + + const receiver = this.broadcastConfig.getString('receiver'); + if (receiver === 'none') { + return []; + } + + if (receiver === 'config') { + return ( + this.broadcastConfig.getOptionalStringArray('receiverEmails') ?? [] + ); + } + + if (receiver === 'users') { + const cached = await this.cache?.get<JsonArray>('user-emails:all'); + if (cached) { + return cached as string[]; + } + + const credentials = await this.auth.getOwnServiceCredentials(); + const { token } = await this.auth.getPluginRequestToken({ + onBehalfOf: credentials, + targetPluginId: 'catalog', + }); + const entities = await this.catalog.getEntities( + { + filter: [ + { kind: 'user', 'spec.profile.email': CATALOG_FILTER_EXISTS }, + ], + fields: ['spec.profile.email'], + }, + { token }, + ); + const ret = compact([ + ...new Set( + entities.items.map(entity => { + return (entity as UserEntity)?.spec.profile?.email; + }), + ), + ]); + await this.cache?.set('user-emails:all', ret as JsonArray, { + ttl: this.cacheTtl, + }); + return ret; + } + + throw new Error(`Unsupported broadcast receiver: ${receiver}`); + } + + private async getUserEmail(entityRef: string): Promise<string[]> { + const cached = await this.cache?.get<string>(`user-emails:${entityRef}`); + if (cached) { + return [cached]; + } + + const credentials = await this.auth.getOwnServiceCredentials(); + const { token } = await this.auth.getPluginRequestToken({ + onBehalfOf: credentials, + targetPluginId: 'catalog', + }); + const entity = await this.catalog.getEntityByRef(entityRef, { token }); + if (!entity) { + return []; + } + + const userEntity = entity as UserEntity; + if (!userEntity.spec.profile?.email) { + return []; + } + + await this.cache?.set( + `user-emails:${entityRef}`, + userEntity.spec.profile.email, + { ttl: this.cacheTtl }, + ); + + return [userEntity.spec.profile.email]; + } + + private async getRecipientEmails( + notification: Notification, + options: NotificationSendOptions, + ) { + if (options.recipients.type === 'broadcast' || notification.user === null) { + return await this.getBroadcastEmails(); + } + return await this.getUserEmail(notification.user); + } + + async postProcess( + notification: Notification, + options: NotificationSendOptions, + ): Promise<void> { + let emails: string[] = []; + try { + emails = await this.getRecipientEmails(notification, options); + } catch (e) { + this.logger.error(`Failed to resolve recipient emails: ${e}`); + return; + } + + // TODO: add template support for content either HTML or text + const contentParts: string[] = []; + if (notification.payload.description) { + contentParts.push(`${notification.payload.description}`); + } + if (notification.payload.link) { + contentParts.push(`${notification.payload.link}`); + } + + const mailOptions = { + from: this.sender, + subject: notification.payload.title, + html: + this.format === 'html' + ? `<p>${contentParts.join('<br/>')}</p>` + : undefined, + text: this.format === 'text' ? contentParts.join('\n\n') : undefined, + replyTo: this.replyTo, + }; + + for (const email of emails) { + try { + await this.transportter.sendMail({ ...mailOptions, to: email }); + } catch (e) { + this.logger.error(`Failed to send email to ${email}: ${e}`); + } + } + } +} diff --git a/plugins/notifications-backend-module-email/src/processor/index.ts b/plugins/notifications-backend-module-email/src/processor/index.ts new file mode 100644 index 0000000000..cc5d6a1dc8 --- /dev/null +++ b/plugins/notifications-backend-module-email/src/processor/index.ts @@ -0,0 +1,16 @@ +/* + * 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. + */ +export { NotificationsEmailProcessor } from './NotificationsEmailProcessor'; diff --git a/plugins/notifications-backend-module-email/src/processor/transports/index.ts b/plugins/notifications-backend-module-email/src/processor/transports/index.ts new file mode 100644 index 0000000000..ccea9f77cc --- /dev/null +++ b/plugins/notifications-backend-module-email/src/processor/transports/index.ts @@ -0,0 +1,18 @@ +/* + * 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. + */ +export { createSmtpTransport } from './smtp'; +export { createSesTransport } from './ses'; +export { createSendmailTransport } from './sendmail'; diff --git a/plugins/notifications-backend-module-email/src/processor/transports/sendmail.ts b/plugins/notifications-backend-module-email/src/processor/transports/sendmail.ts new file mode 100644 index 0000000000..8f093f562d --- /dev/null +++ b/plugins/notifications-backend-module-email/src/processor/transports/sendmail.ts @@ -0,0 +1,25 @@ +/* + * 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 { createTransport } from 'nodemailer'; +import { SendmailTransportConfig } from '../../types'; + +export const createSendmailTransport = (config: SendmailTransportConfig) => { + return createTransport({ + sendmail: true, + newline: config.newline ?? 'unix', + path: config.path ?? '/usr/sbin/sendmail', + }); +}; diff --git a/plugins/notifications-backend-module-email/src/processor/transports/ses.ts b/plugins/notifications-backend-module-email/src/processor/transports/ses.ts new file mode 100644 index 0000000000..739a3167ab --- /dev/null +++ b/plugins/notifications-backend-module-email/src/processor/transports/ses.ts @@ -0,0 +1,39 @@ +/* + * 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 { SesTransportConfig } from '../../types'; +import { createTransport } from 'nodemailer'; +import * as aws from '@aws-sdk/client-ses'; +import { defaultProvider } from '@aws-sdk/credential-provider-node'; + +export const createSesTransport = (config: SesTransportConfig) => { + const ses = new aws.SES([ + { + region: config.region, + apiVersion: config.apiVersion ?? '2010-12-01', + defaultProvider, + credentials: + config.accessKeyId && config.secretAccessKey + ? { + accessKeyId: config.accessKeyId, + secretAccessKey: config.secretAccessKey, + } + : undefined, + }, + ]); + return createTransport({ + SES: { ses, aws }, + }); +}; diff --git a/plugins/notifications-backend-module-email/src/processor/transports/smtp.ts b/plugins/notifications-backend-module-email/src/processor/transports/smtp.ts new file mode 100644 index 0000000000..e473ea5ba7 --- /dev/null +++ b/plugins/notifications-backend-module-email/src/processor/transports/smtp.ts @@ -0,0 +1,30 @@ +/* + * 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 { SmtpTransportConfig } from '../../types'; +import { createTransport } from 'nodemailer'; + +export const createSmtpTransport = (config: SmtpTransportConfig) => { + return createTransport({ + host: config.hostname, + port: config.port, + secure: config.secure ?? false, + requireTLS: config.requireTls ?? false, + auth: + config.username && config.password + ? { user: config.username, pass: config.password } + : undefined, + }); +}; diff --git a/plugins/notifications-backend-module-email/src/types.ts b/plugins/notifications-backend-module-email/src/types.ts new file mode 100644 index 0000000000..6dd6ae9d76 --- /dev/null +++ b/plugins/notifications-backend-module-email/src/types.ts @@ -0,0 +1,80 @@ +/* + * 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. + */ +export interface TransportConfig { + transport: 'smtp' | 'ses' | 'sendmail'; +} + +export interface SmtpTransportConfig extends TransportConfig { + transport: 'smtp'; + /** + * SMTP server hostname + */ + hostname: string; + /** + * SMTP server port + */ + port: number; + /** + * Use secure connection for SMTP, defaults to false + */ + secure?: boolean; + /** + * Require TLS for SMTP connection, defaults to false + */ + requireTls?: boolean; + /** + * SMTP username + */ + username?: string; + /** + * SMTP password + * @visibility secret + */ + password?: string; +} + +export interface SesTransportConfig extends TransportConfig { + transport: 'ses'; + /** + * SES ApiVersion to use, defaults to 2010-12-01 + */ + apiVersion?: string; + /** + * SES region to use + */ + region?: string; + /** + * AWS access key id + */ + accessKeyId?: string; + /** + * AWS secret access key + * @visibility secret + */ + secretAccessKey?: string; +} + +export interface SendmailTransportConfig extends TransportConfig { + transport: 'sendmail'; + /** + * Sendmail binary path, defaults to /usr/sbin/sendmail + */ + path?: string; + /** + * Newline style, defaults to 'unix' + */ + newline?: string; +} diff --git a/yarn.lock b/yarn.lock index a07e9249f7..a0f16fa1ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -599,6 +599,55 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-ses@npm:^3.556.0": + version: 3.556.0 + resolution: "@aws-sdk/client-ses@npm:3.556.0" + dependencies: + "@aws-crypto/sha256-browser": 3.0.0 + "@aws-crypto/sha256-js": 3.0.0 + "@aws-sdk/client-sts": 3.556.0 + "@aws-sdk/core": 3.556.0 + "@aws-sdk/credential-provider-node": 3.556.0 + "@aws-sdk/middleware-host-header": 3.535.0 + "@aws-sdk/middleware-logger": 3.535.0 + "@aws-sdk/middleware-recursion-detection": 3.535.0 + "@aws-sdk/middleware-user-agent": 3.540.0 + "@aws-sdk/region-config-resolver": 3.535.0 + "@aws-sdk/types": 3.535.0 + "@aws-sdk/util-endpoints": 3.540.0 + "@aws-sdk/util-user-agent-browser": 3.535.0 + "@aws-sdk/util-user-agent-node": 3.535.0 + "@smithy/config-resolver": ^2.2.0 + "@smithy/core": ^1.4.2 + "@smithy/fetch-http-handler": ^2.5.0 + "@smithy/hash-node": ^2.2.0 + "@smithy/invalid-dependency": ^2.2.0 + "@smithy/middleware-content-length": ^2.2.0 + "@smithy/middleware-endpoint": ^2.5.1 + "@smithy/middleware-retry": ^2.3.1 + "@smithy/middleware-serde": ^2.3.0 + "@smithy/middleware-stack": ^2.2.0 + "@smithy/node-config-provider": ^2.3.0 + "@smithy/node-http-handler": ^2.5.0 + "@smithy/protocol-http": ^3.3.0 + "@smithy/smithy-client": ^2.5.1 + "@smithy/types": ^2.12.0 + "@smithy/url-parser": ^2.2.0 + "@smithy/util-base64": ^2.3.0 + "@smithy/util-body-length-browser": ^2.2.0 + "@smithy/util-body-length-node": ^2.3.0 + "@smithy/util-defaults-mode-browser": ^2.2.1 + "@smithy/util-defaults-mode-node": ^2.3.1 + "@smithy/util-endpoints": ^1.2.0 + "@smithy/util-middleware": ^2.2.0 + "@smithy/util-retry": ^2.2.0 + "@smithy/util-utf8": ^2.3.0 + "@smithy/util-waiter": ^2.2.0 + tslib: ^2.6.2 + checksum: 913108e79061185faae51711b121df99da624f988f530af23c3e4270299be60771be83f507dc8ed4af4051765077ea038edab19e4d0d32e159c4e67faf5fc9f8 + languageName: node + linkType: hard + "@aws-sdk/client-sqs@npm:^3.350.0": version: 3.556.0 resolution: "@aws-sdk/client-sqs@npm:3.556.0" @@ -5982,6 +6031,28 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-notifications-backend-module-email@workspace:plugins/notifications-backend-module-email": + version: 0.0.0-use.local + resolution: "@backstage/plugin-notifications-backend-module-email@workspace:plugins/notifications-backend-module-email" + dependencies: + "@aws-sdk/client-ses": ^3.556.0 + "@aws-sdk/credential-provider-node": ^3.350.0 + "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" + "@backstage/catalog-client": "workspace:^" + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/plugin-notifications-common": "workspace:^" + "@backstage/plugin-notifications-node": "workspace:^" + "@backstage/types": "workspace:^" + "@types/nodemailer": ^6.4.14 + lodash: ^4.17.21 + nodemailer: ^6.9.13 + languageName: unknown + linkType: soft + "@backstage/plugin-notifications-backend@workspace:^, @backstage/plugin-notifications-backend@workspace:plugins/notifications-backend": version: 0.0.0-use.local resolution: "@backstage/plugin-notifications-backend@workspace:plugins/notifications-backend" @@ -16064,6 +16135,15 @@ __metadata: languageName: node linkType: hard +"@types/nodemailer@npm:^6.4.14": + version: 6.4.14 + resolution: "@types/nodemailer@npm:6.4.14" + dependencies: + "@types/node": "*" + checksum: 5f61f01dd736b17f431d1e8b320322f86460604b45df947fc4bc8999d7c7719405e349f7abba86e4fb100a464a30b52615d00dac03d9cb37562ff04487ebd310 + languageName: node + linkType: hard + "@types/normalize-package-data@npm:^2.4.0": version: 2.4.1 resolution: "@types/normalize-package-data@npm:2.4.1" @@ -32254,6 +32334,13 @@ __metadata: languageName: node linkType: hard +"nodemailer@npm:^6.9.13": + version: 6.9.13 + resolution: "nodemailer@npm:6.9.13" + checksum: 1b591ef480be2ff69480127cbff819e6593b1ef263b6f920e1a4e83e40280582daf7a14a809ef92f9828e2a70bdb3ce22b11924e209f2afe4975f9ff37e08e9d + languageName: node + linkType: hard + "nodemon@npm:^3.0.1": version: 3.1.0 resolution: "nodemon@npm:3.1.0" From 1c1718857e3083e1fbeff1450c46cecba542dc65 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Wed, 17 Apr 2024 18:16:47 +0300 Subject: [PATCH 112/234] fix: use credentials manager instead config Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .../config.d.ts | 13 +- .../package.json | 5 +- .../NotificationsEmailProcessor.test.ts | 48 ++- .../processor/NotificationsEmailProcessor.ts | 86 +++--- .../src/processor/transports/ses.ts | 24 +- .../src/types.ts | 17 +- yarn.lock | 273 +++++++++++++++++- 7 files changed, 381 insertions(+), 85 deletions(-) diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index 503d381161..fff78f735f 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -58,18 +58,9 @@ export interface Config { */ apiVersion?: string; /** - * SES region to use + * AWS account ID to use */ - region?: string; - /** - * AWS access key id - */ - accessKeyId?: string; - /** - * AWS secret access key - * @visibility secret - */ - secretAccessKey?: string; + accountId?: string; } | { transport: 'sendmail'; diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 0a5bf4ffa2..345ba162b6 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -32,13 +32,14 @@ "test": "backstage-cli package test" }, "dependencies": { - "@aws-sdk/client-ses": "^3.556.0", - "@aws-sdk/credential-provider-node": "^3.350.0", + "@aws-sdk/client-ses": "3.554.0", + "@aws-sdk/types": "^3.347.0", "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/integration-aws-node": "workspace:^", "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index 13e1e912d8..72d1113eb1 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -45,7 +45,7 @@ describe('NotificationsEmailProcessor', () => { jest.resetAllMocks(); }); - it('should create smtp transport', () => { + it('should create smtp transport', async () => { const processor = new NotificationsEmailProcessor( logger, new ConfigReader({ @@ -66,6 +66,20 @@ describe('NotificationsEmailProcessor', () => { auth, ); + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { title: 'notification' }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + expect(processor).toBeInstanceOf(NotificationsEmailProcessor); expect(createTransport as jest.Mock).toHaveBeenCalledWith({ host: 'localhost', @@ -75,7 +89,7 @@ describe('NotificationsEmailProcessor', () => { }); }); - it('should create ses transport', () => { + it('should create ses transport', async () => { const processor = new NotificationsEmailProcessor( logger, new ConfigReader({ @@ -93,13 +107,27 @@ describe('NotificationsEmailProcessor', () => { auth, ); + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { title: 'notification' }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + expect(processor).toBeInstanceOf(NotificationsEmailProcessor); expect(createTransport as jest.Mock).toHaveBeenCalledWith({ SES: expect.anything(), }); }); - it('should create sendmail transport', () => { + it('should create sendmail transport', async () => { const processor = new NotificationsEmailProcessor( logger, new ConfigReader({ @@ -117,6 +145,20 @@ describe('NotificationsEmailProcessor', () => { auth, ); + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { title: 'notification' }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + expect(processor).toBeInstanceOf(NotificationsEmailProcessor); expect(createTransport as jest.Mock).toHaveBeenCalledWith({ sendmail: true, diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index 8cf28917d5..8150aeebe6 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -33,9 +33,10 @@ import { createSendmailTransport, createSmtpTransport } from './transports'; import { createSesTransport } from './transports/ses'; import { UserEntity } from '@backstage/catalog-model'; import { compact } from 'lodash'; +import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node'; export class NotificationsEmailProcessor implements NotificationProcessor { - private readonly transportter: any; + private transporter: any; private readonly broadcastConfig?: Config; private readonly sender: string; private readonly format: string; @@ -44,41 +45,11 @@ export class NotificationsEmailProcessor implements NotificationProcessor { constructor( private readonly logger: LoggerService, - config: Config, + private readonly config: Config, private readonly catalog: CatalogClient, private readonly auth: AuthService, private readonly cache?: CacheService, ) { - const transportConfig = config.getConfig('notifications.email.transport'); - const transport = transportConfig.getString('transport'); - if (transport === 'smtp') { - this.transportter = createSmtpTransport({ - transport: 'smtp', - hostname: transportConfig.getString('hostname'), - port: transportConfig.getNumber('port'), - secure: transportConfig.getOptionalBoolean('secure'), - requireTls: transportConfig.getOptionalBoolean('requireTls'), - username: transportConfig.getOptionalString('username'), - password: transportConfig.getOptionalString('password'), - }); - } else if (transport === 'ses') { - this.transportter = createSesTransport({ - transport: 'ses', - apiVersion: transportConfig.getOptionalString('apiVersion'), - region: transportConfig.getOptionalString('region'), - accessKeyId: transportConfig.getOptionalString('accessKeyId'), - secretAccessKey: transportConfig.getOptionalString('secretAccessKey'), - }); - } else if (transport === 'sendmail') { - this.transportter = createSendmailTransport({ - transport: 'sendmail', - path: transportConfig.getOptionalString('path'), - newline: transportConfig.getOptionalString('newline'), - }); - } else { - throw new Error(`Unsupported transport: ${transport}`); - } - this.broadcastConfig = config.getOptionalConfig( 'notifications.email.broadcastConfig', ); @@ -90,6 +61,46 @@ export class NotificationsEmailProcessor implements NotificationProcessor { config.getOptionalNumber('notifications.email.cacheTtl') ?? 3_600_000; } + private async getTransporter() { + if (this.transporter) { + return this.transporter; + } + const transportConfig = this.config.getConfig( + 'notifications.email.transport', + ); + const transport = transportConfig.getString('transport'); + if (transport === 'smtp') { + this.transporter = createSmtpTransport({ + transport: 'smtp', + hostname: transportConfig.getString('hostname'), + port: transportConfig.getNumber('port'), + secure: transportConfig.getOptionalBoolean('secure'), + requireTls: transportConfig.getOptionalBoolean('requireTls'), + username: transportConfig.getOptionalString('username'), + password: transportConfig.getOptionalString('password'), + }); + } else if (transport === 'ses') { + const awsCredentialsManager = DefaultAwsCredentialsManager.fromConfig( + this.config, + ); + this.transporter = await createSesTransport({ + transport: 'ses', + credentialsManager: awsCredentialsManager, + apiVersion: transportConfig.getOptionalString('apiVersion'), + accountId: transportConfig.getOptionalString('accountId'), + }); + } else if (transport === 'sendmail') { + this.transporter = createSendmailTransport({ + transport: 'sendmail', + path: transportConfig.getOptionalString('path'), + newline: transportConfig.getOptionalString('newline'), + }); + } else { + throw new Error(`Unsupported transport: ${transport}`); + } + return this.transporter; + } + getName(): string { return 'Email'; } @@ -190,6 +201,8 @@ export class NotificationsEmailProcessor implements NotificationProcessor { notification: Notification, options: NotificationSendOptions, ): Promise<void> { + this.transporter = await this.getTransporter(); + let emails: string[] = []; try { emails = await this.getRecipientEmails(notification, options); @@ -198,6 +211,13 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return; } + if (emails.length === 0) { + this.logger.info( + `No email recipients found for notification: ${notification.id}, skipping`, + ); + return; + } + // TODO: add template support for content either HTML or text const contentParts: string[] = []; if (notification.payload.description) { @@ -220,7 +240,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { for (const email of emails) { try { - await this.transportter.sendMail({ ...mailOptions, to: email }); + await this.transporter.sendMail({ ...mailOptions, to: email }); } catch (e) { this.logger.error(`Failed to send email to ${email}: ${e}`); } diff --git a/plugins/notifications-backend-module-email/src/processor/transports/ses.ts b/plugins/notifications-backend-module-email/src/processor/transports/ses.ts index 739a3167ab..7073cc08b2 100644 --- a/plugins/notifications-backend-module-email/src/processor/transports/ses.ts +++ b/plugins/notifications-backend-module-email/src/processor/transports/ses.ts @@ -15,25 +15,21 @@ */ import { SesTransportConfig } from '../../types'; import { createTransport } from 'nodemailer'; -import * as aws from '@aws-sdk/client-ses'; -import { defaultProvider } from '@aws-sdk/credential-provider-node'; +import { SendRawEmailCommand, SES } from '@aws-sdk/client-ses'; -export const createSesTransport = (config: SesTransportConfig) => { - const ses = new aws.SES([ +export const createSesTransport = async (config: SesTransportConfig) => { + const credentials = await config.credentialsManager.getCredentialProvider({ + accountId: config.accountId, + }); + const ses = new SES([ { - region: config.region, apiVersion: config.apiVersion ?? '2010-12-01', - defaultProvider, - credentials: - config.accessKeyId && config.secretAccessKey - ? { - accessKeyId: config.accessKeyId, - secretAccessKey: config.secretAccessKey, - } - : undefined, + credentials: credentials.sdkCredentialProvider, + region: credentials.stsRegion, + accountId: credentials.accountId, }, ]); return createTransport({ - SES: { ses, aws }, + SES: { ses, aws: { SendRawEmailCommand } }, }); }; diff --git a/plugins/notifications-backend-module-email/src/types.ts b/plugins/notifications-backend-module-email/src/types.ts index 6dd6ae9d76..67c7a1740d 100644 --- a/plugins/notifications-backend-module-email/src/types.ts +++ b/plugins/notifications-backend-module-email/src/types.ts @@ -13,6 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { AwsCredentialsManager } from '@backstage/integration-aws-node'; + export interface TransportConfig { transport: 'smtp' | 'ses' | 'sendmail'; } @@ -52,19 +54,8 @@ export interface SesTransportConfig extends TransportConfig { * SES ApiVersion to use, defaults to 2010-12-01 */ apiVersion?: string; - /** - * SES region to use - */ - region?: string; - /** - * AWS access key id - */ - accessKeyId?: string; - /** - * AWS secret access key - * @visibility secret - */ - secretAccessKey?: string; + accountId?: string; + credentialsManager: AwsCredentialsManager; } export interface SendmailTransportConfig extends TransportConfig { diff --git a/yarn.lock b/yarn.lock index a0f16fa1ab..94ba875fae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -599,15 +599,15 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-ses@npm:^3.556.0": - version: 3.556.0 - resolution: "@aws-sdk/client-ses@npm:3.556.0" +"@aws-sdk/client-ses@npm:3.554.0": + version: 3.554.0 + resolution: "@aws-sdk/client-ses@npm:3.554.0" dependencies: "@aws-crypto/sha256-browser": 3.0.0 "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/client-sts": 3.556.0 - "@aws-sdk/core": 3.556.0 - "@aws-sdk/credential-provider-node": 3.556.0 + "@aws-sdk/client-sts": 3.554.0 + "@aws-sdk/core": 3.554.0 + "@aws-sdk/credential-provider-node": 3.554.0 "@aws-sdk/middleware-host-header": 3.535.0 "@aws-sdk/middleware-logger": 3.535.0 "@aws-sdk/middleware-recursion-detection": 3.535.0 @@ -644,7 +644,7 @@ __metadata: "@smithy/util-utf8": ^2.3.0 "@smithy/util-waiter": ^2.2.0 tslib: ^2.6.2 - checksum: 913108e79061185faae51711b121df99da624f988f530af23c3e4270299be60771be83f507dc8ed4af4051765077ea038edab19e4d0d32e159c4e67faf5fc9f8 + checksum: 14512aac8bd84a69066535580bd739509a5698a64be78b111fd9656f4a01f4921c016b0c22c8b103261370d9e75ba3c6301cc652b47e396e421b8b02f93334ce languageName: node linkType: hard @@ -698,6 +698,55 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sso-oidc@npm:3.554.0": + version: 3.554.0 + resolution: "@aws-sdk/client-sso-oidc@npm:3.554.0" + dependencies: + "@aws-crypto/sha256-browser": 3.0.0 + "@aws-crypto/sha256-js": 3.0.0 + "@aws-sdk/client-sts": 3.554.0 + "@aws-sdk/core": 3.554.0 + "@aws-sdk/middleware-host-header": 3.535.0 + "@aws-sdk/middleware-logger": 3.535.0 + "@aws-sdk/middleware-recursion-detection": 3.535.0 + "@aws-sdk/middleware-user-agent": 3.540.0 + "@aws-sdk/region-config-resolver": 3.535.0 + "@aws-sdk/types": 3.535.0 + "@aws-sdk/util-endpoints": 3.540.0 + "@aws-sdk/util-user-agent-browser": 3.535.0 + "@aws-sdk/util-user-agent-node": 3.535.0 + "@smithy/config-resolver": ^2.2.0 + "@smithy/core": ^1.4.2 + "@smithy/fetch-http-handler": ^2.5.0 + "@smithy/hash-node": ^2.2.0 + "@smithy/invalid-dependency": ^2.2.0 + "@smithy/middleware-content-length": ^2.2.0 + "@smithy/middleware-endpoint": ^2.5.1 + "@smithy/middleware-retry": ^2.3.1 + "@smithy/middleware-serde": ^2.3.0 + "@smithy/middleware-stack": ^2.2.0 + "@smithy/node-config-provider": ^2.3.0 + "@smithy/node-http-handler": ^2.5.0 + "@smithy/protocol-http": ^3.3.0 + "@smithy/smithy-client": ^2.5.1 + "@smithy/types": ^2.12.0 + "@smithy/url-parser": ^2.2.0 + "@smithy/util-base64": ^2.3.0 + "@smithy/util-body-length-browser": ^2.2.0 + "@smithy/util-body-length-node": ^2.3.0 + "@smithy/util-defaults-mode-browser": ^2.2.1 + "@smithy/util-defaults-mode-node": ^2.3.1 + "@smithy/util-endpoints": ^1.2.0 + "@smithy/util-middleware": ^2.2.0 + "@smithy/util-retry": ^2.2.0 + "@smithy/util-utf8": ^2.3.0 + tslib: ^2.6.2 + peerDependencies: + "@aws-sdk/credential-provider-node": ^3.554.0 + checksum: 91d0f6b8c5c787e2b705803d615f9400430ea0bca9d12171a5e7abe5c53cd83f711300e5c0a106dc4f1f1259adf08f87b728f9820679aa5ca73cd66a09bf8dd4 + languageName: node + linkType: hard + "@aws-sdk/client-sso-oidc@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/client-sso-oidc@npm:3.556.0" @@ -747,6 +796,52 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sso@npm:3.554.0": + version: 3.554.0 + resolution: "@aws-sdk/client-sso@npm:3.554.0" + dependencies: + "@aws-crypto/sha256-browser": 3.0.0 + "@aws-crypto/sha256-js": 3.0.0 + "@aws-sdk/core": 3.554.0 + "@aws-sdk/middleware-host-header": 3.535.0 + "@aws-sdk/middleware-logger": 3.535.0 + "@aws-sdk/middleware-recursion-detection": 3.535.0 + "@aws-sdk/middleware-user-agent": 3.540.0 + "@aws-sdk/region-config-resolver": 3.535.0 + "@aws-sdk/types": 3.535.0 + "@aws-sdk/util-endpoints": 3.540.0 + "@aws-sdk/util-user-agent-browser": 3.535.0 + "@aws-sdk/util-user-agent-node": 3.535.0 + "@smithy/config-resolver": ^2.2.0 + "@smithy/core": ^1.4.2 + "@smithy/fetch-http-handler": ^2.5.0 + "@smithy/hash-node": ^2.2.0 + "@smithy/invalid-dependency": ^2.2.0 + "@smithy/middleware-content-length": ^2.2.0 + "@smithy/middleware-endpoint": ^2.5.1 + "@smithy/middleware-retry": ^2.3.1 + "@smithy/middleware-serde": ^2.3.0 + "@smithy/middleware-stack": ^2.2.0 + "@smithy/node-config-provider": ^2.3.0 + "@smithy/node-http-handler": ^2.5.0 + "@smithy/protocol-http": ^3.3.0 + "@smithy/smithy-client": ^2.5.1 + "@smithy/types": ^2.12.0 + "@smithy/url-parser": ^2.2.0 + "@smithy/util-base64": ^2.3.0 + "@smithy/util-body-length-browser": ^2.2.0 + "@smithy/util-body-length-node": ^2.3.0 + "@smithy/util-defaults-mode-browser": ^2.2.1 + "@smithy/util-defaults-mode-node": ^2.3.1 + "@smithy/util-endpoints": ^1.2.0 + "@smithy/util-middleware": ^2.2.0 + "@smithy/util-retry": ^2.2.0 + "@smithy/util-utf8": ^2.3.0 + tslib: ^2.6.2 + checksum: 08dbaca87bb25d7662a2e856dca16f92bc461b9cf50e048c679969df93189f653c2bc74c8be1ac3e82e720d2742fc81af554a04f0ebf83353c1fa6e30fdbbecf + languageName: node + linkType: hard + "@aws-sdk/client-sso@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/client-sso@npm:3.556.0" @@ -793,6 +888,54 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/client-sts@npm:3.554.0": + version: 3.554.0 + resolution: "@aws-sdk/client-sts@npm:3.554.0" + dependencies: + "@aws-crypto/sha256-browser": 3.0.0 + "@aws-crypto/sha256-js": 3.0.0 + "@aws-sdk/core": 3.554.0 + "@aws-sdk/middleware-host-header": 3.535.0 + "@aws-sdk/middleware-logger": 3.535.0 + "@aws-sdk/middleware-recursion-detection": 3.535.0 + "@aws-sdk/middleware-user-agent": 3.540.0 + "@aws-sdk/region-config-resolver": 3.535.0 + "@aws-sdk/types": 3.535.0 + "@aws-sdk/util-endpoints": 3.540.0 + "@aws-sdk/util-user-agent-browser": 3.535.0 + "@aws-sdk/util-user-agent-node": 3.535.0 + "@smithy/config-resolver": ^2.2.0 + "@smithy/core": ^1.4.2 + "@smithy/fetch-http-handler": ^2.5.0 + "@smithy/hash-node": ^2.2.0 + "@smithy/invalid-dependency": ^2.2.0 + "@smithy/middleware-content-length": ^2.2.0 + "@smithy/middleware-endpoint": ^2.5.1 + "@smithy/middleware-retry": ^2.3.1 + "@smithy/middleware-serde": ^2.3.0 + "@smithy/middleware-stack": ^2.2.0 + "@smithy/node-config-provider": ^2.3.0 + "@smithy/node-http-handler": ^2.5.0 + "@smithy/protocol-http": ^3.3.0 + "@smithy/smithy-client": ^2.5.1 + "@smithy/types": ^2.12.0 + "@smithy/url-parser": ^2.2.0 + "@smithy/util-base64": ^2.3.0 + "@smithy/util-body-length-browser": ^2.2.0 + "@smithy/util-body-length-node": ^2.3.0 + "@smithy/util-defaults-mode-browser": ^2.2.1 + "@smithy/util-defaults-mode-node": ^2.3.1 + "@smithy/util-endpoints": ^1.2.0 + "@smithy/util-middleware": ^2.2.0 + "@smithy/util-retry": ^2.2.0 + "@smithy/util-utf8": ^2.3.0 + tslib: ^2.6.2 + peerDependencies: + "@aws-sdk/credential-provider-node": ^3.554.0 + checksum: dee932b55e89f9b68275d21861f534ae1cdf006410841fe84bab38df12253ebafd4928e440d6243391c3234f2509b943628c5368d2bcd0489ea240464d65edc6 + languageName: node + linkType: hard + "@aws-sdk/client-sts@npm:3.556.0, @aws-sdk/client-sts@npm:^3.350.0": version: 3.556.0 resolution: "@aws-sdk/client-sts@npm:3.556.0" @@ -841,6 +984,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/core@npm:3.554.0": + version: 3.554.0 + resolution: "@aws-sdk/core@npm:3.554.0" + dependencies: + "@smithy/core": ^1.4.2 + "@smithy/protocol-http": ^3.3.0 + "@smithy/signature-v4": ^2.2.1 + "@smithy/smithy-client": ^2.5.1 + "@smithy/types": ^2.12.0 + fast-xml-parser: 4.2.5 + tslib: ^2.6.2 + checksum: 61e48deee6146aa92fca14f787dfcfe1ef91e0b9feda897d375174643071119aa5b0d51185f9a23843a8aaa82372187af4bb6aa9489e20b1fc651c1734592b40 + languageName: node + linkType: hard + "@aws-sdk/core@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/core@npm:3.556.0" @@ -898,6 +1056,25 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-ini@npm:3.554.0": + version: 3.554.0 + resolution: "@aws-sdk/credential-provider-ini@npm:3.554.0" + dependencies: + "@aws-sdk/client-sts": 3.554.0 + "@aws-sdk/credential-provider-env": 3.535.0 + "@aws-sdk/credential-provider-process": 3.535.0 + "@aws-sdk/credential-provider-sso": 3.554.0 + "@aws-sdk/credential-provider-web-identity": 3.554.0 + "@aws-sdk/types": 3.535.0 + "@smithy/credential-provider-imds": ^2.3.0 + "@smithy/property-provider": ^2.2.0 + "@smithy/shared-ini-file-loader": ^2.4.0 + "@smithy/types": ^2.12.0 + tslib: ^2.6.2 + checksum: 5b93ba3d3cf98cd0738b871db1036303ab0b05538171a650168aefc0071a1e51e4bbd0b88d06047b6170fbf20417069beb29d8f71cae13d87c4e17c736a5a252 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-ini@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/credential-provider-ini@npm:3.556.0" @@ -917,6 +1094,26 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-node@npm:3.554.0": + version: 3.554.0 + resolution: "@aws-sdk/credential-provider-node@npm:3.554.0" + dependencies: + "@aws-sdk/credential-provider-env": 3.535.0 + "@aws-sdk/credential-provider-http": 3.552.0 + "@aws-sdk/credential-provider-ini": 3.554.0 + "@aws-sdk/credential-provider-process": 3.535.0 + "@aws-sdk/credential-provider-sso": 3.554.0 + "@aws-sdk/credential-provider-web-identity": 3.554.0 + "@aws-sdk/types": 3.535.0 + "@smithy/credential-provider-imds": ^2.3.0 + "@smithy/property-provider": ^2.2.0 + "@smithy/shared-ini-file-loader": ^2.4.0 + "@smithy/types": ^2.12.0 + tslib: ^2.6.2 + checksum: b8dfbbace2c279792934057de6879d47fb95b5c068aed0938f5c104b79034982b04e34739dba78274c9fabaf97f6079a9938ff3b01765fc3042b15abbf273190 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-node@npm:3.556.0, @aws-sdk/credential-provider-node@npm:^3.350.0": version: 3.556.0 resolution: "@aws-sdk/credential-provider-node@npm:3.556.0" @@ -950,6 +1147,21 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-sso@npm:3.554.0": + version: 3.554.0 + resolution: "@aws-sdk/credential-provider-sso@npm:3.554.0" + dependencies: + "@aws-sdk/client-sso": 3.554.0 + "@aws-sdk/token-providers": 3.554.0 + "@aws-sdk/types": 3.535.0 + "@smithy/property-provider": ^2.2.0 + "@smithy/shared-ini-file-loader": ^2.4.0 + "@smithy/types": ^2.12.0 + tslib: ^2.6.2 + checksum: 0665748fce2ef6c4572570be35af31a55cee3fa3be49ef7a15be9e25ac54d3528c5a0b82c9672b7e147a9114525aeef7d9bb2a9270ef3d3e09de91d940da3779 + languageName: node + linkType: hard + "@aws-sdk/credential-provider-sso@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/credential-provider-sso@npm:3.556.0" @@ -965,6 +1177,19 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/credential-provider-web-identity@npm:3.554.0": + version: 3.554.0 + resolution: "@aws-sdk/credential-provider-web-identity@npm:3.554.0" + dependencies: + "@aws-sdk/client-sts": 3.554.0 + "@aws-sdk/types": 3.535.0 + "@smithy/property-provider": ^2.2.0 + "@smithy/types": ^2.12.0 + tslib: ^2.6.2 + checksum: e47772ca693bb2a0b02a1bf1b7ce288fd141510e944145d3188f532feffa3f7511f037ed5203c581764ab9d397c358c63baf28f3cf96bbce98c303ed7b9f1a2a + languageName: node + linkType: hard + "@aws-sdk/credential-provider-web-identity@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/credential-provider-web-identity@npm:3.556.0" @@ -1310,6 +1535,20 @@ __metadata: languageName: node linkType: hard +"@aws-sdk/token-providers@npm:3.554.0": + version: 3.554.0 + resolution: "@aws-sdk/token-providers@npm:3.554.0" + dependencies: + "@aws-sdk/client-sso-oidc": 3.554.0 + "@aws-sdk/types": 3.535.0 + "@smithy/property-provider": ^2.2.0 + "@smithy/shared-ini-file-loader": ^2.4.0 + "@smithy/types": ^2.12.0 + tslib: ^2.6.2 + checksum: ff6e13ab91ac9d5e0f7de8673a16b541b41dc445c7ec3dae66d85e47514c808ed059af4702156b437c4d2a510d378a83662ee988a853cdf2625cde886fe962cb + languageName: node + linkType: hard + "@aws-sdk/token-providers@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/token-providers@npm:3.556.0" @@ -6035,8 +6274,8 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-notifications-backend-module-email@workspace:plugins/notifications-backend-module-email" dependencies: - "@aws-sdk/client-ses": ^3.556.0 - "@aws-sdk/credential-provider-node": ^3.350.0 + "@aws-sdk/client-ses": 3.554.0 + "@aws-sdk/types": ^3.347.0 "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" @@ -6044,6 +6283,7 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/integration-aws-node": "workspace:^" "@backstage/plugin-notifications-common": "workspace:^" "@backstage/plugin-notifications-node": "workspace:^" "@backstage/types": "workspace:^" @@ -13553,6 +13793,21 @@ __metadata: languageName: node linkType: hard +"@smithy/signature-v4@npm:^2.2.1": + version: 2.2.1 + resolution: "@smithy/signature-v4@npm:2.2.1" + dependencies: + "@smithy/is-array-buffer": ^2.2.0 + "@smithy/types": ^2.12.0 + "@smithy/util-hex-encoding": ^2.2.0 + "@smithy/util-middleware": ^2.2.0 + "@smithy/util-uri-escape": ^2.2.0 + "@smithy/util-utf8": ^2.3.0 + tslib: ^2.6.2 + checksum: 8e44f2acfff8dd690396d93a86655291b344f35ed9f82755433982cf045c69513298935e54aa3a25354d33a167ba4fdf27067486003261a1ba9edcf58f6f9bef + languageName: node + linkType: hard + "@smithy/signature-v4@npm:^2.3.0": version: 2.3.0 resolution: "@smithy/signature-v4@npm:2.3.0" From 29e16abc8592927ce93812680599152ced43dc62 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Wed, 17 Apr 2024 18:59:13 +0300 Subject: [PATCH 113/234] fix: add missing region config for ses Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- plugins/notifications-backend-module-email/config.d.ts | 4 ++++ .../src/processor/NotificationsEmailProcessor.ts | 1 + .../src/processor/transports/ses.ts | 3 +-- plugins/notifications-backend-module-email/src/types.ts | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index fff78f735f..3d85022193 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -61,6 +61,10 @@ export interface Config { * AWS account ID to use */ accountId?: string; + /** + * AWS region to use + */ + region?: string; } | { transport: 'sendmail'; diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index 8150aeebe6..4a190a8bb7 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -88,6 +88,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { credentialsManager: awsCredentialsManager, apiVersion: transportConfig.getOptionalString('apiVersion'), accountId: transportConfig.getOptionalString('accountId'), + region: transportConfig.getOptionalString('region'), }); } else if (transport === 'sendmail') { this.transporter = createSendmailTransport({ diff --git a/plugins/notifications-backend-module-email/src/processor/transports/ses.ts b/plugins/notifications-backend-module-email/src/processor/transports/ses.ts index 7073cc08b2..d208001bad 100644 --- a/plugins/notifications-backend-module-email/src/processor/transports/ses.ts +++ b/plugins/notifications-backend-module-email/src/processor/transports/ses.ts @@ -25,8 +25,7 @@ export const createSesTransport = async (config: SesTransportConfig) => { { apiVersion: config.apiVersion ?? '2010-12-01', credentials: credentials.sdkCredentialProvider, - region: credentials.stsRegion, - accountId: credentials.accountId, + region: config.region, }, ]); return createTransport({ diff --git a/plugins/notifications-backend-module-email/src/types.ts b/plugins/notifications-backend-module-email/src/types.ts index 67c7a1740d..57c4b05234 100644 --- a/plugins/notifications-backend-module-email/src/types.ts +++ b/plugins/notifications-backend-module-email/src/types.ts @@ -55,6 +55,7 @@ export interface SesTransportConfig extends TransportConfig { */ apiVersion?: string; accountId?: string; + region?: string; credentialsManager: AwsCredentialsManager; } From 9f9dbdbdeeae2d1a6d501b273b96e077de9b778e Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Thu, 18 Apr 2024 08:21:32 +0300 Subject: [PATCH 114/234] chore: make cache config follow slack notification config Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- plugins/notifications-backend-module-email/README.md | 4 +++- plugins/notifications-backend-module-email/config.d.ts | 10 ++++++---- .../src/processor/NotificationsEmailProcessor.ts | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/notifications-backend-module-email/README.md b/plugins/notifications-backend-module-email/README.md index f87ce8c18e..1bdfd59c01 100644 --- a/plugins/notifications-backend-module-email/README.md +++ b/plugins/notifications-backend-module-email/README.md @@ -20,7 +20,9 @@ notifications: sender: 'sender@mycompany.com' replyTo: 'no-reply@mycompany.com' broadcastConfig: - receiver: 'user' + receiver: 'users' + cache: + ttl: 60000 ``` See `config.d.ts` for more options for configuration. diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index 3d85022193..ec92bb8813 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -105,10 +105,12 @@ export interface Config { */ receiverEmails?: string[]; }; - /** - * Email cache TTL, defaults to 1 hour - */ - cacheTtl?: number; + cache?: { + /** + * Email cache TTL, defaults to 1 hour + */ + ttl?: number; + }; }; }; }; diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index 4a190a8bb7..f9e9ac7bd3 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -58,7 +58,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { config.getOptionalString('notifications.email.format') ?? 'html'; this.replyTo = config.getOptionalString('notifications.email.replyTo'); this.cacheTtl = - config.getOptionalNumber('notifications.email.cacheTtl') ?? 3_600_000; + config.getOptionalNumber('notifications.email.cache.ttl') ?? 3_600_000; } private async getTransporter() { From c137035b83205e8fc8a59ec125458db0093e2522 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Thu, 18 Apr 2024 08:49:50 +0300 Subject: [PATCH 115/234] feat: add support for templating the email content Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .../README.md | 36 +++ .../api-report.md | 21 ++ .../config.d.ts | 4 - .../package.json | 2 +- .../src/extensions.ts | 41 +++ .../src/index.ts | 1 + .../src/module.ts | 15 + .../NotificationsEmailProcessor.test.ts | 6 +- .../processor/NotificationsEmailProcessor.ts | 83 ++++-- yarn.lock | 270 +----------------- 10 files changed, 181 insertions(+), 298 deletions(-) create mode 100644 plugins/notifications-backend-module-email/src/extensions.ts diff --git a/plugins/notifications-backend-module-email/README.md b/plugins/notifications-backend-module-email/README.md index 1bdfd59c01..008174e5d3 100644 --- a/plugins/notifications-backend-module-email/README.md +++ b/plugins/notifications-backend-module-email/README.md @@ -4,6 +4,42 @@ Adds support for sending Backstage notifications as emails to users. Supports sending emails using SMTP, SES, or sendmail. +## Customizing email content + +The email content can be customized with the `notificationsEmailTemplateExtensionPoint`. When you create +this extension, you can set the custom `NotificationTemplateRenderer` to the module. To modify the contents, +override the `getSubject`, `getHtml` and `getText` methods. + +```ts +import { notificationsEmailTemplateExtensionPoint } from '@backstage/plugin-notifications-backend-module-email'; +import { Notification } from '@backstage/plugin-notifications-common'; + +export const notificationsModuleEmailDecorator = createBackendModule({ + pluginId: 'notifications', + moduleId: 'email.templates', + register(reg) { + reg.registerInit({ + deps: { + emailTemplates: notificationsEmailTemplateExtensionPoint, + }, + async init({ emailTemplates }) { + emailTemplates.setTemplateRenderer({ + getSubject(notification) { + return `New notification from ${notification.source}`; + }, + getText(notification) { + return notification.content; + }, + getHtml(notification) { + return `<p>${notification.content}</p>`; + }, + }); + }, + }); + }, +}); +``` + ## Example configuration: ```yaml diff --git a/plugins/notifications-backend-module-email/api-report.md b/plugins/notifications-backend-module-email/api-report.md index 3f7c9246f5..26563bde85 100644 --- a/plugins/notifications-backend-module-email/api-report.md +++ b/plugins/notifications-backend-module-email/api-report.md @@ -4,8 +4,29 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; +import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { Notification as Notification_2 } from '@backstage/plugin-notifications-common'; + +// @public (undocumented) +export interface NotificationsEmailTemplateExtensionPoint { + // (undocumented) + setTemplateRenderer(renderer: NotificationTemplateRenderer): void; +} + +// @public (undocumented) +export const notificationsEmailTemplateExtensionPoint: ExtensionPoint<NotificationsEmailTemplateExtensionPoint>; // @public (undocumented) const notificationsModuleEmail: () => BackendFeature; export default notificationsModuleEmail; + +// @public (undocumented) +export interface NotificationTemplateRenderer { + // (undocumented) + getHtml?(notification: Notification_2): string; + // (undocumented) + getSubject?(notification: Notification_2): string; + // (undocumented) + getText?(notification: Notification_2): string; +} ``` diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index ec92bb8813..54de2f6ab4 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -81,10 +81,6 @@ export interface Config { * Sender email address */ sender: string; - /** - * Email format, defaults to HTML - */ - format?: 'html' | 'text'; /** * Optional reply-to address */ diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 345ba162b6..29cc9d7c24 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -32,7 +32,7 @@ "test": "backstage-cli package test" }, "dependencies": { - "@aws-sdk/client-ses": "3.554.0", + "@aws-sdk/client-ses": "^3.550.0", "@aws-sdk/types": "^3.347.0", "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", diff --git a/plugins/notifications-backend-module-email/src/extensions.ts b/plugins/notifications-backend-module-email/src/extensions.ts new file mode 100644 index 0000000000..454fe9b384 --- /dev/null +++ b/plugins/notifications-backend-module-email/src/extensions.ts @@ -0,0 +1,41 @@ +/* + * 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 { createExtensionPoint } from '@backstage/backend-plugin-api'; +import { Notification } from '@backstage/plugin-notifications-common'; + +/** + * @public + */ +export interface NotificationTemplateRenderer { + getSubject?(notification: Notification): string; + getText?(notification: Notification): string; + getHtml?(notification: Notification): string; +} + +/** + * @public + */ +export interface NotificationsEmailTemplateExtensionPoint { + setTemplateRenderer(renderer: NotificationTemplateRenderer): void; +} + +/** + * @public + */ +export const notificationsEmailTemplateExtensionPoint = + createExtensionPoint<NotificationsEmailTemplateExtensionPoint>({ + id: 'notifications.email.templates', + }); diff --git a/plugins/notifications-backend-module-email/src/index.ts b/plugins/notifications-backend-module-email/src/index.ts index 816d5d3579..547a69360c 100644 --- a/plugins/notifications-backend-module-email/src/index.ts +++ b/plugins/notifications-backend-module-email/src/index.ts @@ -21,3 +21,4 @@ */ export { notificationsModuleEmail as default } from './module'; +export * from './extensions'; diff --git a/plugins/notifications-backend-module-email/src/module.ts b/plugins/notifications-backend-module-email/src/module.ts index 9c8dfabd8c..6b74bf70e7 100644 --- a/plugins/notifications-backend-module-email/src/module.ts +++ b/plugins/notifications-backend-module-email/src/module.ts @@ -20,6 +20,10 @@ import { import { CatalogClient } from '@backstage/catalog-client'; import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; import { NotificationsEmailProcessor } from './processor'; +import { + notificationsEmailTemplateExtensionPoint, + NotificationTemplateRenderer, +} from './extensions'; /** * @public @@ -28,6 +32,16 @@ export const notificationsModuleEmail = createBackendModule({ pluginId: 'notifications', moduleId: 'email', register(reg) { + let templateRenderer: NotificationTemplateRenderer | undefined; + reg.registerExtensionPoint(notificationsEmailTemplateExtensionPoint, { + setTemplateRenderer(renderer) { + if (templateRenderer) { + throw new Error(`Email template renderer was already registered`); + } + templateRenderer = renderer; + }, + }); + reg.registerInit({ deps: { config: coreServices.rootConfig, @@ -49,6 +63,7 @@ export const notificationsModuleEmail = createBackendModule({ catalogClient, auth, cache, + templateRenderer, ), ); }, diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index 72d1113eb1..f4db00869e 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -213,7 +213,7 @@ describe('NotificationsEmailProcessor', () => { html: '<p></p>', replyTo: undefined, subject: 'notification', - text: undefined, + text: '', to: 'mock@backstage.io', }); }); @@ -271,7 +271,7 @@ describe('NotificationsEmailProcessor', () => { html: '<p></p>', replyTo: undefined, subject: 'notification', - text: undefined, + text: '', to: 'mock@backstage.io', }); }); @@ -330,7 +330,7 @@ describe('NotificationsEmailProcessor', () => { html: '<p></p>', replyTo: undefined, subject: 'notification', - text: undefined, + text: '', to: 'broadcast@backstage.io', }); }); diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index f9e9ac7bd3..296cf83657 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -34,12 +34,12 @@ import { createSesTransport } from './transports/ses'; import { UserEntity } from '@backstage/catalog-model'; import { compact } from 'lodash'; import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node'; +import { NotificationTemplateRenderer } from '../extensions'; export class NotificationsEmailProcessor implements NotificationProcessor { private transporter: any; private readonly broadcastConfig?: Config; private readonly sender: string; - private readonly format: string; private readonly replyTo?: string; private readonly cacheTtl: number; @@ -49,13 +49,12 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly catalog: CatalogClient, private readonly auth: AuthService, private readonly cache?: CacheService, + private readonly templateRenderer?: NotificationTemplateRenderer, ) { this.broadcastConfig = config.getOptionalConfig( 'notifications.email.broadcastConfig', ); this.sender = config.getString('notifications.email.sender'); - this.format = - config.getOptionalString('notifications.email.format') ?? 'html'; this.replyTo = config.getOptionalString('notifications.email.replyTo'); this.cacheTtl = config.getOptionalNumber('notifications.email.cache.ttl') ?? 3_600_000; @@ -198,6 +197,55 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return await this.getUserEmail(notification.user); } + private async sendPlainEmail(notification: Notification, emails: string[]) { + const contentParts: string[] = []; + if (notification.payload.description) { + contentParts.push(`${notification.payload.description}`); + } + if (notification.payload.link) { + contentParts.push(`${notification.payload.link}`); + } + + const mailOptions = { + from: this.sender, + subject: notification.payload.title, + html: `<p>${contentParts.join('<br/>')}</p>`, + text: contentParts.join('\n\n'), + replyTo: this.replyTo, + }; + + for (const email of emails) { + try { + await this.transporter.sendMail({ ...mailOptions, to: email }); + } catch (e) { + this.logger.error(`Failed to send email to ${email}: ${e}`); + } + } + } + + private async sendTemplateEmail( + notification: Notification, + emails: string[], + ) { + const mailOptions = { + from: this.sender, + subject: + this.templateRenderer?.getSubject?.(notification) ?? + notification.payload.title, + html: this.templateRenderer?.getHtml?.(notification), + text: this.templateRenderer?.getText?.(notification), + replyTo: this.replyTo, + }; + + for (const email of emails) { + try { + await this.transporter.sendMail({ ...mailOptions, to: email }); + } catch (e) { + this.logger.error(`Failed to send email to ${email}: ${e}`); + } + } + } + async postProcess( notification: Notification, options: NotificationSendOptions, @@ -219,32 +267,11 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return; } - // TODO: add template support for content either HTML or text - const contentParts: string[] = []; - if (notification.payload.description) { - contentParts.push(`${notification.payload.description}`); - } - if (notification.payload.link) { - contentParts.push(`${notification.payload.link}`); + if (!this.templateRenderer) { + await this.sendPlainEmail(notification, emails); + return; } - const mailOptions = { - from: this.sender, - subject: notification.payload.title, - html: - this.format === 'html' - ? `<p>${contentParts.join('<br/>')}</p>` - : undefined, - text: this.format === 'text' ? contentParts.join('\n\n') : undefined, - replyTo: this.replyTo, - }; - - for (const email of emails) { - try { - await this.transporter.sendMail({ ...mailOptions, to: email }); - } catch (e) { - this.logger.error(`Failed to send email to ${email}: ${e}`); - } - } + await this.sendTemplateEmail(notification, emails); } } diff --git a/yarn.lock b/yarn.lock index 94ba875fae..40554ed539 100644 --- a/yarn.lock +++ b/yarn.lock @@ -599,15 +599,15 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-ses@npm:3.554.0": - version: 3.554.0 - resolution: "@aws-sdk/client-ses@npm:3.554.0" +"@aws-sdk/client-ses@npm:^3.550.0": + version: 3.556.0 + resolution: "@aws-sdk/client-ses@npm:3.556.0" dependencies: "@aws-crypto/sha256-browser": 3.0.0 "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/client-sts": 3.554.0 - "@aws-sdk/core": 3.554.0 - "@aws-sdk/credential-provider-node": 3.554.0 + "@aws-sdk/client-sts": 3.556.0 + "@aws-sdk/core": 3.556.0 + "@aws-sdk/credential-provider-node": 3.556.0 "@aws-sdk/middleware-host-header": 3.535.0 "@aws-sdk/middleware-logger": 3.535.0 "@aws-sdk/middleware-recursion-detection": 3.535.0 @@ -644,7 +644,7 @@ __metadata: "@smithy/util-utf8": ^2.3.0 "@smithy/util-waiter": ^2.2.0 tslib: ^2.6.2 - checksum: 14512aac8bd84a69066535580bd739509a5698a64be78b111fd9656f4a01f4921c016b0c22c8b103261370d9e75ba3c6301cc652b47e396e421b8b02f93334ce + checksum: 913108e79061185faae51711b121df99da624f988f530af23c3e4270299be60771be83f507dc8ed4af4051765077ea038edab19e4d0d32e159c4e67faf5fc9f8 languageName: node linkType: hard @@ -698,55 +698,6 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-sso-oidc@npm:3.554.0": - version: 3.554.0 - resolution: "@aws-sdk/client-sso-oidc@npm:3.554.0" - dependencies: - "@aws-crypto/sha256-browser": 3.0.0 - "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/client-sts": 3.554.0 - "@aws-sdk/core": 3.554.0 - "@aws-sdk/middleware-host-header": 3.535.0 - "@aws-sdk/middleware-logger": 3.535.0 - "@aws-sdk/middleware-recursion-detection": 3.535.0 - "@aws-sdk/middleware-user-agent": 3.540.0 - "@aws-sdk/region-config-resolver": 3.535.0 - "@aws-sdk/types": 3.535.0 - "@aws-sdk/util-endpoints": 3.540.0 - "@aws-sdk/util-user-agent-browser": 3.535.0 - "@aws-sdk/util-user-agent-node": 3.535.0 - "@smithy/config-resolver": ^2.2.0 - "@smithy/core": ^1.4.2 - "@smithy/fetch-http-handler": ^2.5.0 - "@smithy/hash-node": ^2.2.0 - "@smithy/invalid-dependency": ^2.2.0 - "@smithy/middleware-content-length": ^2.2.0 - "@smithy/middleware-endpoint": ^2.5.1 - "@smithy/middleware-retry": ^2.3.1 - "@smithy/middleware-serde": ^2.3.0 - "@smithy/middleware-stack": ^2.2.0 - "@smithy/node-config-provider": ^2.3.0 - "@smithy/node-http-handler": ^2.5.0 - "@smithy/protocol-http": ^3.3.0 - "@smithy/smithy-client": ^2.5.1 - "@smithy/types": ^2.12.0 - "@smithy/url-parser": ^2.2.0 - "@smithy/util-base64": ^2.3.0 - "@smithy/util-body-length-browser": ^2.2.0 - "@smithy/util-body-length-node": ^2.3.0 - "@smithy/util-defaults-mode-browser": ^2.2.1 - "@smithy/util-defaults-mode-node": ^2.3.1 - "@smithy/util-endpoints": ^1.2.0 - "@smithy/util-middleware": ^2.2.0 - "@smithy/util-retry": ^2.2.0 - "@smithy/util-utf8": ^2.3.0 - tslib: ^2.6.2 - peerDependencies: - "@aws-sdk/credential-provider-node": ^3.554.0 - checksum: 91d0f6b8c5c787e2b705803d615f9400430ea0bca9d12171a5e7abe5c53cd83f711300e5c0a106dc4f1f1259adf08f87b728f9820679aa5ca73cd66a09bf8dd4 - languageName: node - linkType: hard - "@aws-sdk/client-sso-oidc@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/client-sso-oidc@npm:3.556.0" @@ -796,52 +747,6 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-sso@npm:3.554.0": - version: 3.554.0 - resolution: "@aws-sdk/client-sso@npm:3.554.0" - dependencies: - "@aws-crypto/sha256-browser": 3.0.0 - "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/core": 3.554.0 - "@aws-sdk/middleware-host-header": 3.535.0 - "@aws-sdk/middleware-logger": 3.535.0 - "@aws-sdk/middleware-recursion-detection": 3.535.0 - "@aws-sdk/middleware-user-agent": 3.540.0 - "@aws-sdk/region-config-resolver": 3.535.0 - "@aws-sdk/types": 3.535.0 - "@aws-sdk/util-endpoints": 3.540.0 - "@aws-sdk/util-user-agent-browser": 3.535.0 - "@aws-sdk/util-user-agent-node": 3.535.0 - "@smithy/config-resolver": ^2.2.0 - "@smithy/core": ^1.4.2 - "@smithy/fetch-http-handler": ^2.5.0 - "@smithy/hash-node": ^2.2.0 - "@smithy/invalid-dependency": ^2.2.0 - "@smithy/middleware-content-length": ^2.2.0 - "@smithy/middleware-endpoint": ^2.5.1 - "@smithy/middleware-retry": ^2.3.1 - "@smithy/middleware-serde": ^2.3.0 - "@smithy/middleware-stack": ^2.2.0 - "@smithy/node-config-provider": ^2.3.0 - "@smithy/node-http-handler": ^2.5.0 - "@smithy/protocol-http": ^3.3.0 - "@smithy/smithy-client": ^2.5.1 - "@smithy/types": ^2.12.0 - "@smithy/url-parser": ^2.2.0 - "@smithy/util-base64": ^2.3.0 - "@smithy/util-body-length-browser": ^2.2.0 - "@smithy/util-body-length-node": ^2.3.0 - "@smithy/util-defaults-mode-browser": ^2.2.1 - "@smithy/util-defaults-mode-node": ^2.3.1 - "@smithy/util-endpoints": ^1.2.0 - "@smithy/util-middleware": ^2.2.0 - "@smithy/util-retry": ^2.2.0 - "@smithy/util-utf8": ^2.3.0 - tslib: ^2.6.2 - checksum: 08dbaca87bb25d7662a2e856dca16f92bc461b9cf50e048c679969df93189f653c2bc74c8be1ac3e82e720d2742fc81af554a04f0ebf83353c1fa6e30fdbbecf - languageName: node - linkType: hard - "@aws-sdk/client-sso@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/client-sso@npm:3.556.0" @@ -888,54 +793,6 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/client-sts@npm:3.554.0": - version: 3.554.0 - resolution: "@aws-sdk/client-sts@npm:3.554.0" - dependencies: - "@aws-crypto/sha256-browser": 3.0.0 - "@aws-crypto/sha256-js": 3.0.0 - "@aws-sdk/core": 3.554.0 - "@aws-sdk/middleware-host-header": 3.535.0 - "@aws-sdk/middleware-logger": 3.535.0 - "@aws-sdk/middleware-recursion-detection": 3.535.0 - "@aws-sdk/middleware-user-agent": 3.540.0 - "@aws-sdk/region-config-resolver": 3.535.0 - "@aws-sdk/types": 3.535.0 - "@aws-sdk/util-endpoints": 3.540.0 - "@aws-sdk/util-user-agent-browser": 3.535.0 - "@aws-sdk/util-user-agent-node": 3.535.0 - "@smithy/config-resolver": ^2.2.0 - "@smithy/core": ^1.4.2 - "@smithy/fetch-http-handler": ^2.5.0 - "@smithy/hash-node": ^2.2.0 - "@smithy/invalid-dependency": ^2.2.0 - "@smithy/middleware-content-length": ^2.2.0 - "@smithy/middleware-endpoint": ^2.5.1 - "@smithy/middleware-retry": ^2.3.1 - "@smithy/middleware-serde": ^2.3.0 - "@smithy/middleware-stack": ^2.2.0 - "@smithy/node-config-provider": ^2.3.0 - "@smithy/node-http-handler": ^2.5.0 - "@smithy/protocol-http": ^3.3.0 - "@smithy/smithy-client": ^2.5.1 - "@smithy/types": ^2.12.0 - "@smithy/url-parser": ^2.2.0 - "@smithy/util-base64": ^2.3.0 - "@smithy/util-body-length-browser": ^2.2.0 - "@smithy/util-body-length-node": ^2.3.0 - "@smithy/util-defaults-mode-browser": ^2.2.1 - "@smithy/util-defaults-mode-node": ^2.3.1 - "@smithy/util-endpoints": ^1.2.0 - "@smithy/util-middleware": ^2.2.0 - "@smithy/util-retry": ^2.2.0 - "@smithy/util-utf8": ^2.3.0 - tslib: ^2.6.2 - peerDependencies: - "@aws-sdk/credential-provider-node": ^3.554.0 - checksum: dee932b55e89f9b68275d21861f534ae1cdf006410841fe84bab38df12253ebafd4928e440d6243391c3234f2509b943628c5368d2bcd0489ea240464d65edc6 - languageName: node - linkType: hard - "@aws-sdk/client-sts@npm:3.556.0, @aws-sdk/client-sts@npm:^3.350.0": version: 3.556.0 resolution: "@aws-sdk/client-sts@npm:3.556.0" @@ -984,21 +841,6 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/core@npm:3.554.0": - version: 3.554.0 - resolution: "@aws-sdk/core@npm:3.554.0" - dependencies: - "@smithy/core": ^1.4.2 - "@smithy/protocol-http": ^3.3.0 - "@smithy/signature-v4": ^2.2.1 - "@smithy/smithy-client": ^2.5.1 - "@smithy/types": ^2.12.0 - fast-xml-parser: 4.2.5 - tslib: ^2.6.2 - checksum: 61e48deee6146aa92fca14f787dfcfe1ef91e0b9feda897d375174643071119aa5b0d51185f9a23843a8aaa82372187af4bb6aa9489e20b1fc651c1734592b40 - languageName: node - linkType: hard - "@aws-sdk/core@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/core@npm:3.556.0" @@ -1056,25 +898,6 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-ini@npm:3.554.0": - version: 3.554.0 - resolution: "@aws-sdk/credential-provider-ini@npm:3.554.0" - dependencies: - "@aws-sdk/client-sts": 3.554.0 - "@aws-sdk/credential-provider-env": 3.535.0 - "@aws-sdk/credential-provider-process": 3.535.0 - "@aws-sdk/credential-provider-sso": 3.554.0 - "@aws-sdk/credential-provider-web-identity": 3.554.0 - "@aws-sdk/types": 3.535.0 - "@smithy/credential-provider-imds": ^2.3.0 - "@smithy/property-provider": ^2.2.0 - "@smithy/shared-ini-file-loader": ^2.4.0 - "@smithy/types": ^2.12.0 - tslib: ^2.6.2 - checksum: 5b93ba3d3cf98cd0738b871db1036303ab0b05538171a650168aefc0071a1e51e4bbd0b88d06047b6170fbf20417069beb29d8f71cae13d87c4e17c736a5a252 - languageName: node - linkType: hard - "@aws-sdk/credential-provider-ini@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/credential-provider-ini@npm:3.556.0" @@ -1094,26 +917,6 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-node@npm:3.554.0": - version: 3.554.0 - resolution: "@aws-sdk/credential-provider-node@npm:3.554.0" - dependencies: - "@aws-sdk/credential-provider-env": 3.535.0 - "@aws-sdk/credential-provider-http": 3.552.0 - "@aws-sdk/credential-provider-ini": 3.554.0 - "@aws-sdk/credential-provider-process": 3.535.0 - "@aws-sdk/credential-provider-sso": 3.554.0 - "@aws-sdk/credential-provider-web-identity": 3.554.0 - "@aws-sdk/types": 3.535.0 - "@smithy/credential-provider-imds": ^2.3.0 - "@smithy/property-provider": ^2.2.0 - "@smithy/shared-ini-file-loader": ^2.4.0 - "@smithy/types": ^2.12.0 - tslib: ^2.6.2 - checksum: b8dfbbace2c279792934057de6879d47fb95b5c068aed0938f5c104b79034982b04e34739dba78274c9fabaf97f6079a9938ff3b01765fc3042b15abbf273190 - languageName: node - linkType: hard - "@aws-sdk/credential-provider-node@npm:3.556.0, @aws-sdk/credential-provider-node@npm:^3.350.0": version: 3.556.0 resolution: "@aws-sdk/credential-provider-node@npm:3.556.0" @@ -1147,21 +950,6 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-sso@npm:3.554.0": - version: 3.554.0 - resolution: "@aws-sdk/credential-provider-sso@npm:3.554.0" - dependencies: - "@aws-sdk/client-sso": 3.554.0 - "@aws-sdk/token-providers": 3.554.0 - "@aws-sdk/types": 3.535.0 - "@smithy/property-provider": ^2.2.0 - "@smithy/shared-ini-file-loader": ^2.4.0 - "@smithy/types": ^2.12.0 - tslib: ^2.6.2 - checksum: 0665748fce2ef6c4572570be35af31a55cee3fa3be49ef7a15be9e25ac54d3528c5a0b82c9672b7e147a9114525aeef7d9bb2a9270ef3d3e09de91d940da3779 - languageName: node - linkType: hard - "@aws-sdk/credential-provider-sso@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/credential-provider-sso@npm:3.556.0" @@ -1177,19 +965,6 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/credential-provider-web-identity@npm:3.554.0": - version: 3.554.0 - resolution: "@aws-sdk/credential-provider-web-identity@npm:3.554.0" - dependencies: - "@aws-sdk/client-sts": 3.554.0 - "@aws-sdk/types": 3.535.0 - "@smithy/property-provider": ^2.2.0 - "@smithy/types": ^2.12.0 - tslib: ^2.6.2 - checksum: e47772ca693bb2a0b02a1bf1b7ce288fd141510e944145d3188f532feffa3f7511f037ed5203c581764ab9d397c358c63baf28f3cf96bbce98c303ed7b9f1a2a - languageName: node - linkType: hard - "@aws-sdk/credential-provider-web-identity@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/credential-provider-web-identity@npm:3.556.0" @@ -1535,20 +1310,6 @@ __metadata: languageName: node linkType: hard -"@aws-sdk/token-providers@npm:3.554.0": - version: 3.554.0 - resolution: "@aws-sdk/token-providers@npm:3.554.0" - dependencies: - "@aws-sdk/client-sso-oidc": 3.554.0 - "@aws-sdk/types": 3.535.0 - "@smithy/property-provider": ^2.2.0 - "@smithy/shared-ini-file-loader": ^2.4.0 - "@smithy/types": ^2.12.0 - tslib: ^2.6.2 - checksum: ff6e13ab91ac9d5e0f7de8673a16b541b41dc445c7ec3dae66d85e47514c808ed059af4702156b437c4d2a510d378a83662ee988a853cdf2625cde886fe962cb - languageName: node - linkType: hard - "@aws-sdk/token-providers@npm:3.556.0": version: 3.556.0 resolution: "@aws-sdk/token-providers@npm:3.556.0" @@ -6274,7 +6035,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-notifications-backend-module-email@workspace:plugins/notifications-backend-module-email" dependencies: - "@aws-sdk/client-ses": 3.554.0 + "@aws-sdk/client-ses": ^3.550.0 "@aws-sdk/types": ^3.347.0 "@backstage/backend-common": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" @@ -13793,21 +13554,6 @@ __metadata: languageName: node linkType: hard -"@smithy/signature-v4@npm:^2.2.1": - version: 2.2.1 - resolution: "@smithy/signature-v4@npm:2.2.1" - dependencies: - "@smithy/is-array-buffer": ^2.2.0 - "@smithy/types": ^2.12.0 - "@smithy/util-hex-encoding": ^2.2.0 - "@smithy/util-middleware": ^2.2.0 - "@smithy/util-uri-escape": ^2.2.0 - "@smithy/util-utf8": ^2.3.0 - tslib: ^2.6.2 - checksum: 8e44f2acfff8dd690396d93a86655291b344f35ed9f82755433982cf045c69513298935e54aa3a25354d33a167ba4fdf27067486003261a1ba9edcf58f6f9bef - languageName: node - linkType: hard - "@smithy/signature-v4@npm:^2.3.0": version: 2.3.0 resolution: "@smithy/signature-v4@npm:2.3.0" From 4e4e5607e2d544b3f951cef5095028fc719302bf Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Wed, 24 Apr 2024 09:18:35 +0300 Subject: [PATCH 116/234] fix: review findings for email notification processor Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .../README.md | 12 +- .../config.d.ts | 8 +- .../package.json | 3 +- .../NotificationsEmailProcessor.test.ts | 92 ++++++------ .../processor/NotificationsEmailProcessor.ts | 133 +++++++++--------- .../src/processor/transports/sendmail.ts | 8 +- .../src/processor/transports/ses.ts | 16 ++- .../src/processor/transports/smtp.ts | 20 +-- .../src/types.ts | 72 ---------- yarn.lock | 1 + 10 files changed, 161 insertions(+), 204 deletions(-) delete mode 100644 plugins/notifications-backend-module-email/src/types.ts diff --git a/plugins/notifications-backend-module-email/README.md b/plugins/notifications-backend-module-email/README.md index 008174e5d3..7bbac05b62 100644 --- a/plugins/notifications-backend-module-email/README.md +++ b/plugins/notifications-backend-module-email/README.md @@ -2,7 +2,7 @@ Adds support for sending Backstage notifications as emails to users. -Supports sending emails using SMTP, SES, or sendmail. +Supports sending emails using `SMTP`, `SES`, or `sendmail`. ## Customizing email content @@ -46,6 +46,7 @@ export const notificationsModuleEmailDecorator = createBackendModule({ notifications: processors: email: + # Transport config, see options at `config.d.ts` transportConfig: transport: 'smtp' hostname: 'my-smtp-server' @@ -53,12 +54,19 @@ notifications: secure: false username: 'my-username' password: 'my-password' + # The email sender address sender: 'sender@mycompany.com' replyTo: 'no-reply@mycompany.com' + # Who to get email for broadcast notifications broadcastConfig: receiver: 'users' + # How many emails to send concurrently, defaults to 2 + concurrencyLimit: 10 + # Cache configuration for email addresses + # This is to prevent unnecessary calls to the catalog cache: - ttl: 60000 + ttl: + days: 1 ``` See `config.d.ts` for more options for configuration. diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index 54de2f6ab4..90fb43e046 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { HumanDuration } from '@backstage/types'; + export interface Config { /** * Configuration options for notifications-backend-module-email */ @@ -85,6 +87,10 @@ export interface Config { * Optional reply-to address */ replyTo?: string; + /** + * Concurrency limit for email sending, defaults to 2 + */ + concurrencyLimit?: number; /** * Configuration for broadcast notifications */ @@ -105,7 +111,7 @@ export interface Config { /** * Email cache TTL, defaults to 1 hour */ - ttl?: number; + ttl?: HumanDuration; }; }; }; diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 29cc9d7c24..3d44db6a45 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -44,7 +44,8 @@ "@backstage/plugin-notifications-node": "workspace:^", "@backstage/types": "workspace:^", "lodash": "^4.17.21", - "nodemailer": "^6.9.13" + "nodemailer": "^6.9.13", + "p-limit": "^3.1.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index f4db00869e..3af1e498ca 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -50,15 +50,17 @@ describe('NotificationsEmailProcessor', () => { logger, new ConfigReader({ notifications: { - email: { - transport: { - transport: 'smtp', - hostname: 'localhost', - port: 465, - secure: true, - requireTls: false, + processors: { + email: { + transport: { + transport: 'smtp', + hostname: 'localhost', + port: 465, + secure: true, + requireTls: false, + }, + sender: 'backstage@backstage.io', }, - sender: 'backstage@backstage.io', }, }, }), @@ -94,12 +96,14 @@ describe('NotificationsEmailProcessor', () => { logger, new ConfigReader({ notifications: { - email: { - transport: { - transport: 'ses', - region: 'us-west-2', + processors: { + email: { + transport: { + transport: 'ses', + region: 'us-west-2', + }, + sender: 'backstage@backstage.io', }, - sender: 'backstage@backstage.io', }, }, }), @@ -132,12 +136,14 @@ describe('NotificationsEmailProcessor', () => { logger, new ConfigReader({ notifications: { - email: { - transport: { - transport: 'sendmail', - path: '/usr/local/bin/sendmail', + processors: { + email: { + transport: { + transport: 'sendmail', + path: '/usr/local/bin/sendmail', + }, + sender: 'backstage@backstage.io', }, - sender: 'backstage@backstage.io', }, }, }), @@ -181,12 +187,14 @@ describe('NotificationsEmailProcessor', () => { logger, new ConfigReader({ notifications: { - email: { - transport: { - transport: 'sendmail', - path: '/usr/local/bin/sendmail', + processors: { + email: { + transport: { + transport: 'sendmail', + path: '/usr/local/bin/sendmail', + }, + sender: 'backstage@backstage.io', }, - sender: 'backstage@backstage.io', }, }, }), @@ -236,14 +244,16 @@ describe('NotificationsEmailProcessor', () => { logger, new ConfigReader({ notifications: { - email: { - transport: { - transport: 'sendmail', - path: '/usr/local/bin/sendmail', - }, - sender: 'backstage@backstage.io', - broadcastConfig: { - receiver: 'users', + processors: { + email: { + transport: { + transport: 'sendmail', + path: '/usr/local/bin/sendmail', + }, + sender: 'backstage@backstage.io', + broadcastConfig: { + receiver: 'users', + }, }, }, }, @@ -294,15 +304,17 @@ describe('NotificationsEmailProcessor', () => { logger, new ConfigReader({ notifications: { - email: { - transport: { - transport: 'sendmail', - path: '/usr/local/bin/sendmail', - }, - sender: 'backstage@backstage.io', - broadcastConfig: { - receiver: 'config', - receiverEmails: ['broadcast@backstage.io'] as JsonArray, + processors: { + email: { + transport: { + transport: 'sendmail', + path: '/usr/local/bin/sendmail', + }, + sender: 'backstage@backstage.io', + broadcastConfig: { + receiver: 'config', + receiverEmails: ['broadcast@backstage.io'] as JsonArray, + }, }, }, }, diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index 296cf83657..a52495a8ea 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -22,26 +22,33 @@ import { CacheService, LoggerService, } from '@backstage/backend-plugin-api'; -import { Config } from '@backstage/config'; -import { JsonArray } from '@backstage/types'; +import { Config, readDurationFromConfig } from '@backstage/config'; +import { durationToMilliseconds, JsonArray } from '@backstage/types'; import { CATALOG_FILTER_EXISTS, CatalogClient, } from '@backstage/catalog-client'; import { Notification } from '@backstage/plugin-notifications-common'; -import { createSendmailTransport, createSmtpTransport } from './transports'; -import { createSesTransport } from './transports/ses'; +import { + createSendmailTransport, + createSesTransport, + createSmtpTransport, +} from './transports'; import { UserEntity } from '@backstage/catalog-model'; import { compact } from 'lodash'; import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node'; import { NotificationTemplateRenderer } from '../extensions'; +import Mail from 'nodemailer/lib/mailer'; +import pLimit from 'p-limit'; export class NotificationsEmailProcessor implements NotificationProcessor { private transporter: any; private readonly broadcastConfig?: Config; + private readonly transportConfig: Config; private readonly sender: string; private readonly replyTo?: string; private readonly cacheTtl: number; + private readonly concurrencyLimit: number; constructor( private readonly logger: LoggerService, @@ -51,50 +58,39 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly cache?: CacheService, private readonly templateRenderer?: NotificationTemplateRenderer, ) { - this.broadcastConfig = config.getOptionalConfig( - 'notifications.email.broadcastConfig', + const emailProcessorConfig = config.getConfig( + 'notifications.processors.email', ); - this.sender = config.getString('notifications.email.sender'); - this.replyTo = config.getOptionalString('notifications.email.replyTo'); - this.cacheTtl = - config.getOptionalNumber('notifications.email.cache.ttl') ?? 3_600_000; + this.transportConfig = emailProcessorConfig.getConfig('transport'); + this.broadcastConfig = + emailProcessorConfig.getOptionalConfig('broadcastConfig'); + this.sender = emailProcessorConfig.getString('sender'); + this.replyTo = emailProcessorConfig.getOptionalString('replyTo'); + this.concurrencyLimit = + emailProcessorConfig.getOptionalNumber('concurrencyLimit') ?? 2; + const cacheConfig = emailProcessorConfig.getOptionalConfig('cache.ttl'); + this.cacheTtl = cacheConfig + ? durationToMilliseconds(readDurationFromConfig(cacheConfig)) + : 3_600_000; } private async getTransporter() { if (this.transporter) { return this.transporter; } - const transportConfig = this.config.getConfig( - 'notifications.email.transport', - ); - const transport = transportConfig.getString('transport'); + const transport = this.transportConfig.getString('transport'); if (transport === 'smtp') { - this.transporter = createSmtpTransport({ - transport: 'smtp', - hostname: transportConfig.getString('hostname'), - port: transportConfig.getNumber('port'), - secure: transportConfig.getOptionalBoolean('secure'), - requireTls: transportConfig.getOptionalBoolean('requireTls'), - username: transportConfig.getOptionalString('username'), - password: transportConfig.getOptionalString('password'), - }); + this.transporter = createSmtpTransport(this.transportConfig); } else if (transport === 'ses') { const awsCredentialsManager = DefaultAwsCredentialsManager.fromConfig( this.config, ); - this.transporter = await createSesTransport({ - transport: 'ses', - credentialsManager: awsCredentialsManager, - apiVersion: transportConfig.getOptionalString('apiVersion'), - accountId: transportConfig.getOptionalString('accountId'), - region: transportConfig.getOptionalString('region'), - }); + this.transporter = await createSesTransport( + this.transportConfig, + awsCredentialsManager, + ); } else if (transport === 'sendmail') { - this.transporter = createSendmailTransport({ - transport: 'sendmail', - path: transportConfig.getOptionalString('path'), - newline: transportConfig.getOptionalString('newline'), - }); + this.transporter = createSendmailTransport(this.transportConfig); } else { throw new Error(`Unsupported transport: ${transport}`); } @@ -122,14 +118,13 @@ export class NotificationsEmailProcessor implements NotificationProcessor { } if (receiver === 'users') { - const cached = await this.cache?.get<JsonArray>('user-emails:all'); + const cached = await this.cache?.get<string[]>('user-emails:all'); if (cached) { - return cached as string[]; + return cached; } - const credentials = await this.auth.getOwnServiceCredentials(); const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: credentials, + onBehalfOf: await this.auth.getOwnServiceCredentials(), targetPluginId: 'catalog', }); const entities = await this.catalog.getEntities( @@ -148,6 +143,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { }), ), ]); + await this.cache?.set('user-emails:all', ret as JsonArray, { ttl: this.cacheTtl, }); @@ -163,28 +159,24 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return [cached]; } - const credentials = await this.auth.getOwnServiceCredentials(); const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: credentials, + onBehalfOf: await this.auth.getOwnServiceCredentials(), targetPluginId: 'catalog', }); const entity = await this.catalog.getEntityByRef(entityRef, { token }); - if (!entity) { - return []; + const ret: string[] = []; + if (entity) { + const userEntity = entity as UserEntity; + if (userEntity.spec.profile?.email) { + ret.push(userEntity.spec.profile.email); + } } - const userEntity = entity as UserEntity; - if (!userEntity.spec.profile?.email) { - return []; - } + await this.cache?.set(`user-emails:${entityRef}`, ret[0], { + ttl: this.cacheTtl, + }); - await this.cache?.set( - `user-emails:${entityRef}`, - userEntity.spec.profile.email, - { ttl: this.cacheTtl }, - ); - - return [userEntity.spec.profile.email]; + return ret; } private async getRecipientEmails( @@ -197,6 +189,23 @@ export class NotificationsEmailProcessor implements NotificationProcessor { return await this.getUserEmail(notification.user); } + private async sendMail(options: Mail.Options) { + try { + await this.transporter.sendMail(options); + } catch (e) { + this.logger.error(`Failed to send email to ${options.to}: ${e}`); + } + } + + private async sendMails(options: Mail.Options, emails: string[]) { + const limit = pLimit(this.concurrencyLimit); + await Promise.all( + emails.map(email => + limit(() => this.sendMail({ ...options, to: email })), + ), + ); + } + private async sendPlainEmail(notification: Notification, emails: string[]) { const contentParts: string[] = []; if (notification.payload.description) { @@ -214,13 +223,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { replyTo: this.replyTo, }; - for (const email of emails) { - try { - await this.transporter.sendMail({ ...mailOptions, to: email }); - } catch (e) { - this.logger.error(`Failed to send email to ${email}: ${e}`); - } - } + await this.sendMails(mailOptions, emails); } private async sendTemplateEmail( @@ -237,13 +240,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { replyTo: this.replyTo, }; - for (const email of emails) { - try { - await this.transporter.sendMail({ ...mailOptions, to: email }); - } catch (e) { - this.logger.error(`Failed to send email to ${email}: ${e}`); - } - } + await this.sendMails(mailOptions, emails); } async postProcess( diff --git a/plugins/notifications-backend-module-email/src/processor/transports/sendmail.ts b/plugins/notifications-backend-module-email/src/processor/transports/sendmail.ts index 8f093f562d..e16ea37ac9 100644 --- a/plugins/notifications-backend-module-email/src/processor/transports/sendmail.ts +++ b/plugins/notifications-backend-module-email/src/processor/transports/sendmail.ts @@ -14,12 +14,12 @@ * limitations under the License. */ import { createTransport } from 'nodemailer'; -import { SendmailTransportConfig } from '../../types'; +import { Config } from '@backstage/config'; -export const createSendmailTransport = (config: SendmailTransportConfig) => { +export const createSendmailTransport = (config: Config) => { return createTransport({ sendmail: true, - newline: config.newline ?? 'unix', - path: config.path ?? '/usr/sbin/sendmail', + newline: config.getOptionalString('newline') ?? 'unix', + path: config.getOptionalString('path') ?? '/usr/sbin/sendmail', }); }; diff --git a/plugins/notifications-backend-module-email/src/processor/transports/ses.ts b/plugins/notifications-backend-module-email/src/processor/transports/ses.ts index d208001bad..0be23078ab 100644 --- a/plugins/notifications-backend-module-email/src/processor/transports/ses.ts +++ b/plugins/notifications-backend-module-email/src/processor/transports/ses.ts @@ -13,19 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { SesTransportConfig } from '../../types'; import { createTransport } from 'nodemailer'; import { SendRawEmailCommand, SES } from '@aws-sdk/client-ses'; +import { Config } from '@backstage/config'; +import { AwsCredentialsManager } from '@backstage/integration-aws-node'; -export const createSesTransport = async (config: SesTransportConfig) => { - const credentials = await config.credentialsManager.getCredentialProvider({ - accountId: config.accountId, +export const createSesTransport = async ( + config: Config, + credentialsManager: AwsCredentialsManager, +) => { + const credentials = await credentialsManager.getCredentialProvider({ + accountId: config.getOptionalString('accountId'), }); const ses = new SES([ { - apiVersion: config.apiVersion ?? '2010-12-01', + apiVersion: config.getOptionalString('apiVersion') ?? '2010-12-01', credentials: credentials.sdkCredentialProvider, - region: config.region, + region: config.getOptionalString('region'), }, ]); return createTransport({ diff --git a/plugins/notifications-backend-module-email/src/processor/transports/smtp.ts b/plugins/notifications-backend-module-email/src/processor/transports/smtp.ts index e473ea5ba7..feea417c27 100644 --- a/plugins/notifications-backend-module-email/src/processor/transports/smtp.ts +++ b/plugins/notifications-backend-module-email/src/processor/transports/smtp.ts @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { SmtpTransportConfig } from '../../types'; import { createTransport } from 'nodemailer'; +import { Config } from '@backstage/config'; + +export const createSmtpTransport = (config: Config) => { + const username = config.getOptionalString('username'); + const password = config.getOptionalString('password'); -export const createSmtpTransport = (config: SmtpTransportConfig) => { return createTransport({ - host: config.hostname, - port: config.port, - secure: config.secure ?? false, - requireTLS: config.requireTls ?? false, - auth: - config.username && config.password - ? { user: config.username, pass: config.password } - : undefined, + host: config.getString('hostname'), + port: config.getNumber('port'), + secure: config.getOptionalBoolean('secure') ?? false, + requireTLS: config.getOptionalBoolean('requireTls') ?? false, + auth: username && password ? { user: username, pass: password } : undefined, }); }; diff --git a/plugins/notifications-backend-module-email/src/types.ts b/plugins/notifications-backend-module-email/src/types.ts deleted file mode 100644 index 57c4b05234..0000000000 --- a/plugins/notifications-backend-module-email/src/types.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 { AwsCredentialsManager } from '@backstage/integration-aws-node'; - -export interface TransportConfig { - transport: 'smtp' | 'ses' | 'sendmail'; -} - -export interface SmtpTransportConfig extends TransportConfig { - transport: 'smtp'; - /** - * SMTP server hostname - */ - hostname: string; - /** - * SMTP server port - */ - port: number; - /** - * Use secure connection for SMTP, defaults to false - */ - secure?: boolean; - /** - * Require TLS for SMTP connection, defaults to false - */ - requireTls?: boolean; - /** - * SMTP username - */ - username?: string; - /** - * SMTP password - * @visibility secret - */ - password?: string; -} - -export interface SesTransportConfig extends TransportConfig { - transport: 'ses'; - /** - * SES ApiVersion to use, defaults to 2010-12-01 - */ - apiVersion?: string; - accountId?: string; - region?: string; - credentialsManager: AwsCredentialsManager; -} - -export interface SendmailTransportConfig extends TransportConfig { - transport: 'sendmail'; - /** - * Sendmail binary path, defaults to /usr/sbin/sendmail - */ - path?: string; - /** - * Newline style, defaults to 'unix' - */ - newline?: string; -} diff --git a/yarn.lock b/yarn.lock index 40554ed539..400095afee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6051,6 +6051,7 @@ __metadata: "@types/nodemailer": ^6.4.14 lodash: ^4.17.21 nodemailer: ^6.9.13 + p-limit: ^3.1.0 languageName: unknown linkType: soft From 49295af2c16e50b259ec39e9c0c68ed8c0d11d13 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Wed, 24 Apr 2024 10:30:28 +0300 Subject: [PATCH 117/234] feat: allow throttling email sending Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .../config.d.ts | 4 ++++ .../package.json | 2 +- .../processor/NotificationsEmailProcessor.ts | 19 ++++++++++++++----- yarn.lock | 9 ++++++++- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/plugins/notifications-backend-module-email/config.d.ts b/plugins/notifications-backend-module-email/config.d.ts index 90fb43e046..db49d0016a 100644 --- a/plugins/notifications-backend-module-email/config.d.ts +++ b/plugins/notifications-backend-module-email/config.d.ts @@ -91,6 +91,10 @@ export interface Config { * Concurrency limit for email sending, defaults to 2 */ concurrencyLimit?: number; + /** + * Throttle duration between email sending, defaults to 100ms + */ + throttleInterval?: HumanDuration; /** * Configuration for broadcast notifications */ diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 3d44db6a45..dfff036a13 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -45,7 +45,7 @@ "@backstage/types": "workspace:^", "lodash": "^4.17.21", "nodemailer": "^6.9.13", - "p-limit": "^3.1.0" + "p-throttle": "^6.1.0" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index a52495a8ea..d9f6b73e0a 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -39,7 +39,7 @@ import { compact } from 'lodash'; import { DefaultAwsCredentialsManager } from '@backstage/integration-aws-node'; import { NotificationTemplateRenderer } from '../extensions'; import Mail from 'nodemailer/lib/mailer'; -import pLimit from 'p-limit'; +import pThrottle from 'p-throttle'; export class NotificationsEmailProcessor implements NotificationProcessor { private transporter: any; @@ -49,6 +49,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly replyTo?: string; private readonly cacheTtl: number; private readonly concurrencyLimit: number; + private readonly throttleInterval: number; constructor( private readonly logger: LoggerService, @@ -68,6 +69,11 @@ export class NotificationsEmailProcessor implements NotificationProcessor { this.replyTo = emailProcessorConfig.getOptionalString('replyTo'); this.concurrencyLimit = emailProcessorConfig.getOptionalNumber('concurrencyLimit') ?? 2; + const throttleConfig = + emailProcessorConfig.getOptionalConfig('throttleInterval'); + this.throttleInterval = throttleConfig + ? durationToMilliseconds(readDurationFromConfig(throttleConfig)) + : 100; const cacheConfig = emailProcessorConfig.getOptionalConfig('cache.ttl'); this.cacheTtl = cacheConfig ? durationToMilliseconds(readDurationFromConfig(cacheConfig)) @@ -198,11 +204,14 @@ export class NotificationsEmailProcessor implements NotificationProcessor { } private async sendMails(options: Mail.Options, emails: string[]) { - const limit = pLimit(this.concurrencyLimit); + const throttle = pThrottle({ + limit: this.concurrencyLimit, + interval: this.throttleInterval, + }); + + const throttled = throttle((opts: Mail.Options) => this.sendMail(opts)); await Promise.all( - emails.map(email => - limit(() => this.sendMail({ ...options, to: email })), - ), + emails.map(email => throttled({ ...options, to: email })), ); } diff --git a/yarn.lock b/yarn.lock index 400095afee..846721560c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6051,7 +6051,7 @@ __metadata: "@types/nodemailer": ^6.4.14 lodash: ^4.17.21 nodemailer: ^6.9.13 - p-limit: ^3.1.0 + p-throttle: ^6.1.0 languageName: unknown linkType: soft @@ -33255,6 +33255,13 @@ __metadata: languageName: node linkType: hard +"p-throttle@npm:^6.1.0": + version: 6.1.0 + resolution: "p-throttle@npm:6.1.0" + checksum: c1947cca8844564c3d86f8c09067add5e7398b87898cb1f1aea5c48d2c211590d039a316d28a4b0d95364ffa0213c01dff6bf71175c468eab0e381f77715dbdb + languageName: node + linkType: hard + "p-timeout@npm:^3.2.0": version: 3.2.0 resolution: "p-timeout@npm:3.2.0" From 743832d39f64c6091b581c621d4bff99cf651fd8 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Wed, 24 Apr 2024 12:09:54 +0300 Subject: [PATCH 118/234] fix: use string array for cache service Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .../src/processor/NotificationsEmailProcessor.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index d9f6b73e0a..5387d09c3d 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -23,7 +23,7 @@ import { LoggerService, } from '@backstage/backend-plugin-api'; import { Config, readDurationFromConfig } from '@backstage/config'; -import { durationToMilliseconds, JsonArray } from '@backstage/types'; +import { durationToMilliseconds } from '@backstage/types'; import { CATALOG_FILTER_EXISTS, CatalogClient, @@ -150,7 +150,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { ), ]); - await this.cache?.set('user-emails:all', ret as JsonArray, { + await this.cache?.set('user-emails:all', ret, { ttl: this.cacheTtl, }); return ret; @@ -160,9 +160,9 @@ export class NotificationsEmailProcessor implements NotificationProcessor { } private async getUserEmail(entityRef: string): Promise<string[]> { - const cached = await this.cache?.get<string>(`user-emails:${entityRef}`); + const cached = await this.cache?.get<string[]>(`user-emails:${entityRef}`); if (cached) { - return [cached]; + return cached; } const { token } = await this.auth.getPluginRequestToken({ @@ -178,7 +178,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { } } - await this.cache?.set(`user-emails:${entityRef}`, ret[0], { + await this.cache?.set(`user-emails:${entityRef}`, ret, { ttl: this.cacheTtl, }); From f730c0b54f167ee5ecc231fcec445c601661c26a Mon Sep 17 00:00:00 2001 From: Marek Libra <marek.libra@gmail.com> Date: Tue, 23 Apr 2024 10:28:40 +0200 Subject: [PATCH 119/234] feat(notifications): added Mark All Read bulk action The user can newly mark all unread messages as read at one click. Signed-off-by: Marek Libra <marek.libra@gmail.com> --- .changeset/funny-bees-taste.md | 5 ++ plugins/notifications/api-report.md | 2 + .../NotificationsPage/NotificationsPage.tsx | 77 +++++++++++-------- .../NotificationsTable/BulkActions.tsx | 24 +++++- .../NotificationsTable/NotificationsTable.tsx | 50 +++++++++++- 5 files changed, 121 insertions(+), 37 deletions(-) create mode 100644 .changeset/funny-bees-taste.md diff --git a/.changeset/funny-bees-taste.md b/.changeset/funny-bees-taste.md new file mode 100644 index 0000000000..a9b2d84997 --- /dev/null +++ b/.changeset/funny-bees-taste.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +The user can newly mark all unread messages as read at one click. diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index 9ffcf0d727..8fbd148b6d 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -112,6 +112,7 @@ export const NotificationsSidebarItem: (props?: { export const NotificationsTable: ({ isLoading, notifications, + isUnread, onUpdate, setContainsText, onPageChange, @@ -127,6 +128,7 @@ export type NotificationsTableProps = Pick< 'onPageChange' | 'onRowsPerPageChange' | 'page' | 'totalCount' > & { isLoading?: boolean; + isUnread: boolean; notifications?: Notification_2[]; onUpdate: () => void; setContainsText: (search: string) => void; diff --git a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx index 539db4f61e..f1dd9aba5f 100644 --- a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx +++ b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx @@ -22,6 +22,7 @@ import { ResponseErrorPanel, } from '@backstage/core-components'; import Grid from '@material-ui/core/Grid'; +import { ConfirmProvider } from 'material-ui-confirm'; import { useSignal } from '@backstage/plugin-signals-react'; import { NotificationsTable } from '../NotificationsTable'; @@ -32,8 +33,11 @@ import { SortBy, SortByOptions, } from '../NotificationsFilters'; -import { GetNotificationsOptions } from '../../api'; -import { NotificationSeverity } from '@backstage/plugin-notifications-common'; +import { GetNotificationsOptions, GetNotificationsResponse } from '../../api'; +import { + NotificationSeverity, + NotificationStatus, +} from '@backstage/plugin-notifications-common'; const ThrottleDelayMs = 2000; @@ -70,7 +74,9 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { ); const [severity, setSeverity] = React.useState<NotificationSeverity>('low'); - const { error, value, retry, loading } = useNotificationsApi( + const { error, value, retry, loading } = useNotificationsApi< + [GetNotificationsResponse, NotificationStatus] + >( api => { const options: GetNotificationsOptions = { search: containsText, @@ -91,7 +97,7 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { options.createdAfter = createdAfterDate; } - return api.getNotifications(options); + return Promise.all([api.getNotifications(options), api.getStatus()]); }, [ containsText, @@ -131,6 +137,10 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { return <ResponseErrorPanel error={error} />; } + const notifications = value?.[0]?.notifications; + const totalCount = value?.[0]?.totalCount; + const isUnread = !!value?.[1]?.unread; + return ( <PageWithHeader title={title} @@ -141,35 +151,38 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { typeLink={typeLink} > <Content> - <Grid container> - <Grid item xs={2}> - <NotificationsFilters - unreadOnly={unreadOnly} - onUnreadOnlyChanged={setUnreadOnly} - createdAfter={createdAfter} - onCreatedAfterChanged={setCreatedAfter} - onSortingChanged={setSorting} - sorting={sorting} - saved={saved} - onSavedChanged={setSaved} - severity={severity} - onSeverityChanged={setSeverity} - /> + <ConfirmProvider> + <Grid container> + <Grid item xs={2}> + <NotificationsFilters + unreadOnly={unreadOnly} + onUnreadOnlyChanged={setUnreadOnly} + createdAfter={createdAfter} + onCreatedAfterChanged={setCreatedAfter} + onSortingChanged={setSorting} + sorting={sorting} + saved={saved} + onSavedChanged={setSaved} + severity={severity} + onSeverityChanged={setSeverity} + /> + </Grid> + <Grid item xs={10}> + <NotificationsTable + isLoading={loading} + isUnread={isUnread} + notifications={notifications} + onUpdate={onUpdate} + setContainsText={setContainsText} + onPageChange={setPageNumber} + onRowsPerPageChange={setPageSize} + page={pageNumber} + pageSize={pageSize} + totalCount={totalCount} + /> + </Grid> </Grid> - <Grid item xs={10}> - <NotificationsTable - isLoading={loading} - notifications={value?.notifications} - onUpdate={onUpdate} - setContainsText={setContainsText} - onPageChange={setPageNumber} - onRowsPerPageChange={setPageSize} - page={pageNumber} - pageSize={pageSize} - totalCount={value?.totalCount} - /> - </Grid> - </Grid> + </ConfirmProvider> </Content> </PageWithHeader> ); diff --git a/plugins/notifications/src/components/NotificationsTable/BulkActions.tsx b/plugins/notifications/src/components/NotificationsTable/BulkActions.tsx index 034da7c93c..40e2f5c8bb 100644 --- a/plugins/notifications/src/components/NotificationsTable/BulkActions.tsx +++ b/plugins/notifications/src/components/NotificationsTable/BulkActions.tsx @@ -22,17 +22,22 @@ import MarkAsUnreadIcon from '@material-ui/icons/Markunread' /* TODO: use Drafts import MarkAsReadIcon from '@material-ui/icons/CheckCircle'; import MarkAsUnsavedIcon from '@material-ui/icons/LabelOff' /* TODO: use BookmarkRemove and BookmarkAdd once we have mui 5 icons */; import MarkAsSavedIcon from '@material-ui/icons/Label'; +import MarkAllReadIcon from '@material-ui/icons/DoneAll'; export const BulkActions = ({ selectedNotifications, notifications, + isUnread, onSwitchReadStatus, onSwitchSavedStatus, + onMarkAllRead, }: { selectedNotifications: Set<Notification['id']>; notifications: Notification[]; + isUnread?: boolean; onSwitchReadStatus: (ids: Notification['id'][], newStatus: boolean) => void; onSwitchSavedStatus: (ids: Notification['id'][], newStatus: boolean) => void; + onMarkAllRead?: () => void; }) => { const isDisabled = selectedNotifications.size === 0; const bulkNotifications = notifications.filter(notification => @@ -58,7 +63,22 @@ export const BulkActions = ({ return ( <Grid container wrap="nowrap"> - <Grid item> + <Grid item xs={3}> + {onMarkAllRead ? ( + <Tooltip title="Mark all read"> + <div> + {/* The <div> here is a workaround for the Tooltip which does not work for a "disabled" child */} + <IconButton disabled={!isUnread} onClick={onMarkAllRead}> + <MarkAllReadIcon aria-label={markAsSavedText} /> + </IconButton> + </div> + </Tooltip> + ) : ( + <div /> + )} + </Grid> + + <Grid item xs={3}> <Tooltip title={markAsSavedText}> <div> {/* The <div> here is a workaround for the Tooltip which does not work for a "disabled" child */} @@ -74,7 +94,7 @@ export const BulkActions = ({ </Tooltip> </Grid> - <Grid item> + <Grid item xs={3}> <Tooltip title={markAsReadText}> <div> <IconButton diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx index f1d6e58b51..f8091a021f 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx @@ -23,9 +23,8 @@ import CheckBox from '@material-ui/core/Checkbox'; import Typography from '@material-ui/core/Typography'; import { makeStyles } from '@material-ui/core/styles'; import { Notification } from '@backstage/plugin-notifications-common'; - -import { notificationsApiRef } from '../../api'; -import { useApi } from '@backstage/core-plugin-api'; +import { useConfirm } from 'material-ui-confirm'; +import { alertApiRef, useApi } from '@backstage/core-plugin-api'; import { Link, Table, @@ -33,6 +32,8 @@ import { TableColumn, } from '@backstage/core-components'; +import { notificationsApiRef } from '../../api'; + import { SeverityIcon } from './SeverityIcon'; import { SelectAll } from './SelectAll'; import { BulkActions } from './BulkActions'; @@ -55,6 +56,7 @@ export type NotificationsTableProps = Pick< 'onPageChange' | 'onRowsPerPageChange' | 'page' | 'totalCount' > & { isLoading?: boolean; + isUnread: boolean; notifications?: Notification[]; onUpdate: () => void; setContainsText: (search: string) => void; @@ -65,6 +67,7 @@ export type NotificationsTableProps = Pick< export const NotificationsTable = ({ isLoading, notifications = [], + isUnread, onUpdate, setContainsText, onPageChange, @@ -75,6 +78,9 @@ export const NotificationsTable = ({ }: NotificationsTableProps) => { const classes = useStyles(); const notificationsApi = useApi(notificationsApiRef); + const alertApi = useApi(alertApiRef); + const confirm = useConfirm(); + const [selectedNotifications, setSelectedNotifications] = React.useState( new Set<Notification['id']>(), ); @@ -117,6 +123,39 @@ export const NotificationsTable = ({ [notificationsApi, onUpdate], ); + const onMarkAllRead = React.useCallback(() => { + confirm({ + title: 'Are you sure?', + description: ( + <> + Mark <b>all</b> notifications as <b>read</b>. + </> + ), + confirmationText: 'Mark All', + }) + .then(async () => { + const ids = ( + await notificationsApi.getNotifications({ read: false }) + ).notifications?.map(notification => notification.id); + + return notificationsApi + .updateNotifications({ + ids, + read: true, + }) + .then(onUpdate); + }) + .catch(e => { + if (e) { + // if e === undefined, the Cancel button has been hit + alertApi.post({ + message: 'Failed to mark all notifications as read', + severity: 'error', + }); + } + }); + }, [alertApi, confirm, notificationsApi, onUpdate]); + const throttledContainsTextHandler = React.useMemo( () => throttle(setContainsText, ThrottleDelayMs), [setContainsText], @@ -210,8 +249,10 @@ export const NotificationsTable = ({ <BulkActions notifications={notifications} selectedNotifications={selectedNotifications} + isUnread={isUnread} onSwitchReadStatus={onSwitchReadStatus} onSwitchSavedStatus={onSwitchSavedStatus} + onMarkAllRead={onMarkAllRead} /> ), render: (notification: Notification) => ( @@ -220,6 +261,7 @@ export const NotificationsTable = ({ selectedNotifications={new Set([notification.id])} onSwitchReadStatus={onSwitchReadStatus} onSwitchSavedStatus={onSwitchSavedStatus} + // /> ), }, @@ -227,8 +269,10 @@ export const NotificationsTable = ({ [ selectedNotifications, notifications, + isUnread, onSwitchReadStatus, onSwitchSavedStatus, + onMarkAllRead, onNotificationsSelectChange, classes.severityItem, classes.description, From 23b461f0f0eb4f84e670bad991d9ff4eae4375f5 Mon Sep 17 00:00:00 2001 From: Marek Libra <marek.libra@gmail.com> Date: Tue, 23 Apr 2024 10:31:06 +0200 Subject: [PATCH 120/234] Add material-ui-confirm dependency Signed-off-by: Marek Libra <marek.libra@gmail.com> --- plugins/notifications/package.json | 1 + yarn.lock | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 9832871023..9da48caefc 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -43,6 +43,7 @@ "@material-ui/lab": "^4.0.0-alpha.61", "@types/react": "^16.13.1 || ^17.0.0", "lodash": "^4.17.21", + "material-ui-confirm": "^3.0.12", "notistack": "^3.0.1", "react-relative-time": "^0.0.9", "react-use": "^17.2.4" diff --git a/yarn.lock b/yarn.lock index 9a940a725a..bcb3acdc0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6212,6 +6212,7 @@ __metadata: "@testing-library/user-event": ^14.0.0 "@types/react": ^16.13.1 || ^17.0.0 lodash: ^4.17.21 + material-ui-confirm: ^3.0.12 msw: ^1.0.0 notistack: ^3.0.1 react-relative-time: ^0.0.9 @@ -30548,6 +30549,17 @@ __metadata: languageName: node linkType: hard +"material-ui-confirm@npm:^3.0.12": + version: 3.0.12 + resolution: "material-ui-confirm@npm:3.0.12" + peerDependencies: + "@mui/material": ">= 5.0.0" + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + checksum: de1092fdc4ab8104fbfdb5034921aeb2a5db5854ad5e31fb24dc4c204c6fb5b66a33ad377abf7adda390c432aefe96a2188ef1b8a3b9c1a80a9a28af63833458 + languageName: node + linkType: hard + "material-ui-popup-state@npm:^1.9.3": version: 1.9.3 resolution: "material-ui-popup-state@npm:1.9.3" From 9ae9bb2022db86256c55fbfc7d72024b38f4b221 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Wed, 24 Apr 2024 13:21:23 +0200 Subject: [PATCH 121/234] +Update the paths logic in the api reports command to support complex subpaths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- .changeset/curly-shirts-flow.md | 5 ++ .../src/commands/api-reports/api-extractor.ts | 65 +++++++++++------- .../api-reports/generateTypeDeclarations.ts | 2 +- .../src/commands/type-deps/type-deps.ts | 8 +-- packages/repo-tools/src/lib/entryPoints.ts | 39 ----------- .../src/lib/getPackageExportDetails.ts | 68 +++++++++++++++++++ 6 files changed, 118 insertions(+), 69 deletions(-) create mode 100644 .changeset/curly-shirts-flow.md delete mode 100644 packages/repo-tools/src/lib/entryPoints.ts create mode 100644 packages/repo-tools/src/lib/getPackageExportDetails.ts diff --git a/.changeset/curly-shirts-flow.md b/.changeset/curly-shirts-flow.md new file mode 100644 index 0000000000..b9a73809ba --- /dev/null +++ b/.changeset/curly-shirts-flow.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Update the paths logic in the api reports command to support complex subpaths diff --git a/packages/repo-tools/src/commands/api-reports/api-extractor.ts b/packages/repo-tools/src/commands/api-reports/api-extractor.ts index 7e36b08b0a..6a523af6a9 100644 --- a/packages/repo-tools/src/commands/api-reports/api-extractor.ts +++ b/packages/repo-tools/src/commands/api-reports/api-extractor.ts @@ -62,7 +62,7 @@ import { IMarkdownEmitterContext } from '@microsoft/api-documenter/lib/markdown/ import { AstDeclaration } from '@microsoft/api-extractor/lib/analyzer/AstDeclaration'; import { paths as cliPaths } from '../../lib/paths'; import { minimatch } from 'minimatch'; -import { getPackageExportNames } from '../../lib/entryPoints'; +import { getPackageExportDetails } from '../../lib/getPackageExportDetails'; import { createBinRunner } from '../util'; const tmpDir = cliPaths.resolveTargetRoot( @@ -303,8 +303,15 @@ function logApiReportInstructions() { async function findPackageEntryPoints(packageDirs: string[]): Promise< Array<{ + // package dir relative to root, e.g. "packages/backend-app-api" packageDir: string; + // the name of the export, e.g. "index" or "alpha" name: string; + // the path within the dist directory for this export, e.g. "alpha.d.ts" + distPath: string; + // the path within the dist-types directory of this package for this export, + // e.g. "src/entrypoints/foo/index.d.ts" + distTypesPath: string; }> > { return Promise.all( @@ -313,12 +320,9 @@ async function findPackageEntryPoints(packageDirs: string[]): Promise< cliPaths.resolveTargetRoot(packageDir, 'package.json'), ); - return ( - getPackageExportNames(pkg)?.map(name => ({ packageDir, name })) ?? { - packageDir, - name: 'index', - } - ); + return getPackageExportDetails(pkg).map(details => { + return { packageDir, ...details }; + }); }), ).then(results => results.flat()); } @@ -344,13 +348,23 @@ export async function runApiExtraction({ }: ApiExtractionOptions) { await fs.remove(outputDir); - const packageEntryPoints = await findPackageEntryPoints(packageDirs); + // The collection of all entry points of all packages, as a single list + const allEntryPoints = await findPackageEntryPoints(packageDirs); - const entryPoints = packageEntryPoints.map(({ packageDir, name }) => { - return cliPaths.resolveTargetRoot( - `./dist-types/${packageDir}/src/${name}.d.ts`, - ); - }); + // The path (relative to the root) to ALL dist-types entry points (e.g. + // "dist-types/packages/backend-app-api/src/index.d.ts"). These are used as + // "extra"/contextual entry points for the extractor so that it can see the + // full context of things that are required by the local entry point being + // inspected. + const allDistTypesEntryPointPaths = allEntryPoints.map( + ({ packageDir, distTypesPath }) => { + return cliPaths.resolveTargetRoot( + './dist-types', + packageDir, + distTypesPath, + ); + }, + ); let compilerState: CompilerState | undefined = undefined; @@ -364,8 +378,8 @@ export async function runApiExtraction({ } const warnings = new Array<string>(); - for (const [packageDir, group] of Object.entries( - groupBy(packageEntryPoints, ep => ep.packageDir), + for (const [packageDir, packageEntryPoints] of Object.entries( + groupBy(allEntryPoints, ep => ep.packageDir), )) { console.log(`## Processing ${packageDir}`); const noBail = Array.isArray(allowWarnings) @@ -377,7 +391,6 @@ export async function runApiExtraction({ './dist-types', packageDir, ); - const names = group.map(ep => ep.name); const remainingReportFiles = new Set( fs @@ -389,8 +402,9 @@ export async function runApiExtraction({ ), ); - for (const name of names) { - const suffix = name === 'index' ? '' : `-${name}`; + for (const packageEntryPoint of packageEntryPoints) { + const suffix = + packageEntryPoint.name === 'index' ? '' : `-${packageEntryPoint.name}`; const reportFileName = `api-report${suffix}.md`; const reportPath = resolvePath(projectFolder, reportFileName); remainingReportFiles.delete(reportFileName); @@ -401,7 +415,7 @@ export async function runApiExtraction({ configObject: { mainEntryPointFilePath: resolvePath( packageFolder, - `src/${name}.d.ts`, + packageEntryPoint.distTypesPath, ), bundledPackages: [], @@ -422,7 +436,7 @@ export async function runApiExtraction({ docModel: { // TODO(Rugvip): This skips docs for non-index entry points. We can try to work around it, but // most likely it makes sense to wait for API Extractor to natively support exports. - enabled: name === 'index', + enabled: packageEntryPoint.name === 'index', apiJsonFilePath: resolvePath( outputDir, `<unscopedPackageName>${suffix}.api.json`, @@ -482,7 +496,7 @@ export async function runApiExtraction({ if (!compilerState) { compilerState = CompilerState.create(extractorConfig, { - additionalEntryPoints: entryPoints, + additionalEntryPoints: allDistTypesEntryPointPaths, }); } @@ -519,18 +533,21 @@ export async function runApiExtraction({ }); // This release tag validation makes sure that the release tag of known entry points match expectations. - // The root index entrypoint is only allowed @public exports, while /alpha and /beta only allow @alpha and @beta. + // The root index entry point is only allowed @public exports, while /alpha and /beta only allow @alpha and @beta. if ( validateReleaseTags && fs.pathExistsSync(extractorConfig.reportFilePath) ) { - if (['index', 'alpha', 'beta'].includes(name)) { + if (['index', 'alpha', 'beta'].includes(packageEntryPoint.name)) { const report = await fs.readFile( extractorConfig.reportFilePath, 'utf8', ); const lines = report.split(/\r?\n/); - const expectedTag = name === 'index' ? 'public' : name; + const expectedTag = + packageEntryPoint.name === 'index' + ? 'public' + : packageEntryPoint.name; for (let i = 0; i < lines.length; i += 1) { const line = lines[i]; const match = line.match(/^\/\/ @(alpha|beta|public)/); diff --git a/packages/repo-tools/src/commands/api-reports/generateTypeDeclarations.ts b/packages/repo-tools/src/commands/api-reports/generateTypeDeclarations.ts index 58f4117bc2..43ee2aa85f 100644 --- a/packages/repo-tools/src/commands/api-reports/generateTypeDeclarations.ts +++ b/packages/repo-tools/src/commands/api-reports/generateTypeDeclarations.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import fs from 'fs-extra'; import { spawnSync } from 'child_process'; import { paths as cliPaths } from '../../lib/paths'; @@ -27,7 +28,6 @@ import { paths as cliPaths } from '../../lib/paths'; * @param tsconfigFilePath {string} The path to the `tsconfig.json` file to use for generating the declaration files. * @returns {Promise<void>} A promise that resolves when the declaration files have been generated. */ - export async function generateTypeDeclarations(tsconfigFilePath: string) { await fs.remove(cliPaths.resolveTargetRoot('dist-types')); const { status } = spawnSync( diff --git a/packages/repo-tools/src/commands/type-deps/type-deps.ts b/packages/repo-tools/src/commands/type-deps/type-deps.ts index 17164cea39..7b75ac37ee 100644 --- a/packages/repo-tools/src/commands/type-deps/type-deps.ts +++ b/packages/repo-tools/src/commands/type-deps/type-deps.ts @@ -20,7 +20,7 @@ import { resolve as resolvePath } from 'path'; // eslint-disable-next-line @backstage/no-undeclared-imports import chalk from 'chalk'; import { getPackages, Package } from '@manypkg/get-packages'; -import { getPackageExportNames } from '../../lib/entryPoints'; +import { getPackageExportDetails } from '../../lib/getPackageExportDetails'; export default async () => { const { packages } = await getPackages(resolvePath('.')); @@ -97,11 +97,9 @@ function findAllDeps(declSrc: string) { * missing or incorrect in package.json */ function checkTypes(pkg: Package) { - const entryPointNames = getPackageExportNames(pkg.packageJson) ?? ['index']; - - const allDeps = entryPointNames.flatMap(name => { + const allDeps = getPackageExportDetails(pkg.packageJson).flatMap(exp => { const typeDecl = fs.readFileSync( - resolvePath(pkg.dir, `dist/${name}.d.ts`), + resolvePath(pkg.dir, 'dist', exp.distPath), 'utf8', ); return findAllDeps(typeDecl); diff --git a/packages/repo-tools/src/lib/entryPoints.ts b/packages/repo-tools/src/lib/entryPoints.ts deleted file mode 100644 index 4a412d410b..0000000000 --- a/packages/repo-tools/src/lib/entryPoints.ts +++ /dev/null @@ -1,39 +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 { extname } from 'path'; -import type { JsonObject } from '@backstage/types'; - -export function getPackageExportNames(pkg: JsonObject): string[] | undefined { - if (pkg.exports && typeof pkg.exports !== 'string') { - return Object.entries(pkg.exports).flatMap(([mount, path]) => { - const ext = extname(String(path)); - if (!['.ts', '.tsx', '.cts', '.mts'].includes(ext)) { - return []; // Ignore non-TS entry points - } - let name = mount; - if (name.startsWith('./')) { - name = name.slice(2); - } - if (!name || name === '.') { - return ['index']; - } - return [name]; - }); - } - - return undefined; -} diff --git a/packages/repo-tools/src/lib/getPackageExportDetails.ts b/packages/repo-tools/src/lib/getPackageExportDetails.ts new file mode 100644 index 0000000000..6889bb2e77 --- /dev/null +++ b/packages/repo-tools/src/lib/getPackageExportDetails.ts @@ -0,0 +1,68 @@ +/* + * 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 { extname } from 'path'; +import type { JsonObject } from '@backstage/types'; + +export function getPackageExportDetails(pkg: JsonObject): Array<{ + // the name of the export, e.g. "index" or "alpha" + name: string; + // the path within the dist directory for this export, e.g. "alpha.d.ts" + distPath: string; + // the path within the dist-types directory of this package for this export, + // e.g. "src/entrypoints/foo/index.d.ts" + distTypesPath: string; +}> { + if (pkg.exports && typeof pkg.exports !== 'string') { + return Object.entries(pkg.exports).flatMap( + ([mount, path]: [string, string]) => { + const ext = extname(path); + if (!['.ts', '.tsx', '.cts', '.mts'].includes(ext)) { + return []; // Ignore non-TS entry points + } + + let name = mount; + if (name.startsWith('./')) { + name = name.slice(2); + } + if (!name || name === '.') { + name = 'index'; + } + + const distPath = `${name}.d.ts`; + const distTypesPath = path + .replace(/^\.\//, '') // Remove leading "./" + .replace(/\.[^.]+$/, '.d.ts'); // Replace .extension with .d.ts + + return [ + { + name, + distPath, + distTypesPath, + }, + ]; + }, + ); + } + + return [ + { + name: 'index', + distPath: 'index.d.ts', + distTypesPath: 'src/index.d.ts', + }, + ]; +} From cba628ae6f05700c78dd738a1d6c226aee2247fc Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Mon, 22 Apr 2024 09:22:41 +0300 Subject: [PATCH 122/234] feat: generate random notifications in local development Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .changeset/tall-ads-shave.md | 5 ++ plugins/notifications-backend/dev/index.ts | 88 ++++++++++++++++++---- plugins/notifications/dev/index.tsx | 13 ++++ 3 files changed, 90 insertions(+), 16 deletions(-) create mode 100644 .changeset/tall-ads-shave.md diff --git a/.changeset/tall-ads-shave.md b/.changeset/tall-ads-shave.md new file mode 100644 index 0000000000..17d58452eb --- /dev/null +++ b/.changeset/tall-ads-shave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend': patch +--- + +Add possibility to generate random notifications on the fly in local development diff --git a/plugins/notifications-backend/dev/index.ts b/plugins/notifications-backend/dev/index.ts index fe35606dd2..5ee822c0cf 100644 --- a/plugins/notifications-backend/dev/index.ts +++ b/plugins/notifications-backend/dev/index.ts @@ -20,6 +20,55 @@ import { createBackendPlugin, } from '@backstage/backend-plugin-api'; import { notificationService } from '@backstage/plugin-notifications-node'; +import { errorHandler } from '@backstage/backend-common'; +import { + notificationSeverities, + NotificationSeverity, +} from '@backstage/plugin-notifications-common'; +import express, { Response } from 'express'; +import Router from 'express-promise-router'; + +const randomSeverity = (): NotificationSeverity => { + return notificationSeverities[ + Math.floor(Math.random() * notificationSeverities.length) + ]; +}; + +const notificationTitles = [ + 'You have a new notification', + 'Scaffolder task ended', + 'Your entity has some issues', +]; +const randomTitle = (): string => { + return notificationTitles[ + Math.floor(Math.random() * notificationTitles.length) + ]; +}; + +const notificationDescriptions = [ + 'There is a problem in the backstage', + 'See the sound engineer, please', + undefined, +]; +const randomDescription = (): string | undefined => { + return notificationDescriptions[ + Math.floor(Math.random() * notificationDescriptions.length) + ]; +}; + +const notificationTopics = ['Scaffolder', 'Backstage', 'Entity', undefined]; +const randomTopic = (): string | undefined => { + return notificationTopics[ + Math.floor(Math.random() * notificationTopics.length) + ]; +}; + +const notificationLinks = ['/catalog', '/create/tasks/123456', undefined]; +const randomLink = (): string | undefined => { + return notificationLinks[ + Math.floor(Math.random() * notificationLinks.length) + ]; +}; const notificationsDebug = createBackendPlugin({ pluginId: 'notifications-debug', @@ -27,25 +76,32 @@ const notificationsDebug = createBackendPlugin({ env.registerInit({ deps: { notifications: notificationService, - lifecycle: coreServices.lifecycle, + httpRouter: coreServices.httpRouter, }, - async init({ notifications, lifecycle }) { - let interval: NodeJS.Timeout | undefined; - lifecycle.addStartupHook(async () => { - interval = setInterval(async () => { - await notifications.send({ - recipients: { - type: 'broadcast', - }, - payload: { title: 'Test notification' }, - }); - }, 5000); + async init({ notifications, httpRouter }) { + const router = Router(); + router.use(express.json()); + router.post('/', async (_, res: Response<unknown>) => { + await notifications.send({ + recipients: { + type: 'broadcast', + }, + payload: { + title: randomTitle(), + description: randomDescription(), + topic: randomTopic(), + link: randomLink(), + severity: randomSeverity(), + }, + }); + res.status(200).send({ status: 'ok' }); }); + router.use(errorHandler()); - lifecycle.addShutdownHook(async () => { - if (interval) { - clearInterval(interval); - } + httpRouter.use(router); + httpRouter.addAuthPolicy({ + path: '/', + allow: 'unauthenticated', }); }, }); diff --git a/plugins/notifications/dev/index.tsx b/plugins/notifications/dev/index.tsx index a21170aec0..794812ef8f 100644 --- a/plugins/notifications/dev/index.tsx +++ b/plugins/notifications/dev/index.tsx @@ -21,6 +21,8 @@ import { NotificationsSidebarItem, } from '../src'; import { signalsPlugin } from '@backstage/plugin-signals'; +import { SidebarItem } from '@backstage/core-components'; +import AddAlert from '@material-ui/icons/AddAlert'; createDevApp() .registerPlugin(notificationsPlugin) @@ -35,4 +37,15 @@ createDevApp() path: '/notifications', }) .addSidebarItem(<NotificationsSidebarItem webNotificationsEnabled />) + .addSidebarItem( + <SidebarItem + icon={AddAlert} + text="Random notification" + onClick={() => { + fetch('http://localhost:7007/api/notifications-debug/', { + method: 'POST', + }); + }} + />, + ) .render(); From 07e36af4d4f5cb4368702c80c3787f06dae46879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Wed, 24 Apr 2024 14:10:10 +0200 Subject: [PATCH 123/234] sort MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- packages/backend-app-api/package.json | 44 ++++++++--------- packages/backend-common/package.json | 60 ++++++++++++------------ packages/backend-plugin-api/package.json | 46 +++++++++--------- 3 files changed, 75 insertions(+), 75 deletions(-) diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index f38dfb20d2..eb861bf944 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,20 +1,30 @@ { "name": "@backstage/backend-app-api", - "description": "Core API used by Backstage backend apps", "version": "0.7.1-next.0", - "main": "src/index.ts", - "types": "src/index.ts", - "publishConfig": { - "access": "public" - }, + "description": "Core API used by Backstage backend apps", "backstage": { "role": "node-library" }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "backstage" + ], + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-app-api" + }, + "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": [ @@ -25,16 +35,12 @@ ] } }, - "homepage": "https://backstage.io", - "repository": { - "type": "git", - "url": "https://github.com/backstage/backstage", - "directory": "packages/backend-app-api" - }, - "keywords": [ - "backstage" + "files": [ + "dist", + "config.d.ts", + "alpha", + "migrations/**/*.{js,d.ts}" ], - "license": "Apache-2.0", "scripts": { "build": "backstage-cli package build", "clean": "backstage-cli package clean", @@ -96,11 +102,5 @@ "msw": "^1.0.0", "supertest": "^6.1.3" }, - "configSchema": "config.d.ts", - "files": [ - "dist", - "config.d.ts", - "alpha", - "migrations/**/*.{js,d.ts}" - ] + "configSchema": "config.d.ts" } diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index f6003d9a0e..4cb83c2aa6 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,18 +1,31 @@ { "name": "@backstage/backend-common", - "description": "Common functionality library for Backstage backends", "version": "0.21.8-next.0", - "main": "src/index.ts", - "types": "src/index.ts", + "description": "Common functionality library for Backstage backends", + "backstage": { + "role": "node-library" + }, "publishConfig": { "access": "public" }, + "keywords": [ + "backstage" + ], + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-common" + }, + "license": "Apache-2.0", "exports": { ".": "./src/index.ts", "./alpha": "./src/alpha.ts", "./testUtils": "./src/testUtils.ts", "./package.json": "./package.json" }, + "main": "src/index.ts", + "types": "src/index.ts", "typesVersions": { "*": { "alpha": [ @@ -26,27 +39,19 @@ ] } }, - "backstage": { - "role": "node-library" - }, - "homepage": "https://backstage.io", - "repository": { - "type": "git", - "url": "https://github.com/backstage/backstage", - "directory": "packages/backend-common" - }, - "keywords": [ - "backstage" + "files": [ + "dist", + "config.d.ts", + "alpha" ], - "license": "Apache-2.0", "scripts": { "build": "backstage-cli package build", + "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", "start": "backstage-cli package start", + "test": "backstage-cli package test", "test:kubernetes": "backstage-cli package test -t KubernetesContainerRunner --no-watch" }, "dependencies": { @@ -108,14 +113,6 @@ "yauzl": "^3.0.0", "yn": "^4.0.0" }, - "peerDependencies": { - "pg-connection-string": "^2.3.0" - }, - "peerDependenciesMeta": { - "pg-connection-string": { - "optional": true - } - }, "devDependencies": { "@aws-sdk/util-stream-node": "^3.350.0", "@backstage/backend-test-utils": "workspace:^", @@ -138,10 +135,13 @@ "mysql2": "^3.0.0", "supertest": "^6.1.3" }, - "files": [ - "dist", - "config.d.ts", - "alpha" - ], + "peerDependencies": { + "pg-connection-string": "^2.3.0" + }, + "peerDependenciesMeta": { + "pg-connection-string": { + "optional": true + } + }, "configSchema": "config.d.ts" } diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 7b0eb70cad..fade566265 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,20 +1,30 @@ { "name": "@backstage/backend-plugin-api", - "description": "Core API used by Backstage backend plugins", "version": "0.6.18-next.0", - "main": "src/index.ts", - "types": "src/index.ts", - "publishConfig": { - "access": "public" - }, + "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "backstage" + ], + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-plugin-api" + }, + "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": [ @@ -25,24 +35,18 @@ ] } }, - "homepage": "https://backstage.io", - "repository": { - "type": "git", - "url": "https://github.com/backstage/backstage", - "directory": "packages/backend-plugin-api" - }, - "keywords": [ - "backstage" + "files": [ + "dist", + "alpha" ], - "license": "Apache-2.0", "scripts": { "build": "backstage-cli package build", + "clean": "backstage-cli package clean", "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@backstage/backend-tasks": "workspace:^", @@ -56,9 +60,5 @@ }, "devDependencies": { "@backstage/cli": "workspace:^" - }, - "files": [ - "dist", - "alpha" - ] + } } From 65a9c6c7cb794d82cfc56951e446bd9c6f2a82f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Wed, 24 Apr 2024 14:12:08 +0200 Subject: [PATCH 124/234] remove alphas MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- packages/backend-app-api/package.json | 1 - packages/backend-common/package.json | 3 +-- packages/backend-plugin-api/package.json | 3 +-- plugins/example-todo-list-backend/package.json | 3 +-- plugins/proxy-backend/package.json | 3 +-- plugins/scaffolder-node/package.json | 1 - plugins/techdocs-react/package.json | 1 - plugins/user-settings-backend/package.json | 3 +-- 8 files changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index eb861bf944..09ff295f8d 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -38,7 +38,6 @@ "files": [ "dist", "config.d.ts", - "alpha", "migrations/**/*.{js,d.ts}" ], "scripts": { diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 4cb83c2aa6..0a08c58e54 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -41,8 +41,7 @@ }, "files": [ "dist", - "config.d.ts", - "alpha" + "config.d.ts" ], "scripts": { "build": "backstage-cli package build", diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index fade566265..e4bfaf3112 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -36,8 +36,7 @@ } }, "files": [ - "dist", - "alpha" + "dist" ], "scripts": { "build": "backstage-cli package build", diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index ff4b20bee5..8a8d32f9b0 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -20,8 +20,7 @@ "main": "src/index.ts", "types": "src/index.ts", "files": [ - "dist", - "alpha" + "dist" ], "scripts": { "build": "backstage-cli package build", diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index d6f113eaf1..b955a40211 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -37,8 +37,7 @@ }, "files": [ "dist", - "config.d.ts", - "alpha" + "config.d.ts" ], "scripts": { "build": "backstage-cli package build", diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 214fd07dd1..d7d6105689 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -33,7 +33,6 @@ } }, "files": [ - "alpha", "dist" ], "scripts": { diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index 40a3731a75..4431c8f107 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -25,7 +25,6 @@ "main": "src/index.ts", "types": "src/index.ts", "files": [ - "alpha", "dist" ], "scripts": { diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 54bb8a5e5d..b8e9949bcf 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -34,8 +34,7 @@ }, "files": [ "dist", - "migrations", - "alpha" + "migrations" ], "scripts": { "build": "backstage-cli package build", From 5c4826cbc6be577f5070c6bdf8e1e9ac09775a20 Mon Sep 17 00:00:00 2001 From: blam <ben@blam.sh> Date: Wed, 24 Apr 2024 15:15:14 +0200 Subject: [PATCH 125/234] chore: use setup-python action to upgrade pip Signed-off-by: blam <ben@blam.sh> --- .github/workflows/verify_microsite.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 95bca2725d..408fb03b2e 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -34,6 +34,9 @@ jobs: uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18.x + - uses: actions/setup-python@v5 + with: + python-version: '3.9' # Skip caching of microsite dependencies, it keeps the global cache size # smaller, which make Windows builds a lot faster for the rest of the project. @@ -48,7 +51,7 @@ jobs: run: yarn build:api-docs - name: Install MkDocs dependencies - run: pip3 install mkdocs mkdocs-techdocs-core + run: pip install mkdocs mkdocs-techdocs-core - name: Build MkDocs for TechDocs run: mkdocs build --strict From 894b99737164232341b48bfd4afbd90a818865bd Mon Sep 17 00:00:00 2001 From: blam <ben@blam.sh> Date: Wed, 24 Apr 2024 15:24:42 +0200 Subject: [PATCH 126/234] chore: test Signed-off-by: blam <ben@blam.sh> Signed-off-by: blam <ben@blam.sh> --- .github/workflows/verify_microsite.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 408fb03b2e..68378aa086 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -38,6 +38,9 @@ jobs: with: python-version: '3.9' + - name: Install MkDocs dependencies + run: pip install mkdocs mkdocs-techdocs-core + # Skip caching of microsite dependencies, it keeps the global cache size # smaller, which make Windows builds a lot faster for the rest of the project. - name: top-level install @@ -50,9 +53,6 @@ jobs: - name: build API reference run: yarn build:api-docs - - name: Install MkDocs dependencies - run: pip install mkdocs mkdocs-techdocs-core - - name: Build MkDocs for TechDocs run: mkdocs build --strict From 61d0d74d3781ef41409cd56baff3e6748e16ec24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Wed, 24 Apr 2024 15:46:03 +0200 Subject: [PATCH 127/234] big warning on top of the old authenticate api requests guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- contrib/docs/tutorials/authenticate-api-requests.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contrib/docs/tutorials/authenticate-api-requests.md b/contrib/docs/tutorials/authenticate-api-requests.md index 8b71151ee9..838aaffef6 100644 --- a/contrib/docs/tutorials/authenticate-api-requests.md +++ b/contrib/docs/tutorials/authenticate-api-requests.md @@ -1,5 +1,14 @@ # Authenticate API requests +> [!CAUTION] +> This entire guide MUST NOT BE USED by users of Backstage 1.26 and +> newer. If you have applied the changes in this guide, you need to remove them +> again as you upgrade to recent versions of Backstage. When [the new auth +> changes](https://github.com/backstage/backstage/tree/master/beps/0003-auth-architecture-evolution) +> landed backends became natively secured through the framework, and the +> instructions outlined in here can interfere with the backend functioning +> correctly. + The Backstage backend APIs are by default available without authentication. To avoid evil-doers from accessing or modifying data, one might use a network protection mechanism such as a firewall or an authenticating reverse proxy. For Backstage instances that are available on the Internet one can instead use the experimental IdentityClient as outlined below. API requests from frontend plugins include an authorization header with a Backstage identity token acquired when the user logs in. By adding a middleware that verifies said token to be valid and signed by Backstage, non-authenticated requests can be blocked with a 401 Unauthorized response. From 09f89885bbdb45a82569096153a91ee745f8e9c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Wed, 24 Apr 2024 18:36:24 +0200 Subject: [PATCH 128/234] Remove explicit alg check for user tokens in verifyToken MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- .changeset/loud-timers-flow.md | 5 +++++ .../implementations/auth/user/UserTokenHandler.ts | 11 ++--------- 2 files changed, 7 insertions(+), 9 deletions(-) create mode 100644 .changeset/loud-timers-flow.md diff --git a/.changeset/loud-timers-flow.md b/.changeset/loud-timers-flow.md new file mode 100644 index 0000000000..d7d0d96970 --- /dev/null +++ b/.changeset/loud-timers-flow.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Remove explicit `alg` check for user tokens in `verifyToken` diff --git a/packages/backend-app-api/src/services/implementations/auth/user/UserTokenHandler.ts b/packages/backend-app-api/src/services/implementations/auth/user/UserTokenHandler.ts index af3faae22d..bd73d8701f 100644 --- a/packages/backend-app-api/src/services/implementations/auth/user/UserTokenHandler.ts +++ b/packages/backend-app-api/src/services/implementations/auth/user/UserTokenHandler.ts @@ -34,18 +34,14 @@ import { JwksClient } from '../JwksClient'; */ export class UserTokenHandler { static create(options: { discovery: DiscoveryService }): UserTokenHandler { - const algorithms = ['ES256']; // TODO: configurable? const jwksClient = new JwksClient(async () => { const url = await options.discovery.getBaseUrl('auth'); return new URL(`${url}/.well-known/jwks.json`); }); - return new UserTokenHandler(algorithms, jwksClient); + return new UserTokenHandler(jwksClient); } - constructor( - private readonly algorithms: string[], - private readonly jwksClient: JwksClient, - ) {} + constructor(private readonly jwksClient: JwksClient) {} async verifyToken(token: string) { const verifyOpts = this.#getTokenVerificationOptions(token); @@ -79,7 +75,6 @@ export class UserTokenHandler { if (typ === tokenTypes.user.typParam) { return { - algorithms: this.algorithms, requiredClaims: ['iat', 'exp', 'sub'], typ: tokenTypes.user.typParam, }; @@ -87,7 +82,6 @@ export class UserTokenHandler { if (typ === tokenTypes.limitedUser.typParam) { return { - algorithms: this.algorithms, requiredClaims: ['iat', 'exp', 'sub'], typ: tokenTypes.limitedUser.typParam, }; @@ -96,7 +90,6 @@ export class UserTokenHandler { const { aud } = decodeJwt(token); if (aud === tokenTypes.user.audClaim) { return { - algorithms: this.algorithms, audience: tokenTypes.user.audClaim, }; } From 0329b2514b3790413f53bb0f5012f816718f094f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Thu, 25 Apr 2024 09:57:53 +0200 Subject: [PATCH 129/234] add test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- .../auth/user/UserTokenHandler.test.ts | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/backend-app-api/src/services/implementations/auth/user/UserTokenHandler.test.ts b/packages/backend-app-api/src/services/implementations/auth/user/UserTokenHandler.test.ts index f2a86e4a20..b0e8418655 100644 --- a/packages/backend-app-api/src/services/implementations/auth/user/UserTokenHandler.test.ts +++ b/packages/backend-app-api/src/services/implementations/auth/user/UserTokenHandler.test.ts @@ -133,6 +133,34 @@ describe('UserTokenHandler', () => { ).rejects.toThrow('signature verification failed'); }); + it('should fail to verify tokens that have a bad alg', async () => { + const expectedIssuedAt = 1712071714; + const expectedExpiresAt = 1712075314; + + jest.useFakeTimers({ + now: expectedIssuedAt * 1000 + 600_000, + }); + + const header = encodeData({ + typ: 'vnd.backstage.user', + alg: 'none', + }); + const payload = encodeData({ + iss: 'http://localhost:7007/api/auth', + sub: 'user:development/guest', + ent: ['user:development/guest', 'group:default/team-a'], + aud: 'backstage', + iat: expectedIssuedAt, + exp: expectedExpiresAt, + uip: 'proof', + }); + const token = `${header}.${payload}.`; + + await expect(userTokenHandler.verifyToken(token)).rejects.toThrow( + /Unsupported "alg" value/, + ); + }); + it('should verify a valid legacy backstage token', async () => { const expectedIssuedAt = 1712071714; const expectedExpiresAt = 1712075314; @@ -151,7 +179,7 @@ describe('UserTokenHandler', () => { sub: 'user:development/guest', ent: ['user:development/guest', 'group:default/team-a'], aud: 'backstage', - iat: 1712071714, + iat: expectedIssuedAt, exp: expectedExpiresAt, }, }; @@ -179,7 +207,7 @@ describe('UserTokenHandler', () => { iss: 'http://localhost:7007/api/auth', ent: ['user:development/guest', 'group:default/team-a'], aud: 'backstage', - iat: 1712071714, + iat: expectedIssuedAt, exp: expectedExpiresAt, }, }; @@ -209,7 +237,7 @@ describe('UserTokenHandler', () => { sub: 'user:development/guest', ent: ['user:development/guest', 'group:default/team-a'], aud: 'backstage', - iat: 1712071714, + iat: expectedIssuedAt, exp: expectedExpiresAt, uip: 'proof', }, @@ -239,7 +267,7 @@ describe('UserTokenHandler', () => { payload: { sub: 'user:development/guest', ent: ['user:development/guest', 'group:default/team-a'], - iat: 1712071714, + iat: expectedIssuedAt, exp: expectedExpiresAt, }, }; From 73a963492e5453a118ede38cb790ccf3a8be438e Mon Sep 17 00:00:00 2001 From: Himesh Ladva <himeshladva@gmail.com> Date: Thu, 25 Apr 2024 09:52:04 +0100 Subject: [PATCH 130/234] chore(docs): update openapi generate-client command Signed-off-by: Himesh Ladva <himeshladva@gmail.com> --- docs/openapi/generate-client.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/openapi/generate-client.md b/docs/openapi/generate-client.md index 640036960e..b8acc331e5 100644 --- a/docs/openapi/generate-client.md +++ b/docs/openapi/generate-client.md @@ -20,7 +20,7 @@ info: ### Generating your client -1. Run `yarn backstage-repo-tools schema openapi generate client --output-package <directory>`. This will create a new folder in `<directory>/src/generated` to house the generated content. +1. Run `yarn backstage-repo-tools package schema openapi generate client --client-package <directory>`. This will create a new folder in `<directory>/src/generated` to house the generated content. 2. You should use the generated files as follows, - `apis/DefaultApi.client.ts` - this is the client that you should use. It has types for all of the various operations on your API. From 5223c4ce63251d53ae2db8a657eb5aae9e95386f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Thu, 25 Apr 2024 12:39:57 +0200 Subject: [PATCH 131/234] core-components: fix circular import Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- .changeset/fuzzy-seahorses-tell.md | 5 +++++ .../core-components/src/layout/HeaderTabs/HeaderTabs.tsx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/fuzzy-seahorses-tell.md diff --git a/.changeset/fuzzy-seahorses-tell.md b/.changeset/fuzzy-seahorses-tell.md new file mode 100644 index 0000000000..f184e9ce95 --- /dev/null +++ b/.changeset/fuzzy-seahorses-tell.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Fixed an internal circular import that broke Jest mocks. diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx index 852622c741..9ee9c5525f 100644 --- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx +++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx @@ -18,7 +18,7 @@ import { makeStyles } from '@material-ui/core/styles'; import TabUI, { TabProps } from '@material-ui/core/Tab'; import Tabs from '@material-ui/core/Tabs'; import React, { useCallback, useEffect, useState } from 'react'; -import { Link } from '@backstage/core-components'; +import { Link } from '../../components/Link'; // TODO(blam): Remove this implementation when the Tabs are ready // This is just a temporary solution to implementing tabs for now From 8479a0be3fb631116e6cb3a8ee5857fc085fc89f Mon Sep 17 00:00:00 2001 From: zcmander <zcmander@gmail.com> Date: Thu, 25 Apr 2024 14:11:18 +0300 Subject: [PATCH 132/234] fix: stitching queue gauge to not include entities from the future Signed-off-by: zcmander <zcmander@gmail.com> --- .changeset/sweet-zoos-clap.md | 5 +++++ plugins/catalog-backend/src/stitching/progressTracker.ts | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 .changeset/sweet-zoos-clap.md diff --git a/.changeset/sweet-zoos-clap.md b/.changeset/sweet-zoos-clap.md new file mode 100644 index 0000000000..8c76f4abd7 --- /dev/null +++ b/.changeset/sweet-zoos-clap.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fixed bug in stitching queue gauge that included entities that are scheduled in the future. diff --git a/plugins/catalog-backend/src/stitching/progressTracker.ts b/plugins/catalog-backend/src/stitching/progressTracker.ts index 9e00a1a01b..8c305b4978 100644 --- a/plugins/catalog-backend/src/stitching/progressTracker.ts +++ b/plugins/catalog-backend/src/stitching/progressTracker.ts @@ -54,7 +54,8 @@ export function progressTracker(knex: Knex, logger: LoggerService) { stitchingQueueCount.addCallback(async result => { const total = await knex<DbRefreshStateRow>('refresh_state') .count({ count: '*' }) - .whereNotNull('next_stitch_at'); + .whereNotNull('next_stitch_at') + .where('next_stitch_at', '<=', knex.fn.now()); result.observe(Number(total[0].count)); }); From 6d89bf4f720985c896778a4f4d4617c99529d071 Mon Sep 17 00:00:00 2001 From: secustor <sebastian@poxhofer.at> Date: Thu, 25 Apr 2024 19:59:51 +0200 Subject: [PATCH 133/234] prettier fix Signed-off-by: secustor <sebastian@poxhofer.at> --- .../src/UnprocessedEntitiesModule.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts b/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts index 6f0fa7d2e5..34aedd61af 100644 --- a/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts +++ b/plugins/catalog-backend-module-unprocessed/src/UnprocessedEntitiesModule.ts @@ -162,7 +162,8 @@ export class UnprocessedEntitiesModule { return res.json( await this.unprocessed({ reason: 'failed', - owner: typeof req.query.owner === 'string' ? req.query.owner : undefined, + owner: + typeof req.query.owner === 'string' ? req.query.owner : undefined, }), ); }) @@ -170,7 +171,8 @@ export class UnprocessedEntitiesModule { return res.json( await this.unprocessed({ reason: 'pending', - owner: typeof req.query.owner === 'string' ? req.query.owner : undefined + owner: + typeof req.query.owner === 'string' ? req.query.owner : undefined, }), ); }) From 3ef8cbc9cc4cf99782aedbfaabc6fe3344ddf4c7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 00:20:29 +0000 Subject: [PATCH 134/234] chore(deps): update github/codeql-action action to v3.25.3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/scorecard.yml | 2 +- .github/workflows/sync_snyk-monitor.yml | 2 +- .github/workflows/verify_codeql.yml | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 91f1b02fee..67fdff4020 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -66,6 +66,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 + uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 70fb8a44d9..e2507996b1 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@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 + uses: github/codeql-action/upload-sarif@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 with: sarif_file: snyk.sarif diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index e82577763e..920c9bab17 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@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 + uses: github/codeql-action/init@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 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@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 + uses: github/codeql-action/autobuild@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 # ℹ️ 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@c7f9125735019aa87cfc361530512d50ea439c71 # v3.25.1 + uses: github/codeql-action/analyze@d39d31e687223d841ef683f52467bd88e9b21c14 # v3.25.3 From a9ac73a8eeba54995d88c8c726b0661c89b1e449 Mon Sep 17 00:00:00 2001 From: Aditya Kumar <aditya.kumar60@infosys.com> Date: Fri, 26 Apr 2024 09:35:46 +0530 Subject: [PATCH 135/234] fix: fix deadlinks on microsite Signed-off-by: Aditya Kumar <aditya.kumar60@infosys.com> --- microsite/data/plugins/airbrake.yaml | 2 +- microsite/data/plugins/analytics-module-ga.yaml | 2 +- microsite/data/plugins/apollo-explorer.yaml | 2 +- microsite/data/plugins/azure-pipelines.yaml | 2 +- microsite/data/plugins/badges.yaml | 2 +- microsite/data/plugins/bazaar.yaml | 2 +- microsite/data/plugins/bitrise.yaml | 2 +- microsite/data/plugins/firehydrant.yaml | 4 ++-- microsite/data/plugins/fossa.yaml | 2 +- microsite/data/plugins/github-codespaces.yaml | 2 +- microsite/data/plugins/linguist.yaml | 2 +- microsite/data/plugins/newrelic-dashboard.yaml | 2 +- microsite/data/plugins/octopus-deploy.yaml | 2 +- microsite/data/plugins/opencost.yaml | 2 +- microsite/data/plugins/puppetdb.yaml | 2 +- microsite/data/plugins/shortcuts.yaml | 2 +- microsite/data/plugins/sonarqube.yaml | 2 +- microsite/data/plugins/stack-overflow.yaml | 2 +- 18 files changed, 19 insertions(+), 19 deletions(-) diff --git a/microsite/data/plugins/airbrake.yaml b/microsite/data/plugins/airbrake.yaml index 7f946597ef..8facf13185 100644 --- a/microsite/data/plugins/airbrake.yaml +++ b/microsite/data/plugins/airbrake.yaml @@ -4,7 +4,7 @@ author: Simply Business authorUrl: https://github.com/simplybusiness/ category: Monitoring description: Access Airbrake error monitoring and other integrations from within Backstage -documentation: https://github.com/backstage/backstage/blob/master/plugins/airbrake +documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/airbrake/plugins/airbrake iconUrl: https://wp-assets.airbrake.io/wp-content/uploads/2020/10/05222904/Square-white-A-on-Orange.png npmPackageName: '@backstage/plugin-airbrake' addedDate: '2022-01-10' diff --git a/microsite/data/plugins/analytics-module-ga.yaml b/microsite/data/plugins/analytics-module-ga.yaml index 54dea6454a..968b1831ab 100644 --- a/microsite/data/plugins/analytics-module-ga.yaml +++ b/microsite/data/plugins/analytics-module-ga.yaml @@ -4,7 +4,7 @@ author: Spotify authorUrl: https://github.com/spotify category: Monitoring description: Track usage of your Backstage instance using Google Analytics. -documentation: https://github.com/backstage/backstage/blob/master/plugins/analytics-module-ga/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/analytics/plugins/analytics-module-ga/README.md iconUrl: /img/ga-icon.png npmPackageName: '@backstage/plugin-analytics-module-ga' addedDate: '2021-10-07' diff --git a/microsite/data/plugins/apollo-explorer.yaml b/microsite/data/plugins/apollo-explorer.yaml index 98008e3fc0..cebf36654e 100644 --- a/microsite/data/plugins/apollo-explorer.yaml +++ b/microsite/data/plugins/apollo-explorer.yaml @@ -4,7 +4,7 @@ author: unredundant authorUrl: https://github.com/unredundant category: Debugging description: Integrates Apollo Explorer graphs as a tool to browse GraphQL API endpoints inside Backstage. -documentation: https://github.com/backstage/backstage/blob/master/plugins/apollo-explorer/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/apollo-explorer/plugins/apollo-explorer/README.md iconUrl: /img/apollo-explorer.png npmPackageName: '@backstage/plugin-apollo-explorer' addedDate: '2022-07-20' diff --git a/microsite/data/plugins/azure-pipelines.yaml b/microsite/data/plugins/azure-pipelines.yaml index 5b719a4248..e0e3534fdb 100644 --- a/microsite/data/plugins/azure-pipelines.yaml +++ b/microsite/data/plugins/azure-pipelines.yaml @@ -4,7 +4,7 @@ author: Keyloop authorUrl: https://keyloop.com/ category: CI/CD description: Easily view your Azure Pipelines within the Software Catalog -documentation: https://github.com/backstage/backstage/blob/master/plugins/azure-devops/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/azure-devops/plugins/azure-devops/README.md iconUrl: /img/azure-pipelines.svg npmPackageName: '@backstage/plugin-azure-devops' addedDate: '2021-12-22' diff --git a/microsite/data/plugins/badges.yaml b/microsite/data/plugins/badges.yaml index 86d16ab547..789aaf155d 100644 --- a/microsite/data/plugins/badges.yaml +++ b/microsite/data/plugins/badges.yaml @@ -4,7 +4,7 @@ author: Andreas Stenius authorUrl: https://github.com/backstage/community category: Discovery description: The badges plugin offers a set of badges that can be used outside of Backstage, showing information related to data from the catalog. -documentation: https://github.com/backstage/backstage/blob/master/plugins/badges/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/badges/plugins/badges/README.md iconUrl: /img/badges.svg npmPackageName: '@backstage/plugin-badges' addedDate: '2021-09-29' diff --git a/microsite/data/plugins/bazaar.yaml b/microsite/data/plugins/bazaar.yaml index ce3a4d5dfc..1130822e77 100644 --- a/microsite/data/plugins/bazaar.yaml +++ b/microsite/data/plugins/bazaar.yaml @@ -3,7 +3,7 @@ author: Axis Communications AB authorUrl: https://www.axis.com category: Discovery description: A marketplace where engineers can propose projects suitable for inner sourcing -documentation: https://github.com/backstage/backstage/blob/master/plugins/bazaar/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/bazaar/plugins/bazaar/README.md iconUrl: /img/bazaar.svg npmPackageName: '@backstage/plugin-bazaar' addedDate: '2022-01-11' diff --git a/microsite/data/plugins/bitrise.yaml b/microsite/data/plugins/bitrise.yaml index 5e5b74f3ad..5cbfe5aabf 100644 --- a/microsite/data/plugins/bitrise.yaml +++ b/microsite/data/plugins/bitrise.yaml @@ -4,7 +4,7 @@ author: SDA SE authorUrl: https://sda.se/ category: CI/CD description: View Bitrise builds and download the build artifacts within Backstage. -documentation: https://github.com/backstage/backstage/blob/master/plugins/bitrise/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/bitrise/plugins/bitrise/README.md iconUrl: https://avatars.githubusercontent.com/u/7174390?s=400&v=4 npmPackageName: '@backstage/plugin-bitrise' addedDate: '2021-03-01' diff --git a/microsite/data/plugins/firehydrant.yaml b/microsite/data/plugins/firehydrant.yaml index 8a1f122051..7aa00675b4 100644 --- a/microsite/data/plugins/firehydrant.yaml +++ b/microsite/data/plugins/firehydrant.yaml @@ -4,7 +4,7 @@ author: FireHydrant authorUrl: https://firehydrant.io/ category: Monitoring description: View service incidents information from FireHydrant, such as active incidents and incident metrics, directly within Backstage. -documentation: https://github.com/backstage/backstage/blob/master/plugins/firehydrant/README.md -iconUrl: https://raw.githubusercontent.com/backstage/backstage/master/plugins/firehydrant/doc/firehydrant_logo.png +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/firehydrant/plugins/firehydrant/README.md +iconUrl: https://github.com/backstage/community-plugins/blob/main/workspaces/firehydrant/plugins/firehydrant/doc/firehydrant_logo.png npmPackageName: '@backstage/plugin-firehydrant' addedDate: '2021-08-18' diff --git a/microsite/data/plugins/fossa.yaml b/microsite/data/plugins/fossa.yaml index e46228a848..a1a66eb7d6 100644 --- a/microsite/data/plugins/fossa.yaml +++ b/microsite/data/plugins/fossa.yaml @@ -4,7 +4,7 @@ author: SDA SE authorUrl: https://sda.se/ category: Quality description: View FOSSA license compliance of your components in Backstage. -documentation: https://github.com/backstage/backstage/blob/master/plugins/fossa/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/fossa/plugins/fossa/README.md iconUrl: https://avatars0.githubusercontent.com/u/9543448?s=400&v=4 npmPackageName: '@backstage/plugin-fossa' addedDate: '2020-12-10' diff --git a/microsite/data/plugins/github-codespaces.yaml b/microsite/data/plugins/github-codespaces.yaml index d846235bd6..043be3200b 100644 --- a/microsite/data/plugins/github-codespaces.yaml +++ b/microsite/data/plugins/github-codespaces.yaml @@ -5,7 +5,7 @@ authorUrl: https://github.com/adityasinghal26 category: Development description: Integrates GitHub Codespaces for a Backstage component with the Authenticated User. documentation: https://github.com/adityasinghal26/backstage-plugins/tree/main/plugins/github-codespaces -iconUrl: https://github.com/adityasinghal26/backstage-plugins/blob/00c9c00ba9acc3135014d6454ccf04f573195eef/plugins/github-codespaces/images/GitHubLogo.png +iconUrl: https://github.com/adityasinghal26/backstage-plugins/blob/main/plugins/github-codespaces/images/GitHubLogo.png npmPackageName: '@adityasinghal26/plugin-github-codespaces' tags: - github diff --git a/microsite/data/plugins/linguist.yaml b/microsite/data/plugins/linguist.yaml index d76936391c..fb8e88b747 100644 --- a/microsite/data/plugins/linguist.yaml +++ b/microsite/data/plugins/linguist.yaml @@ -4,7 +4,7 @@ author: Keyloop authorUrl: https://keyloop.com/ category: Metadata description: View the programming language break down for your entities within the Software Catalog -documentation: https://github.com/backstage/backstage/blob/master/plugins/linguist/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/linguist/plugins/linguist/README.md iconUrl: /img/linguist.svg npmPackageName: '@backstage/plugin-linguist' addedDate: '2023-06-17' diff --git a/microsite/data/plugins/newrelic-dashboard.yaml b/microsite/data/plugins/newrelic-dashboard.yaml index 420c506b35..e53285cca7 100644 --- a/microsite/data/plugins/newrelic-dashboard.yaml +++ b/microsite/data/plugins/newrelic-dashboard.yaml @@ -4,7 +4,7 @@ author: '@mufaddal7' authorUrl: https://github.com/mufaddal7 category: Monitoring description: Easily view your New Relic Dashboards in Backstage, via real-time snapshots of your dashboards -documentation: https://github.com/backstage/community-plugins/tree/main/workspaces/newrelic-dashboard/plugins/newrelic-dashboard +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/newrelic/plugins/newrelic-dashboard/README.md iconUrl: https://newrelic.com/themes/custom/erno/assets/mediakit/new_relic_logo_vertical_white.svg npmPackageName: '@backstage-community/plugin-newrelic-dashboard' tags: diff --git a/microsite/data/plugins/octopus-deploy.yaml b/microsite/data/plugins/octopus-deploy.yaml index 1ed7439695..cfc5e2c9e5 100644 --- a/microsite/data/plugins/octopus-deploy.yaml +++ b/microsite/data/plugins/octopus-deploy.yaml @@ -4,7 +4,7 @@ author: jmezach authorUrl: https://jmezach.github.io/ category: CI/CD description: Easily view your Octopus Deploy releases within the Software Catalog -documentation: https://github.com/backstage/backstage/blob/master/plugins/octopus-deploy/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/octopus-deploy/plugins/octopus-deploy/README.md iconUrl: /img/octopus-deploy.svg npmPackageName: '@backstage/plugin-octopus-deploy' addedDate: '2023-02-24' diff --git a/microsite/data/plugins/opencost.yaml b/microsite/data/plugins/opencost.yaml index 950b0606a7..95973e3b0c 100644 --- a/microsite/data/plugins/opencost.yaml +++ b/microsite/data/plugins/opencost.yaml @@ -4,7 +4,7 @@ author: Matt Ray authorUrl: https://opencost.io category: Monitoring description: OpenCost provides cloud cost monitoring for your cloud native environments. -documentation: https://github.com/backstage/backstage/blob/master/plugins/opencost/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/opencost/plugins/opencost/README.md iconUrl: /img/opencost.png npmPackageName: '@backstage/plugin-opencost' addedDate: '2023-10-26' diff --git a/microsite/data/plugins/puppetdb.yaml b/microsite/data/plugins/puppetdb.yaml index d3b6bd2bcb..650d8318d3 100644 --- a/microsite/data/plugins/puppetdb.yaml +++ b/microsite/data/plugins/puppetdb.yaml @@ -4,7 +4,7 @@ author: TDabasinskas authorUrl: https://github.com/tdabasinskas category: Configuration Management description: Visualize resource information and Puppet facts from PuppetDB. -documentation: https://github.com/backstage/backstage/blob/master/plugins/puppetdb/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/puppetdb/plugins/puppetdb/README.md iconUrl: /img/puppet.png npmPackageName: '@backstage/plugin-puppetdb' tags: diff --git a/microsite/data/plugins/shortcuts.yaml b/microsite/data/plugins/shortcuts.yaml index 7c3f2c6d56..b420e28e2f 100644 --- a/microsite/data/plugins/shortcuts.yaml +++ b/microsite/data/plugins/shortcuts.yaml @@ -4,7 +4,7 @@ author: Spotify authorUrl: https://github.com/spotify category: Utility description: The shortcuts plugin allows a user to have easy access to pages within a Backstage app by storing them as "shortcuts" in the Sidebar. -documentation: https://github.com/backstage/backstage/blob/master/plugins/shortcuts/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/shortcuts/plugins/shortcuts/README.md iconUrl: /img/shortcuts.svg npmPackageName: '@backstage/plugin-shortcuts' addedDate: '2021-10-06' diff --git a/microsite/data/plugins/sonarqube.yaml b/microsite/data/plugins/sonarqube.yaml index f24441c46e..72c662e2fa 100644 --- a/microsite/data/plugins/sonarqube.yaml +++ b/microsite/data/plugins/sonarqube.yaml @@ -4,7 +4,7 @@ author: SDA SE authorUrl: https://sda.se/ category: Quality description: Components to display code quality metrics from SonarCloud and SonarQube. -documentation: https://github.com/backstage/backstage/blob/master/plugins/sonarqube/README.md +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/sonarqube/plugins/sonarqube/README.md iconUrl: /img/sonarqube-icon.svg npmPackageName: '@backstage/plugin-sonarqube' addedDate: '2020-11-03' diff --git a/microsite/data/plugins/stack-overflow.yaml b/microsite/data/plugins/stack-overflow.yaml index a5e2122f8a..b058ce90fe 100644 --- a/microsite/data/plugins/stack-overflow.yaml +++ b/microsite/data/plugins/stack-overflow.yaml @@ -4,7 +4,7 @@ author: Spotify authorUrl: https://github.com/spotify category: Discovery description: Provides Stack Overflow specific functionality that can be used in different ways (e.g. for homepage and search) to compose your Backstage App. -documentation: https://github.com/backstage/backstage/blob/master/plugins/stack-overflow +documentation: https://github.com/backstage/community-plugins/blob/main/workspaces/stack-overflow/plugins/stack-overflow/README.md iconUrl: /img/stack-overflow-logo.svg npmPackageName: '@backstage/plugin-stack-overflow' addedDate: '2022-06-14' From f6633ca8d233c7f63c65312095c80a43c964a835 Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Thu, 25 Apr 2024 08:48:08 +0300 Subject: [PATCH 136/234] feat: allow setting notification as read automatically when opening link Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .changeset/young-guests-reflect.md | 5 +++++ plugins/notifications/api-report.md | 3 +++ .../NotificationsPage/NotificationsPage.tsx | 4 ++++ .../NotificationsTable/NotificationsTable.tsx | 14 ++++++++++++-- 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 .changeset/young-guests-reflect.md diff --git a/.changeset/young-guests-reflect.md b/.changeset/young-guests-reflect.md new file mode 100644 index 0000000000..355c38bc21 --- /dev/null +++ b/.changeset/young-guests-reflect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Add option to set the notification as read automatically when the notification link is opened diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index 8fbd148b6d..3699aa1bb0 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -80,6 +80,7 @@ export const NotificationsPage: ( // @public (undocumented) export type NotificationsPageProps = { + markAsReadOnLinkOpen?: boolean; title?: string; themeId?: string; subtitle?: string; @@ -110,6 +111,7 @@ export const NotificationsSidebarItem: (props?: { // @public (undocumented) export const NotificationsTable: ({ + markAsReadOnLinkOpen, isLoading, notifications, isUnread, @@ -127,6 +129,7 @@ export type NotificationsTableProps = Pick< TableProps, 'onPageChange' | 'onRowsPerPageChange' | 'page' | 'totalCount' > & { + markAsReadOnLinkOpen?: boolean; isLoading?: boolean; isUnread: boolean; notifications?: Notification_2[]; diff --git a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx index f1dd9aba5f..d1dde185f2 100644 --- a/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx +++ b/plugins/notifications/src/components/NotificationsPage/NotificationsPage.tsx @@ -43,6 +43,8 @@ const ThrottleDelayMs = 2000; /** @public */ export type NotificationsPageProps = { + /** Mark notification as read when opening the link it contains, defaults to false */ + markAsReadOnLinkOpen?: boolean; title?: string; themeId?: string; subtitle?: string; @@ -59,6 +61,7 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { tooltip, type, typeLink, + markAsReadOnLinkOpen, } = props ?? {}; const [refresh, setRefresh] = React.useState(false); @@ -171,6 +174,7 @@ export const NotificationsPage = (props?: NotificationsPageProps) => { <NotificationsTable isLoading={loading} isUnread={isUnread} + markAsReadOnLinkOpen={markAsReadOnLinkOpen} notifications={notifications} onUpdate={onUpdate} setContainsText={setContainsText} diff --git a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx index f8091a021f..db2b351432 100644 --- a/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx +++ b/plugins/notifications/src/components/NotificationsTable/NotificationsTable.tsx @@ -28,8 +28,8 @@ import { alertApiRef, useApi } from '@backstage/core-plugin-api'; import { Link, Table, - TableProps, TableColumn, + TableProps, } from '@backstage/core-components'; import { notificationsApiRef } from '../../api'; @@ -55,6 +55,7 @@ export type NotificationsTableProps = Pick< TableProps, 'onPageChange' | 'onRowsPerPageChange' | 'page' | 'totalCount' > & { + markAsReadOnLinkOpen?: boolean; isLoading?: boolean; isUnread: boolean; notifications?: Notification[]; @@ -65,6 +66,7 @@ export type NotificationsTableProps = Pick< /** @public */ export const NotificationsTable = ({ + markAsReadOnLinkOpen, isLoading, notifications = [], isUnread, @@ -213,7 +215,14 @@ export const NotificationsTable = ({ <Box> <Typography variant="subtitle2"> {notification.payload.link ? ( - <Link to={notification.payload.link}> + <Link + to={notification.payload.link} + onClick={() => { + if (markAsReadOnLinkOpen && !notification.read) { + onSwitchReadStatus([notification.id], true); + } + }} + > {notification.payload.title} </Link> ) : ( @@ -267,6 +276,7 @@ export const NotificationsTable = ({ }, ], [ + markAsReadOnLinkOpen, selectedNotifications, notifications, isUnread, From 49eab297a30ca201a2c93314ce95450248d27077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= <g.rantila@gmail.com> Date: Fri, 26 Apr 2024 08:31:37 +0200 Subject: [PATCH 137/234] fix: Fixed disabling user photos fetching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä <g.rantila@gmail.com> --- .changeset/chilly-shoes-doubt.md | 5 +++ docs/integrations/azure/org.md | 12 ++++++ .../catalog-backend-module-msgraph/README.md | 2 + .../src/microsoftGraph/read.test.ts | 37 +++++++++++++++++++ .../MicrosoftGraphOrgEntityProvider.ts | 1 + .../MicrosoftGraphOrgReaderProcessor.ts | 1 + 6 files changed, 58 insertions(+) create mode 100644 .changeset/chilly-shoes-doubt.md diff --git a/.changeset/chilly-shoes-doubt.md b/.changeset/chilly-shoes-doubt.md new file mode 100644 index 0000000000..978600f5b4 --- /dev/null +++ b/.changeset/chilly-shoes-doubt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Fixed disabling of user photo fetching. Previously, the config value wasn't propagated properly, so user photos was still being fetched despite disabled by config. diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index 38c2e7618a..5e39aaeb7d 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -146,6 +146,18 @@ microsoftGraphOrg: search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")' ``` +### 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`: + +```yaml +microsoftGraphOrg: + providerId: + user: + filter: ... + loadPhotos: false +``` + ## Customizing Transformation Ingested entities can be customized by providing custom transformers. diff --git a/plugins/catalog-backend-module-msgraph/README.md b/plugins/catalog-backend-module-msgraph/README.md index e3ed302fa9..7ee416ecd4 100644 --- a/plugins/catalog-backend-module-msgraph/README.md +++ b/plugins/catalog-backend-module-msgraph/README.md @@ -54,6 +54,8 @@ catalog: # and for the syntax https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter # This and userGroupMemberFilter are mutually exclusive, only one can be specified filter: accountEnabled eq true and userType eq 'member' + # Set to false to not load user photos. + loadPhotos: true # See https://docs.microsoft.com/en-us/graph/api/resources/schemaextension?view=graph-rest-1.0 select: ['id', 'displayName', 'description'] # Optional configuration block 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 b1dfc5a148..a610df2c29 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts @@ -955,6 +955,43 @@ describe('read microsoft graph', () => { ); }); + it('should ignore loading photos if loadPhotos is false', async () => { + client.getOrganization.mockResolvedValue(getExampleOrg()); + + client.getUsers.mockImplementation(getExampleUsers); + client.getUserPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + client.getGroups.mockImplementation(getExampleGroups); + client.getGroupMembers.mockImplementation(getExampleGroupMembers); + client.getGroupPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + await readMicrosoftGraphOrg(client, 'tenantid', { + logger: getVoidLogger(), + loadUserPhotos: false, + }); + + expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(0); + + expect(client.getUsers).toHaveBeenCalledTimes(1); + expect(client.getUsers).toHaveBeenCalledWith( + { + top: 999, + }, + undefined, + ); + expect(client.getGroups).toHaveBeenCalledTimes(1); + expect(client.getGroups).toHaveBeenCalledWith( + { + top: 999, + }, + undefined, + ); + }); + it('should read users with userSelect', async () => { client.getOrganization.mockResolvedValue({ id: 'tenantid', diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index 9c804216e4..3c8b903e69 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -310,6 +310,7 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { userExpand: provider.userExpand, userFilter: provider.userFilter, userSelect: provider.userSelect, + loadUserPhotos: provider.loadUserPhotos, userGroupMemberFilter: provider.userGroupMemberFilter, userGroupMemberSearch: provider.userGroupMemberSearch, groupExpand: provider.groupExpand, diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts index 570959a381..c058286cb2 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts @@ -112,6 +112,7 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { { userExpand: provider.userExpand, userFilter: provider.userFilter, + loadUserPhotos: provider.loadUserPhotos, userGroupMemberFilter: provider.userGroupMemberFilter, userGroupMemberSearch: provider.userGroupMemberSearch, groupExpand: provider.groupExpand, From 6e370e62e9dd01e7effb70f61542044b89c8edfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustaf=20R=C3=A4ntil=C3=A4?= <g.rantila@gmail.com> Date: Fri, 26 Apr 2024 09:04:19 +0200 Subject: [PATCH 138/234] fix: Handle reading huge amounts of users without crashing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Gustaf Räntilä <g.rantila@gmail.com> --- .changeset/silent-wombats-hang.md | 5 ++ .../src/microsoftGraph/read.test.ts | 46 +++++++++++++++++++ .../src/microsoftGraph/read.ts | 6 +-- 3 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 .changeset/silent-wombats-hang.md diff --git a/.changeset/silent-wombats-hang.md b/.changeset/silent-wombats-hang.md new file mode 100644 index 0000000000..85bda32007 --- /dev/null +++ b/.changeset/silent-wombats-hang.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Handle fetching huge amounts of users from Azure without crashing 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 a610df2c29..9f4b9ee08c 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts @@ -1064,5 +1064,51 @@ describe('read microsoft graph', () => { ); expect(client.getUserPhotoWithSizeLimit).toHaveBeenCalledTimes(1); }); + + it('should handle loading huge amounts of users', async () => { + client.getOrganization.mockResolvedValue(getExampleOrg()); + + const userCount = 200_000; + + async function* getHugeAmountsOfExampleUsers() { + for (let i = 0; i < userCount; ++i) { + yield { + id: `userid-${i}`, + displayName: 'User Name', + mail: 'user.name@example.com', + }; + } + } + + client.getUsers.mockImplementation(getHugeAmountsOfExampleUsers); + + client.getGroups.mockImplementation(getExampleGroups); + client.getGroupMembers.mockImplementation(getExampleGroupMembers); + client.getGroupPhotoWithSizeLimit.mockResolvedValue( + 'data:image/jpeg;base64,...', + ); + + const { users } = await readMicrosoftGraphOrg(client, 'tenantid', { + logger: getVoidLogger(), + loadUserPhotos: false, + }); + + expect(users.length).toBe(userCount); + + expect(client.getUsers).toHaveBeenCalledTimes(1); + expect(client.getUsers).toHaveBeenCalledWith( + { + top: 999, + }, + undefined, + ); + expect(client.getGroups).toHaveBeenCalledTimes(1); + expect(client.getGroups).toHaveBeenCalledWith( + { + top: 999, + }, + undefined, + ); + }); }); }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 0f3d81c224..2ca21fca9c 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -384,7 +384,7 @@ export async function readMicrosoftGraphOrg( logger: LoggerService; }, ): Promise<{ users: UserEntity[]; groups: GroupEntity[] }> { - const users: UserEntity[] = []; + let users: UserEntity[] = []; if (options.userGroupMemberFilter || options.userGroupMemberSearch) { const { users: usersInGroups } = await readMicrosoftGraphUsersInGroups( @@ -401,7 +401,7 @@ export async function readMicrosoftGraphOrg( logger: options.logger, }, ); - users.push(...usersInGroups); + users = usersInGroups; } else { const { users: usersWithFilter } = await readMicrosoftGraphUsers(client, { queryMode: options.queryMode, @@ -412,7 +412,7 @@ export async function readMicrosoftGraphOrg( transformer: options.userTransformer, logger: options.logger, }); - users.push(...usersWithFilter); + users = usersWithFilter; } const { groups, rootGroup, groupMember, groupMemberOf } = await readMicrosoftGraphGroups(client, tenantId, { From ba765801da0547f1d991d2f0569e76a7d0eef771 Mon Sep 17 00:00:00 2001 From: npiyush97 <npiyush35@gmail.com> Date: Fri, 26 Apr 2024 22:52:06 +0530 Subject: [PATCH 139/234] microsite to v3 Signed-off-by: npiyush97 <npiyush35@gmail.com> --- docs/publishing.md | 2 +- microsite/docusaurus.config.js | 349 --------------------------------- microsite/docusaurus.config.ts | 349 +++++++++++++++++++++++++++++++++ microsite/package.json | 6 +- microsite/tsconfig.json | 2 +- microsite/yarn.lock | 33 ++-- 6 files changed, 368 insertions(+), 373 deletions(-) delete mode 100644 microsite/docusaurus.config.js create mode 100644 microsite/docusaurus.config.ts diff --git a/docs/publishing.md b/docs/publishing.md index 29853955f1..d7a4f65008 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -52,7 +52,7 @@ Additional steps for the main line release - Create Release Notes PR - Add the release note file as [`/docs/releases/vx.y.0.md`](./releases) - Add an entry to [`/microsite/sidebar.json`](https://github.com/backstage/backstage/blob/master/microsite/sidebars.json) for the release note - - Update the navigation bar item in [`/microsite/docusaurus.config.js`](https://github.com/backstage/backstage/blob/master/microsite/docusaurus.config.js) to point to the new release note + - Update the navigation bar item in [`/microsite/docusaurus.config.ts`](https://github.com/backstage/backstage/blob/master/microsite/docusaurus.config.ts) to point to the new release note - Finally copy the content, without the metadata header, into the description of the [`Version Packages` Pull Request](https://github.com/backstage/backstage/pulls?q=is%3Aopen+is%3Apr+in%3Atitle+%22Version+Packages) Once the release has been published edit the newly created release in the [GitHub repository](https://github.com/backstage/backstage/releases) and replace the text content with the release notes. diff --git a/microsite/docusaurus.config.js b/microsite/docusaurus.config.js deleted file mode 100644 index 1866deda5c..0000000000 --- a/microsite/docusaurus.config.js +++ /dev/null @@ -1,349 +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. - */ - -// @ts-check - -/** @type{import('prism-react-renderer').PrismTheme} **/ -// @ts-ignore -const prismTheme = require('prism-react-renderer/themes/vsDark'); -prismTheme.plain.backgroundColor = '#232323'; - -/** @type {import('@docusaurus/types').Config} */ -module.exports = { - title: 'Backstage Software Catalog and Developer Platform', - tagline: 'An open source framework for building developer portals', - url: 'https://backstage.io', - baseUrl: '/', - organizationName: 'Spotify', - projectName: 'backstage', - scripts: [ - 'https://buttons.github.io/buttons.js', - 'https://unpkg.com/medium-zoom@1.0.6/dist/medium-zoom.min.js', - '/js/medium-zoom.js', - '/js/dismissable-banner.js', - '/js/scroll-nav-to-view-in-docs.js', - ], - stylesheets: [ - 'https://fonts.googleapis.com/css?family=IBM+Plex+Mono:500,700&display=swap', - ], - favicon: 'img/favicon.ico', - customFields: { - fossWebsite: 'https://spotify.github.io/', - repoUrl: 'https://github.com/backstage/backstage', - }, - onBrokenLinks: 'log', - onBrokenMarkdownLinks: 'log', - presets: [ - [ - '@docusaurus/preset-classic', - /** @type {import('@docusaurus/preset-classic').Options} */ - { - docs: { - editUrl: 'https://github.com/backstage/backstage/edit/master/docs/', - path: '../docs', - sidebarPath: 'sidebars.json', - }, - blog: { - path: 'blog', - }, - theme: { - customCss: 'src/theme/customTheme.scss', - }, - gtag: { - trackingID: 'G-KSEVGGNCJW', - }, - }, - ], - ], - markdown: { - preprocessor({ fileContent }) { - // Replace all HTML comments with empty strings as these are not supported by MDXv2. - return fileContent.replace(/<!--.*?-->/gs, ''); - }, - format: 'detect', - }, - webpack: { - jsLoader: isServer => ({ - loader: require.resolve('swc-loader'), - options: { - jsc: { - parser: { - syntax: 'typescript', - tsx: true, - }, - target: 'es2017', - }, - module: { - type: isServer ? 'commonjs' : 'es6', - }, - }, - }), - }, - plugins: [ - 'docusaurus-plugin-sass', - () => ({ - name: 'yaml-loader', - configureWebpack() { - return { - module: { - rules: [ - { - test: /\.ya?ml$/, - use: 'yaml-loader', - }, - ], - }, - }; - }, - }), - [ - '@docusaurus/plugin-client-redirects', - { - redirects: [ - { - from: '/docs', - to: '/docs/overview/what-is-backstage', - }, - { - from: '/docs/features/software-catalog/software-catalog-overview', - to: '/docs/features/software-catalog/', - }, - { - from: '/docs/features/software-templates/software-templates-index', - to: '/docs/features/software-templates/', - }, - { - from: '/docs/features/techdocs/techdocs-overview', - to: '/docs/features/techdocs/', - }, - { - from: '/docs/features/kubernetes/overview', - to: '/docs/features/kubernetes/', - }, - { - from: '/docs/features/search/search-overview', - to: '/docs/features/search/', - }, - { - from: '/docs/getting-started/running-backstage-locally', - to: '/docs/getting-started/', - }, - { - from: '/docs/features/software-templates/testing-scaffolder-alpha', - to: '/docs/features/software-templates/migrating-to-rjsf-v5', - }, - { - from: '/docs/auth/glossary', - to: '/docs/references/glossary', - }, - { - from: '/docs/overview/glossary', - to: '/docs/references/glossary', - }, - { - from: '/docs/getting-started/create-an-app', - to: '/docs/getting-started/', - }, - { - from: '/docs/getting-started/configuration', - to: '/docs/getting-started/#next-steps', - }, - ], - }, - ], - [ - 'docusaurus-pushfeedback', - { - project: 'q8w1i6cair', - hideIcon: true, - customFont: true, - buttonStyle: 'dark', - }, - ], - ], - themeConfig: - /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ - { - colorMode: { - defaultMode: 'dark', - disableSwitch: true, - }, - navbar: { - logo: { - alt: 'Backstage Software Catalog and Developer Platform', - src: 'img/logo.svg', - }, - items: [ - { - href: 'https://github.com/backstage/backstage', - label: 'GitHub', - position: 'left', - }, - { - to: 'docs/overview/what-is-backstage', - label: 'Docs', - position: 'left', - }, - { - to: '/plugins', - label: 'Plugins', - position: 'left', - }, - { - to: '/blog', - label: 'Blog', - position: 'left', - }, - { - to: 'docs/releases/v1.26.0', - label: 'Releases', - position: 'left', - }, - { - to: '/demos', - label: 'Demos', - position: 'left', - }, - { - to: '/community', - label: 'Community', - position: 'left', - }, - ], - }, - image: 'img/sharing-opengraph.png', - footer: { - links: [ - { - items: [ - { - html: ` - <a href="/" aria-label="Backstage Home"> - <div class="footerLogo"></div> - </a>`, - }, - ], - }, - { - title: 'Docs', - items: [ - { - label: 'What is Backstage?', - to: 'docs/overview/what-is-backstage', - }, - { - label: 'Getting started', - to: 'docs/getting-started/', - }, - { - label: 'Software Catalog', - to: 'docs/features/software-catalog/', - }, - { - label: 'Create a Plugin', - to: 'docs/plugins/create-a-plugin', - }, - { - label: 'Designing for Backstage', - to: 'docs/dls/design', - }, - ], - }, - { - title: 'Community', - items: [ - { - label: 'Support chatroom', - to: 'https://discord.gg/backstage-687207715902193673', - }, - { - label: 'Contributing', - to: 'https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md', - }, - { - label: 'Adopting', - to: 'https://backstage.spotify.com', - }, - { - label: 'Subscribe to our newsletter', - to: 'https://info.backstage.spotify.com/newsletter_subscribe', - }, - { - label: 'CNCF Incubation', - to: 'https://www.cncf.io/projects/', - }, - ], - }, - { - 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/', - }, - ], - }, - ], - copyright: - '<p style="text-align:center"><a href="https://spotify.github.io/">Made with ❤️ at Spotify</a></p><p class="copyright">Copyright © 2024 Backstage Project Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage</p>', - }, - algolia: { - apiKey: '1f0ba86672ccfc3576faa94583e5b318', - indexName: 'crawler_Backstage Docusaurus 2', - appId: 'JCMFNHCHI8', - searchParameters: {}, - }, - prism: { - theme: prismTheme, - // Supported languages: https://prismjs.com/#supported-languages - // Default languages: https://github.com/FormidableLabs/prism-react-renderer/blob/master/packages/generate-prism-languages/index.ts#L9-L23 - additionalLanguages: ['docker', 'bash'], - magicComments: [ - // Extend the default highlight class name - { - className: 'code-block-highlight-line', - line: 'highlight-next-line', - block: { start: 'highlight-start', end: 'highlight-end' }, - }, - { - className: 'code-block-add-line', - line: 'highlight-add-next-line', - block: { start: 'highlight-add-start', end: 'highlight-add-end' }, - }, - { - className: 'code-block-remove-line', - line: 'highlight-remove-next-line', - block: { - start: 'highlight-remove-start', - end: 'highlight-remove-end', - }, - }, - ], - }, - }, -}; diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts new file mode 100644 index 0000000000..256784ddcc --- /dev/null +++ b/microsite/docusaurus.config.ts @@ -0,0 +1,349 @@ +/* + * 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. + */ + +// @ts-check + +/** @type{import('prism-react-renderer').PrismTheme} **/ +// @ts-ignore +import { themes } from 'prism-react-renderer'; +import type * as Preset from '@docusaurus/preset-classic'; +import { Config } from '@docusaurus/types'; +const backstageTheme = themes.vsDark; +backstageTheme.plain.backgroundColor = '#232323'; + +const config: Config = { + title: 'Backstage Software Catalog and Developer Platform', + tagline: 'An open source framework for building developer portals', + url: 'https://backstage.io', + baseUrl: '/', + organizationName: 'Spotify', + projectName: 'backstage', + scripts: [ + 'https://buttons.github.io/buttons.js', + 'https://unpkg.com/medium-zoom@1.0.6/dist/medium-zoom.min.js', + '/js/medium-zoom.js', + '/js/dismissable-banner.js', + '/js/scroll-nav-to-view-in-docs.js', + ], + stylesheets: [ + 'https://fonts.googleapis.com/css?family=IBM+Plex+Mono:500,700&display=swap', + ], + favicon: 'img/favicon.ico', + customFields: { + fossWebsite: 'https://spotify.github.io/', + repoUrl: 'https://github.com/backstage/backstage', + }, + onBrokenLinks: 'log', + onBrokenMarkdownLinks: 'log', + presets: [ + [ + '@docusaurus/preset-classic', + /** @type {import('@docusaurus/preset-classic').Options} */ + { + docs: { + editUrl: 'https://github.com/backstage/backstage/edit/master/docs/', + path: '../docs', + sidebarPath: 'sidebars.json', + }, + blog: { + path: 'blog', + }, + theme: { + customCss: 'src/theme/customTheme.scss', + }, + gtag: { + trackingID: 'G-KSEVGGNCJW', + }, + }, + ], + ], + markdown: { + preprocessor({ fileContent }) { + // Replace all HTML comments with empty strings as these are not supported by MDXv2. + return fileContent.replace(/<!--.*?-->/gs, ''); + }, + format: 'detect', + }, + webpack: { + jsLoader: isServer => ({ + loader: require.resolve('swc-loader'), + options: { + jsc: { + parser: { + syntax: 'typescript', + tsx: true, + }, + target: 'es2017', + }, + module: { + type: isServer ? 'commonjs' : 'es6', + }, + }, + }), + }, + plugins: [ + 'docusaurus-plugin-sass', + () => ({ + name: 'yaml-loader', + configureWebpack() { + return { + module: { + rules: [ + { + test: /\.ya?ml$/, + use: 'yaml-loader', + }, + ], + }, + }; + }, + }), + [ + '@docusaurus/plugin-client-redirects', + { + redirects: [ + { + from: '/docs', + to: '/docs/overview/what-is-backstage', + }, + { + from: '/docs/features/software-catalog/software-catalog-overview', + to: '/docs/features/software-catalog/', + }, + { + from: '/docs/features/software-templates/software-templates-index', + to: '/docs/features/software-templates/', + }, + { + from: '/docs/features/techdocs/techdocs-overview', + to: '/docs/features/techdocs/', + }, + { + from: '/docs/features/kubernetes/overview', + to: '/docs/features/kubernetes/', + }, + { + from: '/docs/features/search/search-overview', + to: '/docs/features/search/', + }, + { + from: '/docs/getting-started/running-backstage-locally', + to: '/docs/getting-started/', + }, + { + from: '/docs/features/software-templates/testing-scaffolder-alpha', + to: '/docs/features/software-templates/migrating-to-rjsf-v5', + }, + { + from: '/docs/auth/glossary', + to: '/docs/references/glossary', + }, + { + from: '/docs/overview/glossary', + to: '/docs/references/glossary', + }, + { + from: '/docs/getting-started/create-an-app', + to: '/docs/getting-started/', + }, + { + from: '/docs/getting-started/configuration', + to: '/docs/getting-started/#next-steps', + }, + ], + }, + ], + [ + 'docusaurus-pushfeedback', + { + project: 'q8w1i6cair', + hideIcon: true, + customFont: true, + buttonStyle: 'dark', + }, + ], + ], + themeConfig: { + colorMode: { + defaultMode: 'dark', + disableSwitch: true, + }, + navbar: { + logo: { + alt: 'Backstage Software Catalog and Developer Platform', + src: 'img/logo.svg', + }, + items: [ + { + href: 'https://github.com/backstage/backstage', + label: 'GitHub', + position: 'left', + }, + { + to: 'docs/overview/what-is-backstage', + label: 'Docs', + position: 'left', + }, + { + to: '/plugins', + label: 'Plugins', + position: 'left', + }, + { + to: '/blog', + label: 'Blog', + position: 'left', + }, + { + to: 'docs/releases/v1.26.0', + label: 'Releases', + position: 'left', + }, + { + to: '/demos', + label: 'Demos', + position: 'left', + }, + { + to: '/community', + label: 'Community', + position: 'left', + }, + ], + }, + image: 'img/sharing-opengraph.png', + footer: { + links: [ + { + items: [ + { + html: ` + <a href="/" aria-label="Backstage Home"> + <div class="footerLogo"></div> + </a>`, + }, + ], + }, + { + title: 'Docs', + items: [ + { + label: 'What is Backstage?', + to: 'docs/overview/what-is-backstage', + }, + { + label: 'Getting started', + to: 'docs/getting-started/', + }, + { + label: 'Software Catalog', + to: 'docs/features/software-catalog/', + }, + { + label: 'Create a Plugin', + to: 'docs/plugins/create-a-plugin', + }, + { + label: 'Designing for Backstage', + to: 'docs/dls/design', + }, + ], + }, + { + title: 'Community', + items: [ + { + label: 'Support chatroom', + to: 'https://discord.gg/backstage-687207715902193673', + }, + { + label: 'Contributing', + to: 'https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md', + }, + { + label: 'Adopting', + to: 'https://backstage.spotify.com', + }, + { + label: 'Subscribe to our newsletter', + to: 'https://info.backstage.spotify.com/newsletter_subscribe', + }, + { + label: 'CNCF Incubation', + to: 'https://www.cncf.io/projects/', + }, + ], + }, + { + 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/', + }, + ], + }, + ], + copyright: `<p style="text-align:center"><a href="https://spotify.github.io/">Made with ❤️ at Spotify</a></p><p class="copyright">Copyright © ${new Date().getFullYear()} Backstage Project Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage</p>`, + }, + algolia: { + apiKey: '1f0ba86672ccfc3576faa94583e5b318', + indexName: 'crawler_Backstage Docusaurus 2', + appId: 'JCMFNHCHI8', + searchParameters: {}, + }, + prism: { + theme: backstageTheme, + // Supported languages: https://prismjs.com/#supported-languages + // Default languages: https://github.com/FormidableLabs/prism-react-renderer/blob/master/packages/generate-prism-languages/index.ts#L9-L23 + additionalLanguages: ['docker', 'bash'], + magicComments: [ + // Extend the default highlight class name + { + className: 'code-block-highlight-line', + line: 'highlight-next-line', + block: { start: 'highlight-start', end: 'highlight-end' }, + }, + { + className: 'code-block-add-line', + line: 'highlight-add-next-line', + block: { start: 'highlight-add-start', end: 'highlight-add-end' }, + }, + { + className: 'code-block-remove-line', + line: 'highlight-remove-next-line', + block: { + start: 'highlight-remove-start', + end: 'highlight-remove-end', + }, + }, + ], + }, + } satisfies Preset.ThemeConfig, +}; +export default config; diff --git a/microsite/package.json b/microsite/package.json index 1be5272286..edc4c08612 100644 --- a/microsite/package.json +++ b/microsite/package.json @@ -22,12 +22,14 @@ "@docusaurus/core": "^3.1.1", "@docusaurus/plugin-client-redirects": "^3.1.1", "@docusaurus/preset-classic": "^3.1.1", + "@docusaurus/types": "^3.1.1", + "@mdx-js/react": "^3.0.0", "@swc/core": "^1.3.46", "clsx": "^2.0.0", "docusaurus-plugin-sass": "^0.2.3", "docusaurus-pushfeedback": "^1.0.0", "luxon": "^3.0.0", - "prism-react-renderer": "^1.3.5", + "prism-react-renderer": "^2.1.0", "react": "^18.0.0", "react-dom": "^18.0.0", "sass": "^1.57.1", @@ -35,8 +37,8 @@ }, "devDependencies": { "@docusaurus/module-type-aliases": "^3.1.1", + "@docusaurus/tsconfig": "^3.1.1", "@spotify/prettier-config": "^15.0.0", - "@tsconfig/docusaurus": "^2.0.0", "@types/luxon": "^3.0.0", "@types/webpack-env": "^1.18.0", "js-yaml": "^4.1.0", diff --git a/microsite/tsconfig.json b/microsite/tsconfig.json index ae8fe2298a..71bc403643 100644 --- a/microsite/tsconfig.json +++ b/microsite/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "@tsconfig/docusaurus/tsconfig.json", + "extends": "@docusaurus/tsconfig", "compilerOptions": { "baseUrl": ".", "types": [ diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 259bbca7b0..3ca3e2837a 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -2160,7 +2160,14 @@ __metadata: languageName: node linkType: hard -"@docusaurus/types@npm:3.2.1": +"@docusaurus/tsconfig@npm:^3.1.1": + version: 3.2.1 + resolution: "@docusaurus/tsconfig@npm:3.2.1" + checksum: ea3c28b79b0de069c50f7b3a67d3ff682b6ded2ef02d2c7a4c2eaeddc8fcf79c9d9f5e60fbd2966cf3d247fbb8f63897b80a61fdd8b485c745a12eb684ae241a + languageName: node + linkType: hard + +"@docusaurus/types@npm:3.2.1, @docusaurus/types@npm:^3.1.1": version: 3.2.1 resolution: "@docusaurus/types@npm:3.2.1" dependencies: @@ -2842,13 +2849,6 @@ __metadata: languageName: node linkType: hard -"@tsconfig/docusaurus@npm:^2.0.0": - version: 2.0.3 - resolution: "@tsconfig/docusaurus@npm:2.0.3" - checksum: d8245a64bf131daa0c287649cb9c37ad8fecb2aedea18e15047e0df6463916e5012af3755bd73ecf90999ace1f14a3748a8b3e041d5158e2a85c22a539c649c9 - languageName: node - linkType: hard - "@types/acorn@npm:^4.0.0": version: 4.0.6 resolution: "@types/acorn@npm:4.0.6" @@ -3847,9 +3847,11 @@ __metadata: "@docusaurus/module-type-aliases": ^3.1.1 "@docusaurus/plugin-client-redirects": ^3.1.1 "@docusaurus/preset-classic": ^3.1.1 + "@docusaurus/tsconfig": ^3.1.1 + "@docusaurus/types": ^3.1.1 + "@mdx-js/react": ^3.0.0 "@spotify/prettier-config": ^15.0.0 "@swc/core": ^1.3.46 - "@tsconfig/docusaurus": ^2.0.0 "@types/luxon": ^3.0.0 "@types/webpack-env": ^1.18.0 clsx: ^2.0.0 @@ -3858,7 +3860,7 @@ __metadata: js-yaml: ^4.1.0 luxon: ^3.0.0 prettier: ^2.6.2 - prism-react-renderer: ^1.3.5 + prism-react-renderer: ^2.1.0 react: ^18.0.0 react-dom: ^18.0.0 sass: ^1.57.1 @@ -9583,16 +9585,7 @@ __metadata: languageName: node linkType: hard -"prism-react-renderer@npm:^1.3.5": - version: 1.3.5 - resolution: "prism-react-renderer@npm:1.3.5" - peerDependencies: - react: ">=0.14.9" - checksum: c18806dcbc4c0b4fd6fd15bd06b4f7c0a6da98d93af235c3e970854994eb9b59e23315abb6cfc29e69da26d36709a47e25da85ab27fed81b6812f0a52caf6dfa - languageName: node - linkType: hard - -"prism-react-renderer@npm:^2.3.0": +"prism-react-renderer@npm:^2.1.0, prism-react-renderer@npm:^2.3.0": version: 2.3.1 resolution: "prism-react-renderer@npm:2.3.1" dependencies: From cfacb71538028155f94961b303e4f60d32b1d2b7 Mon Sep 17 00:00:00 2001 From: npiyush97 <npiyush35@gmail.com> Date: Fri, 26 Apr 2024 23:01:58 +0530 Subject: [PATCH 140/234] recommeded codeql fix Signed-off-by: npiyush97 <npiyush35@gmail.com> --- microsite/docusaurus.config.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index 256784ddcc..e2ae4ab338 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -73,7 +73,15 @@ const config: Config = { markdown: { preprocessor({ fileContent }) { // Replace all HTML comments with empty strings as these are not supported by MDXv2. - return fileContent.replace(/<!--.*?-->/gs, ''); + function removeHtmlComments(input) { + let previous; + do { + previous = input; + input = input.replace(/<!--|--!?>/g, ''); + } while (input !== previous); + return input; + } + return removeHtmlComments(fileContent); }, format: 'detect', }, From 3102a99f48def3f64602dca60f5cf7f2ab5460b9 Mon Sep 17 00:00:00 2001 From: Seba <sebastianolaini@gmail.com> Date: Fri, 26 Apr 2024 20:28:17 +0200 Subject: [PATCH 141/234] add the namespace label to CronJobDrawer & IngressDrawer (#24485) Signed-off-by: sebalaini <sebastianolaini@gmail.com> --- .changeset/thick-terms-rush.md | 5 +++++ .../src/components/IngressesAccordions/IngressDrawer.tsx | 7 +++++++ .../src/components/ServicesAccordions/ServiceDrawer.tsx | 7 +++++++ 3 files changed, 19 insertions(+) create mode 100644 .changeset/thick-terms-rush.md diff --git a/.changeset/thick-terms-rush.md b/.changeset/thick-terms-rush.md new file mode 100644 index 0000000000..fb12f11f7e --- /dev/null +++ b/.changeset/thick-terms-rush.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-react': patch +--- + +add the namespace label to CronJobDrawer & IngressDrawer. diff --git a/plugins/kubernetes-react/src/components/IngressesAccordions/IngressDrawer.tsx b/plugins/kubernetes-react/src/components/IngressesAccordions/IngressDrawer.tsx index 0590546bbd..feb85c6d3d 100644 --- a/plugins/kubernetes-react/src/components/IngressesAccordions/IngressDrawer.tsx +++ b/plugins/kubernetes-react/src/components/IngressesAccordions/IngressDrawer.tsx @@ -19,6 +19,7 @@ import { V1Ingress } from '@kubernetes/client-node'; import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; +import Chip from '@material-ui/core/Chip'; export const IngressDrawer = ({ ingress, @@ -27,6 +28,7 @@ export const IngressDrawer = ({ ingress: V1Ingress; expanded?: boolean; }) => { + const namespace = ingress.metadata?.namespace; return ( <KubernetesStructuredMetadataTableDrawer object={ingress} @@ -53,6 +55,11 @@ export const IngressDrawer = ({ Ingress </Typography> </Grid> + {namespace && ( + <Grid item> + <Chip size="small" label={`namespace: ${namespace}`} /> + </Grid> + )} </Grid> </KubernetesStructuredMetadataTableDrawer> ); diff --git a/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx b/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx index c5392a3e3c..08678e45bd 100644 --- a/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx +++ b/plugins/kubernetes-react/src/components/ServicesAccordions/ServiceDrawer.tsx @@ -19,6 +19,7 @@ import { V1Service } from '@kubernetes/client-node'; import { KubernetesStructuredMetadataTableDrawer } from '../KubernetesDrawer'; import Typography from '@material-ui/core/Typography'; import Grid from '@material-ui/core/Grid'; +import Chip from '@material-ui/core/Chip'; export const ServiceDrawer = ({ service, @@ -27,6 +28,7 @@ export const ServiceDrawer = ({ service: V1Service; expanded?: boolean; }) => { + const namespace = service.metadata?.namespace; return ( <KubernetesStructuredMetadataTableDrawer object={service} @@ -53,6 +55,11 @@ export const ServiceDrawer = ({ Service </Typography> </Grid> + {namespace && ( + <Grid item> + <Chip size="small" label={`namespace: ${namespace}`} /> + </Grid> + )} </Grid> </KubernetesStructuredMetadataTableDrawer> ); From 18f736ffc5efbd2e2dcdf08c12fa8c14ca883791 Mon Sep 17 00:00:00 2001 From: JeevaRamanathan <jeevaramanathan.m@infosys.com> Date: Sat, 27 Apr 2024 22:22:06 +0530 Subject: [PATCH 142/234] Add examples for scaffolder action & improve related tests Signed-off-by: JeevaRamanathan <jeevaramanathan.m@infosys.com> --- .changeset/tame-jars-double.md | 5 + ...tlabProjectVariableAction.examples.test.ts | 236 ++++++++++++++++++ ...ateGitlabProjectVariableAction.examples.ts | 160 ++++++++++++ .../createGitlabProjectVariableAction.ts | 3 +- 4 files changed, 403 insertions(+), 1 deletion(-) create mode 100644 .changeset/tame-jars-double.md create mode 100644 plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts create mode 100644 plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts diff --git a/.changeset/tame-jars-double.md b/.changeset/tame-jars-double.md new file mode 100644 index 0000000000..8af92a2d72 --- /dev/null +++ b/.changeset/tame-jars-double.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': minor +--- + +Add examples for `gitlab:projectVariable:create` scaffolder action & improve related tests diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts new file mode 100644 index 0000000000..d1c37ccd9d --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.test.ts @@ -0,0 +1,236 @@ +/* + * 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 { createGitlabProjectVariableAction } from './createGitlabProjectVariableAction'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import yaml from 'yaml'; +import { examples } from './createGitlabProjectVariableAction.examples'; + +const mockGitlabClient = { + ProjectVariables: { + create: jest.fn(), + }, +}; +jest.mock('@gitbeaker/node', () => ({ + Gitlab: class { + constructor() { + return mockGitlabClient; + } + }, +})); + +describe('gitlab:projectVariableAction: create examples', () => { + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'gitlab.com', + token: 'tokenlols', + apiBaseUrl: 'https://api.gitlab.com', + }, + { + host: 'hosted.gitlab.com', + apiBaseUrl: 'https://api.hosted.gitlab.com', + }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const action = createGitlabProjectVariableAction({ integrations }); + const mockContext = createMockActionContext({ + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + }, + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it(`Should ${examples[0].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[0].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '123', + { + key: 'MY_VARIABLE', + value: 'my_value', + variable_type: 'env_var', + environment_scope: '*', + masked: false, + protected: false, + raw: false, + }, + ); + }); + it(`Should ${examples[1].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[1].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '123', + { + key: 'MY_VARIABLE', + value: 'my-file-content', + protected: false, + masked: false, + raw: false, + environment_scope: '*', + variable_type: 'file', + }, + ); + }); + + it(`Should ${examples[2].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[2].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '456', + { + key: 'MY_VARIABLE', + value: 'my_value', + masked: false, + raw: false, + environment_scope: '*', + variable_type: 'env_var', + protected: true, + }, + ); + }); + + it(`Should ${examples[3].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[3].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '789', + { + key: 'DB_PASSWORD', + value: 'password123', + protected: false, + raw: false, + environment_scope: '*', + variable_type: 'env_var', + masked: true, + }, + ); + }); + + it(`Should ${examples[4].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[4].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '123', + { + key: 'MY_VARIABLE', + value: 'my_value', + protected: false, + environment_scope: '*', + variable_type: 'env_var', + masked: false, + raw: true, + }, + ); + }); + + it(`Should ${examples[5].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[5].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '123', + { + key: 'MY_VARIABLE', + value: 'my_value', + protected: false, + variable_type: 'env_var', + masked: false, + raw: false, + environment_scope: 'production', + }, + ); + }); + + it(`Should ${examples[6].description}`, async () => { + mockGitlabClient.ProjectVariables.create.mockResolvedValue({ + token: 'TOKEN', + }); + + await action.handler({ + ...mockContext, + input: yaml.parse(examples[6].example).steps[0].input, + }); + + expect(mockGitlabClient.ProjectVariables.create).toHaveBeenCalledWith( + '123', + { + key: 'MY_VARIABLE', + value: 'my_value', + protected: false, + variable_type: 'env_var', + masked: false, + raw: false, + environment_scope: '*', + }, + ); + }); +}); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts new file mode 100644 index 0000000000..81d526f7c8 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.examples.ts @@ -0,0 +1,160 @@ +/* + * 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 { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Creating a GitLab project variable of type env_var', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:createGitlabProjectVariableAction', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + }, + }, + ], + }), + }, + { + description: 'Creating a GitLab project variable of type file', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:createGitlabProjectVariableAction', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my-file-content', + variableType: 'file', + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project variable that is protected.', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:createGitlabProjectVariableAction', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '456', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + variableProtected: true, + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project variable with masked flag as true', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:createGitlabProjectVariableAction', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '789', + key: 'DB_PASSWORD', + value: 'password123', + variableType: 'env_var', + masked: true, + }, + }, + ], + }), + }, + { + description: 'Create a GitLab project variable that is expandable.', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:projectVariable:create', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + raw: true, + }, + }, + ], + }), + }, + { + description: + 'Create a GitLab project variable with a specific environment scope.', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:projectVariable:create', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + environmentScope: 'production', + }, + }, + ], + }), + }, + { + description: + 'Create a GitLab project variable with a wildcard environment scope.', + example: yaml.stringify({ + steps: [ + { + id: 'createVariable', + action: 'gitlab:projectVariable:create', + name: 'Create GitLab Project Variable', + input: { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + projectId: '123', + key: 'MY_VARIABLE', + value: 'my_value', + variableType: 'env_var', + environmentScope: '*', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts index a154c16662..e09a074701 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/createGitlabProjectVariableAction.ts @@ -20,6 +20,7 @@ import { Gitlab } from '@gitbeaker/node'; import { getToken } from '../util'; import commonGitlabConfig from '../commonGitlabConfig'; import { z } from 'zod'; +import { examples } from './createGitlabProjectVariableAction.examples'; /** * Creates a `gitlab:projectVariable:create` Scaffolder action. @@ -33,6 +34,7 @@ export const createGitlabProjectVariableAction = (options: { const { integrations } = options; return createTemplateAction({ id: 'gitlab:projectVariable:create', + examples, schema: { input: commonGitlabConfig.merge( z.object({ @@ -85,7 +87,6 @@ export const createGitlabProjectVariableAction = (options: { host: integrationConfig.config.baseUrl, token: token, }); - await api.ProjectVariables.create(projectId, { key: key, value: value, From 915665429011f3156df2d10c85ab3470d295ef15 Mon Sep 17 00:00:00 2001 From: bnechyporenko <bnechyporenko@bol.com> Date: Sat, 27 Apr 2024 20:59:32 +0200 Subject: [PATCH 143/234] Capturing more event clicks for scaffolder Signed-off-by: bnechyporenko <bnechyporenko@bol.com> --- .changeset/blue-hotels-shake.md | 6 ++++ .../src/Template.v1beta3.schema.json | 30 +++++++++++-------- .../src/next/components/Stepper/Stepper.tsx | 25 ++++++++-------- .../components/TemplateCard/TemplateCard.tsx | 18 ++++++++--- .../components/OngoingTask/ContextMenu.tsx | 4 ++- .../components/OngoingTask/OngoingTask.tsx | 7 ++++- .../TemplateWizardPage.test.tsx | 17 +++++++++-- 7 files changed, 74 insertions(+), 33 deletions(-) create mode 100644 .changeset/blue-hotels-shake.md diff --git a/.changeset/blue-hotels-shake.md b/.changeset/blue-hotels-shake.md new file mode 100644 index 0000000000..57fb806377 --- /dev/null +++ b/.changeset/blue-hotels-shake.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-react': patch +'@backstage/plugin-scaffolder': patch +--- + +Capturing more event clicks for scaffolder diff --git a/plugins/scaffolder-common/src/Template.v1beta3.schema.json b/plugins/scaffolder-common/src/Template.v1beta3.schema.json index 99b0e0b7cb..4e34545d6d 100644 --- a/plugins/scaffolder-common/src/Template.v1beta3.schema.json +++ b/plugins/scaffolder-common/src/Template.v1beta3.schema.json @@ -141,19 +141,25 @@ }, "presentation": { "type": "object", - "description": "A way to redefine the labels for actionable buttons.", + "description": "A way to redefine the presentation of the scaffolder.", "properties": { - "backButtonText": { - "type": "string", - "description": "A button which return the user to one step back." - }, - "createButtonText": { - "type": "string", - "description": "A button which start the execution of the template." - }, - "reviewButtonText": { - "type": "string", - "description": "A button which open the review step to verify the input prior to start the execution." + "buttonLabels": { + "type": "object", + "description": "A way to redefine the labels for actionable buttons.", + "properties": { + "backButtonText": { + "type": "string", + "description": "A button which return the user to one step back." + }, + "createButtonText": { + "type": "string", + "description": "A button which start the execution of the template." + }, + "reviewButtonText": { + "type": "string", + "description": "A button which open the review step to verify the input prior to start the execution." + } + } } } }, diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 62effbd14a..33e0f5a2a0 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -35,9 +35,8 @@ import { type FormValidation, } from './createAsyncValidators'; import { ReviewState, type ReviewStateProps } from '../ReviewState'; -import { useTemplateSchema } from '../../hooks/useTemplateSchema'; +import { useTemplateSchema, useFormDataFromQuery } from '../../hooks'; import validator from '@rjsf/validator-ajv8'; -import { useFormDataFromQuery } from '../../hooks'; import { useTransformSchemaToProps } from '../../hooks/useTransformSchemaToProps'; import { hasErrors } from './utils'; import * as FieldOverrides from './FieldOverrides'; @@ -115,6 +114,13 @@ export const Stepper = (stepperProps: StepperProps) => { const [errors, setErrors] = useState<undefined | FormValidation>(); const styles = useStyles(); + const backLabel = + presentation?.buttonLabels?.backButtonText ?? backButtonText; + const createLabel = + presentation?.buttonLabels?.createButtonText ?? createButtonText; + const reviewLabel = + presentation?.buttonLabels?.reviewButtonText ?? reviewButtonText; + const extensions = useMemo(() => { return Object.fromEntries( props.extensions.map(({ name, component }) => [name, component]), @@ -150,7 +156,8 @@ export const Stepper = (stepperProps: StepperProps) => { const handleCreate = useCallback(() => { props.onCreate(formState); - }, [props, formState]); + analytics.captureEvent('click', `${createLabel}`); + }, [props, formState, analytics, createLabel]); const currentStep = useTransformSchemaToProps(steps[activeStep], { layouts }); @@ -175,19 +182,13 @@ export const Stepper = (stepperProps: StepperProps) => { setActiveStep(prevActiveStep => { const stepNum = prevActiveStep + 1; analytics.captureEvent('click', `Next Step (${stepNum})`); + analytics.captureEvent('click', `Next Step (${stepNum})`); return stepNum; }); } setFormState(current => ({ ...current, ...formData })); }; - const backLabel = - presentation?.buttonLabels?.backButtonText ?? backButtonText; - const createLabel = - presentation?.buttonLabels?.createButtonText ?? createButtonText; - const reviewLabel = - presentation?.buttonLabels?.reviewButtonText ?? reviewButtonText; - return ( <> {isValidating && <LinearProgress variant="indeterminate" />} @@ -214,7 +215,7 @@ export const Stepper = (stepperProps: StepperProps) => { ); })} <MuiStep> - <MuiStepLabel>Review</MuiStepLabel> + <MuiStepLabel>${reviewLabel}</MuiStepLabel> </MuiStep> </MuiStepper> <div className={styles.formWrapper}> @@ -274,7 +275,7 @@ export const Stepper = (stepperProps: StepperProps) => { className={styles.backButton} disabled={activeStep < 1} > - Back + {backLabel} </Button> <Button variant="contained" diff --git a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx index 4fb2ca0e4a..4d4583a7d7 100644 --- a/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder-react/src/next/components/TemplateCard/TemplateCard.tsx @@ -16,7 +16,11 @@ import { RELATION_OWNED_BY } from '@backstage/catalog-model'; import { MarkdownContent, UserIcon } from '@backstage/core-components'; -import { IconComponent, useApp } from '@backstage/core-plugin-api'; +import { + IconComponent, + useAnalytics, + useApp, +} from '@backstage/core-plugin-api'; import { EntityRefLinks, getEntityRelations, @@ -32,7 +36,7 @@ import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; import { makeStyles, Theme } from '@material-ui/core/styles'; import LanguageIcon from '@material-ui/icons/Language'; -import React from 'react'; +import React, { useCallback } from 'react'; import { CardHeader } from './CardHeader'; import { CardLink } from './CardLink'; @@ -92,8 +96,9 @@ export interface TemplateCardProps { * @alpha */ export const TemplateCard = (props: TemplateCardProps) => { - const { template } = props; + const { onSelected, template } = props; const styles = useStyles(); + const analytics = useAnalytics(); const ownedByRelations = getEntityRelations(template, RELATION_OWNED_BY); const app = useApp(); const iconResolver = (key?: string): IconComponent => @@ -103,6 +108,11 @@ export const TemplateCard = (props: TemplateCardProps) => { !!props.additionalLinks?.length || !!template.metadata.links?.length; const displayDefaultDivider = !hasTags && !hasLinks; + const handleChoose = useCallback(() => { + analytics.captureEvent('click', `Template has been opened`); + onSelected?.(template); + }, [analytics, onSelected, template]); + return ( <Card> <CardHeader template={template} /> @@ -190,7 +200,7 @@ export const TemplateCard = (props: TemplateCardProps) => { size="small" variant="outlined" color="primary" - onClick={() => props.onSelected?.(template)} + onClick={handleChoose} > Choose </Button> diff --git a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx index 99d9235791..1dd6a48f64 100644 --- a/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/ContextMenu.tsx @@ -28,7 +28,7 @@ import Toc from '@material-ui/icons/Toc'; import ControlPointIcon from '@material-ui/icons/ControlPoint'; import MoreVert from '@material-ui/icons/MoreVert'; import React, { useState } from 'react'; -import { useApi } from '@backstage/core-plugin-api'; +import { useAnalytics, useApi } from '@backstage/core-plugin-api'; import { scaffolderApiRef } from '@backstage/plugin-scaffolder-react'; type ContextMenuProps = { @@ -61,10 +61,12 @@ export const ContextMenu = (props: ContextMenuProps) => { const pageTheme = getPageTheme({ themeId: 'website' }); const classes = useStyles({ fontColor: pageTheme.fontColor }); const scaffolderApi = useApi(scaffolderApiRef); + const analytics = useAnalytics(); const [anchorEl, setAnchorEl] = useState<HTMLButtonElement>(); const [{ status: cancelStatus }, { execute: cancel }] = useAsync(async () => { if (taskId) { + analytics.captureEvent('cancelled', 'Template has been cancelled'); await scaffolderApi.cancelTask(taskId); } }); diff --git a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx index 3e7a3b8f52..f50b76e14a 100644 --- a/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx +++ b/plugins/scaffolder/src/components/OngoingTask/OngoingTask.tsx @@ -26,7 +26,7 @@ import { useTaskEventStream, } from '@backstage/plugin-scaffolder-react'; import { selectedTemplateRouteRef } from '../../routes'; -import { useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { useAnalytics, useApi, useRouteRef } from '@backstage/core-plugin-api'; import qs from 'qs'; import { ContextMenu } from './ContextMenu'; import { @@ -66,6 +66,7 @@ export const OngoingTask = (props: { const { taskId } = useParams(); const templateRouteRef = useRouteRef(selectedTemplateRouteRef); const navigate = useNavigate(); + const analytics = useAnalytics(); const scaffolderApi = useApi(scaffolderApiRef); const taskStream = useTaskEventStream(taskId!); const classes = useStyles(); @@ -113,6 +114,8 @@ export const OngoingTask = (props: { return; } + analytics.captureEvent('click', `Task has been started over`); + navigate({ pathname: templateRouteRef({ namespace, @@ -121,6 +124,7 @@ export const OngoingTask = (props: { search: `?${qs.stringify({ formData: JSON.stringify(formData) })}`, }); }, [ + analytics, navigate, taskStream.task?.spec.parameters, taskStream.task?.spec.templateInfo?.entity?.metadata, @@ -130,6 +134,7 @@ export const OngoingTask = (props: { const [{ status: cancelStatus }, { execute: triggerCancel }] = useAsync( async () => { if (taskId) { + analytics.captureEvent('cancelled', 'Template has been cancelled'); await scaffolderApi.cancelTask(taskId); } }, diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.test.tsx index 269ff81271..5f112cf37d 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.test.tsx @@ -129,19 +129,30 @@ describe('TemplateWizardPage', () => { fireEvent.click(await findByRole('button', { name: 'Create' })); }); - // The "Next Step" button should have fired an event + // The "Next Step" button should have fired few events expect(analyticsMock.getEvents()[0]).toMatchObject({ action: 'click', subject: 'Next Step (1)', context: { entityRef: 'template:default/test' }, }); - // And the "Create" button should have fired an event expect(analyticsMock.getEvents()[1]).toMatchObject({ + action: 'click', + subject: 'Next Step (1)', + context: { entityRef: 'template:default/test' }, + }); + + // And the "Create" button should have fired few event + expect(analyticsMock.getEvents()[2]).toMatchObject({ action: 'create', subject: 'expected-name', context: { entityRef: 'template:default/test' }, - value: 120, + }); + + expect(analyticsMock.getEvents()[3]).toMatchObject({ + action: 'click', + subject: 'Create', + context: { entityRef: 'template:default/test' }, }); }); describe('scaffolder page context menu', () => { From 39174e9e4ffb0d399ca82924011c3fcff820c9ce Mon Sep 17 00:00:00 2001 From: bnechyporenko <bnechyporenko@bol.com> Date: Sat, 27 Apr 2024 21:03:54 +0200 Subject: [PATCH 144/234] Capturing more event clicks for scaffolder Signed-off-by: bnechyporenko <bnechyporenko@bol.com> --- .changeset/blue-hotels-shake.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/blue-hotels-shake.md b/.changeset/blue-hotels-shake.md index 57fb806377..3d55aacffa 100644 --- a/.changeset/blue-hotels-shake.md +++ b/.changeset/blue-hotels-shake.md @@ -1,4 +1,5 @@ --- +'@backstage/plugin-scaffolder-common': patch '@backstage/plugin-scaffolder-react': patch '@backstage/plugin-scaffolder': patch --- From 1da71d800c3e0c7e86c53755844d71b116eacf3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Sun, 28 Apr 2024 11:33:59 +0200 Subject: [PATCH 145/234] Make individual connector classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- .../src/database/DatabaseManager.ts | 36 +- .../src/database/connection.test.ts | 308 --------------- .../backend-common/src/database/connection.ts | 355 +++++++++--------- .../src/database/connectors/mysql.ts | 336 ++++++++++++++++- .../src/database/connectors/postgres.ts | 341 ++++++++++++++++- .../src/database/connectors/sqlite3.ts | 339 ++++++++++++++++- packages/backend-common/src/database/types.ts | 28 +- 7 files changed, 1224 insertions(+), 519 deletions(-) delete mode 100644 packages/backend-common/src/database/connection.test.ts diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts index 8a4a4b50fa..776f05aed4 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -19,14 +19,6 @@ import { JsonObject } from '@backstage/types'; import { Knex } from 'knex'; import { merge, omit } from 'lodash'; import { mergeDatabaseConfig } from './config'; -import { - createDatabaseClient, - createNameOverride, - createSchemaOverride, - ensureDatabaseExists, - ensureSchemaExists, - normalizeConnection, -} from './connection'; import { PluginDatabaseManager } from './types'; import path from 'path'; import { @@ -36,6 +28,9 @@ import { PluginMetadataService, } from '@backstage/backend-plugin-api'; import { stringifyError } from '@backstage/errors'; +import { PgConnector } from './connectors/postgres'; +import { Sqlite3Connector } from './connectors/sqlite3'; +import { MysqlConnector } from './connectors/mysql'; /** * Provides a config lookup path for a plugin's config block. @@ -86,10 +81,16 @@ export class DatabaseManager implements LegacyRootDatabaseService { options?: DatabaseManagerOptions, ): DatabaseManager { const databaseConfig = config.getConfig('backend.database'); - return new DatabaseManager( databaseConfig, databaseConfig.getOptionalString('prefix'), + { + pg: new PgConnector(config, prefix, options), + sqlite3: new Sqlite3Connector(config, prefix, options), + 'better-sqlite3': new Sqlite3Connector(config, prefix, options), + mysql: new MysqlConnector(config, prefix, options), + mysql2: new MysqlConnector(config, prefix, options), + }, options, ); } @@ -97,6 +98,18 @@ export class DatabaseManager implements LegacyRootDatabaseService { private constructor( private readonly config: Config, private readonly prefix: string = 'backstage_plugin_', + private readonly connectors: Record< + string, + { + getClient( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Promise<Knex>; + } + >, private readonly options?: DatabaseManagerOptions, private readonly databaseCache: Map<string, Promise<Knex>> = new Map(), ) {} @@ -115,6 +128,11 @@ export class DatabaseManager implements LegacyRootDatabaseService { pluginMetadata: PluginMetadataService; }, ): PluginDatabaseManager { + const client = this.getClientType(pluginId).client; + const connector = this.connectors[client]; + if (!connector) { + throw new Error(`Unsupported database client type '${client}'`); + } const getClient = () => this.getDatabase(pluginId, deps); const migrations = { skip: false, ...this.options?.migrations }; return { getClient, migrations }; diff --git a/packages/backend-common/src/database/connection.test.ts b/packages/backend-common/src/database/connection.test.ts deleted file mode 100644 index 3028d46cae..0000000000 --- a/packages/backend-common/src/database/connection.test.ts +++ /dev/null @@ -1,308 +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 { - createDatabaseClient, - createNameOverride, - createSchemaOverride, - dropDatabase, - ensureSchemaExists, - parseConnectionString, -} from './connection'; -import { mysqlConnector, pgConnector } from './connectors'; - -const mocked = (f: Function) => f as jest.Mock; - -jest.mock('./connectors', () => { - const connectors = jest.requireActual('./connectors'); - return { - ...connectors, - mysqlConnector: { - ...connectors.mysqlConnector, - dropDatabase: jest.fn(), - }, - pgConnector: { - ...connectors.pgConnector, - dropDatabase: jest.fn(), - ensureSchemaExists: jest.fn(), - }, - }; -}); - -describe('database connection', () => { - describe('createDatabaseClient', () => { - it('returns a postgres connection', () => { - expect( - createDatabaseClient( - new ConfigReader({ - client: 'pg', - connection: { - host: 'acme', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }), - ), - ).toBeTruthy(); - }); - - it('returns an sqlite connection', () => { - expect( - createDatabaseClient( - new ConfigReader({ - client: 'better-sqlite3', - connection: ':memory:', - }), - ), - ).toBeTruthy(); - }); - - it('returns a mysql connection', () => { - expect(() => - createDatabaseClient( - new ConfigReader({ - client: 'mysql2', - connection: { - host: '127.0.0.1', - user: 'foo', - password: 'bar', - database: 'dbname', - }, - }), - ), - ).toBeTruthy(); - }); - - it('accepts overrides', () => { - expect( - createDatabaseClient( - new ConfigReader({ - client: 'pg', - connection: { - host: 'acme', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }), - { - connection: { - database: 'foo', - }, - }, - ), - ).toBeTruthy(); - }); - - it('throws an error without a client', () => { - expect(() => - createDatabaseClient( - new ConfigReader({ - connection: '', - }), - ), - ).toThrow(); - }); - - it('throws an error without a connection', () => { - expect(() => - createDatabaseClient( - new ConfigReader({ - client: 'pg', - }), - ), - ).toThrow(); - }); - }); - - describe('createNameOverride', () => { - it('returns Knex config for postgres', () => { - expect(createNameOverride('pg', 'testpg')).toHaveProperty( - 'connection.database', - 'testpg', - ); - }); - - it('returns Knex config for sqlite', () => { - expect(createNameOverride('better-sqlite3', 'testsqlite')).toHaveProperty( - 'connection.filename', - 'testsqlite', - ); - }); - - it('returns Knex config for mysql', () => { - expect(createNameOverride('mysql', 'testmysql')).toHaveProperty( - 'connection.database', - 'testmysql', - ); - }); - - it('throws an error for unknown connection', () => { - expect(() => createNameOverride('unknown', 'testname')).toThrow(); - }); - }); - - describe('parseConnectionString', () => { - it('returns parsed Knex.StaticConnectionConfig for postgres', () => { - expect( - parseConnectionString('postgresql://foo:bar@acme:5432/foodb', 'pg'), - ).toHaveProperty('database', 'foodb'); - }); - - it('returns parsed Knex.StaticConnectionConfig for mysql2', () => { - expect( - parseConnectionString('mysql://foo:bar@acme:3306/foodb', 'mysql2'), - ).toHaveProperty('database', 'foodb'); - }); - - it('throws an error if client hint is not provided', () => { - expect(() => parseConnectionString('sqlite://')).toThrow(); - }); - }); - - describe('createSchemaOverride', () => { - it('returns Knex config for postgres', () => { - expect(createSchemaOverride('pg', 'testpg')).toHaveProperty( - 'searchPath', - ['testpg'], - ); - }); - - it('throws error for sqlite', () => { - expect( - createSchemaOverride('better-sqlite3', 'testsqlite'), - ).toBeUndefined(); - }); - - it('returns Knex config for mysql', () => { - expect(createSchemaOverride('mysql', 'testmysql')).toBeUndefined(); - }); - - it('throws an error for unknown connection', () => { - expect(createSchemaOverride('unknown', 'testname')).toBeUndefined(); - }); - }); - - describe('ensureSchemaExists', () => { - it('returns successfully with pg client', async () => { - await ensureSchemaExists( - new ConfigReader({ - client: 'pg', - schema: 'catalog', - connection: 'postgresql://testuser:testpass@acme:5432/userdbname', - }), - 'catalog', - ); - - const mockCalls = mocked( - pgConnector.ensureSchemaExists as Function, - ).mock.calls.splice(-1); - const [baseConfig, schemaName] = mockCalls[0]; - - expect(baseConfig.get()).toMatchObject({ - client: 'pg', - connection: 'postgresql://testuser:testpass@acme:5432/userdbname', - }); - - expect(schemaName).toEqual('catalog'); - }); - - it('throws error for non pg client', () => { - return expect( - ensureSchemaExists( - new ConfigReader({ - client: 'better-sqlite3', - schema: 'catalog', - connection: ':memory:', - }), - 'catalog', - ), - ).resolves.toBeUndefined(); - }); - }); - - describe('dropDatabase', () => { - it('returns successfully with pg client', async () => { - await dropDatabase( - new ConfigReader({ - client: 'pg', - schema: 'catalog', - connection: 'postgresql://testuser:testpass@acme:5432/userdbname', - }), - 'backstage_plugin_foobar', - ); - - const mockCalls = mocked( - pgConnector.dropDatabase as Function, - ).mock.calls.splice(-1); - const [baseConfig, databaseName] = mockCalls[0]; - - expect(baseConfig.get()).toMatchObject({ - client: 'pg', - connection: 'postgresql://testuser:testpass@acme:5432/userdbname', - }); - - expect(databaseName).toEqual('backstage_plugin_foobar'); - }); - - it('returns successfully with mysql client', async () => { - await dropDatabase( - new ConfigReader({ - client: 'mysql2', - connection: { - host: '127.0.0.1', - user: 'foo', - password: 'bar', - database: 'dbname', - }, - }), - 'backstage_plugin_foobar', - ); - - const mockCalls = mocked( - mysqlConnector.dropDatabase as Function, - ).mock.calls.splice(-1); - const [baseConfig, databaseName] = mockCalls[0]; - - expect(baseConfig.get()).toMatchObject({ - client: 'mysql2', - connection: { - host: '127.0.0.1', - user: 'foo', - password: 'bar', - database: 'dbname', - }, - }); - - expect(databaseName).toEqual('backstage_plugin_foobar'); - }); - - it('does nothing in other database drivers', () => { - return expect( - dropDatabase( - new ConfigReader({ - client: 'better-sqlite3', - schema: 'catalog', - connection: ':memory:', - }), - 'catalog', - ), - ).resolves.toBeUndefined(); - }); - }); -}); diff --git a/packages/backend-common/src/database/connection.ts b/packages/backend-common/src/database/connection.ts index c0e031516f..875734aa19 100644 --- a/packages/backend-common/src/database/connection.ts +++ b/packages/backend-common/src/database/connection.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -14,190 +14,205 @@ * limitations under the License. */ -import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; -import { InputError } from '@backstage/errors'; -import knexFactory, { Knex } from 'knex'; -import limiterFactory from 'p-limit'; -import { mergeDatabaseConfig } from './config'; -import { DatabaseConnector } from './types'; +// * Copyright 2020 The 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 { mysqlConnector, pgConnector, sqlite3Connector } from './connectors'; -import { - LifecycleService, - PluginMetadataService, -} from '@backstage/backend-plugin-api'; +// import { Config } from '@backstage/config'; +// import { JsonObject } from '@backstage/types'; +// import { InputError } from '@backstage/errors'; +// import knexFactory, { Knex } from 'knex'; +// import limiterFactory from 'p-limit'; +// import { mergeDatabaseConfig } from './config'; +// import { DatabaseConnector } from './types'; -type DatabaseClient = - | 'pg' - | 'better-sqlite3' - | 'sqlite3' - | 'mysql' - | 'mysql2' - | string; +// import { mysqlConnector, pgConnector, sqlite3Connector } from './connectors'; +// import { +// LifecycleService, +// PluginMetadataService, +// } from '@backstage/backend-plugin-api'; -// This limits the number of concurrent CREATE DATABASE and CREATE SCHEMA -// commands, globally, to just one. This is overly defensive, and was added as -// an attempt to counteract the pool issues on recent node versions. See -// https://github.com/backstage/backstage/pull/19988 -const ddlLimiter = limiterFactory(1); +// type DatabaseClient = +// | 'pg' +// | 'better-sqlite3' +// | 'sqlite3' +// | 'mysql' +// | 'mysql2' +// | string; -/** - * Mapping of client type to supported database connectors - * - * Database connectors can be aliased here, for example mysql2 uses - * the same connector as mysql. - */ -const ConnectorMapping: Record<DatabaseClient, DatabaseConnector> = { - pg: pgConnector, - 'better-sqlite3': sqlite3Connector, - sqlite3: sqlite3Connector, - mysql: mysqlConnector, - mysql2: mysqlConnector, -}; +// // This limits the number of concurrent CREATE DATABASE and CREATE SCHEMA +// // commands, globally, to just one. This is overly defensive, and was added as +// // an attempt to counteract the pool issues on recent node versions. See +// // https://github.com/backstage/backstage/pull/19988 +// const ddlLimiter = limiterFactory(1); -/** - * Creates a knex database connection - * - * @public - * @param dbConfig - The database config - * @param overrides - Additional options to merge with the config - */ -export function createDatabaseClient( - dbConfig: Config, - overrides?: Partial<Knex.Config>, - deps?: { - lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; - }, -) { - const client: DatabaseClient = dbConfig.getString('client'); +// /** +// * Mapping of client type to supported database connectors +// * +// * Database connectors can be aliased here, for example mysql2 uses +// * the same connector as mysql. +// */ +// const ConnectorMapping: Record<DatabaseClient, DatabaseConnector> = { +// pg: pgConnector, +// 'better-sqlite3': sqlite3Connector, +// sqlite3: sqlite3Connector, +// mysql: mysqlConnector, +// mysql2: mysqlConnector, +// }; - return ( - ConnectorMapping[client]?.createClient(dbConfig, overrides, deps) ?? - knexFactory(mergeDatabaseConfig(dbConfig.get(), overrides)) - ); -} +// /** +// * Creates a knex database connection +// * +// * @public +// * @param dbConfig - The database config +// * @param overrides - Additional options to merge with the config +// */ +// export function createDatabaseClient( +// dbConfig: Config, +// overrides?: Partial<Knex.Config>, +// deps?: { +// lifecycle: LifecycleService; +// pluginMetadata: PluginMetadataService; +// }, +// ) { +// const client: DatabaseClient = dbConfig.getString('client'); -/** - * Ensures that the given databases all exist, creating them if they do not. - * - * @public - */ -export async function ensureDatabaseExists( - dbConfig: Config, - ...databases: Array<string> -): Promise<void> { - const client: DatabaseClient = dbConfig.getString('client'); +// return ( +// ConnectorMapping[client]?.createClient(dbConfig, overrides, deps) ?? +// knexFactory(mergeDatabaseConfig(dbConfig.get(), overrides)) +// ); +// } - return await ddlLimiter(() => - ConnectorMapping[client]?.ensureDatabaseExists?.(dbConfig, ...databases), - ); -} +// /** +// * Ensures that the given databases all exist, creating them if they do not. +// * +// * @public +// */ +// export async function ensureDatabaseExists( +// dbConfig: Config, +// ...databases: Array<string> +// ): Promise<void> { +// const client: DatabaseClient = dbConfig.getString('client'); -/** - * Drops the given databases. - * - * @public - */ -export async function dropDatabase( - dbConfig: Config, - ...databases: Array<string> -): Promise<void> { - const client: DatabaseClient = dbConfig.getString('client'); +// return await ddlLimiter(() => +// ConnectorMapping[client]?.ensureDatabaseExists?.(dbConfig, ...databases), +// ); +// } - return await ddlLimiter(() => - ConnectorMapping[client]?.dropDatabase?.(dbConfig, ...databases), - ); -} +// /** +// * Drops the given databases. +// * +// * @public +// */ +// export async function dropDatabase( +// dbConfig: Config, +// ...databases: Array<string> +// ): Promise<void> { +// const client: DatabaseClient = dbConfig.getString('client'); -/** - * Ensures that the given schemas all exist, creating them if they do not. - * - * @public - */ -export async function ensureSchemaExists( - dbConfig: Config, - ...schemas: Array<string> -): Promise<void> { - const client: DatabaseClient = dbConfig.getString('client'); +// return await ddlLimiter(() => +// ConnectorMapping[client]?.dropDatabase?.(dbConfig, ...databases), +// ); +// } - return await ddlLimiter(() => - ConnectorMapping[client]?.ensureSchemaExists?.(dbConfig, ...schemas), - ); -} +// /** +// * Ensures that the given schemas all exist, creating them if they do not. +// * +// * @public +// */ +// export async function ensureSchemaExists( +// dbConfig: Config, +// ...schemas: Array<string> +// ): Promise<void> { +// const client: DatabaseClient = dbConfig.getString('client'); -/** - * Provides a `Knex.Config` object with the provided database name for a given - * client. - */ -export function createNameOverride( - client: string, - name: string, -): Partial<Knex.Config> { - try { - return ConnectorMapping[client].createNameOverride(name); - } catch (e) { - throw new InputError( - `Unable to create database name override for '${client}' connector`, - e, - ); - } -} +// return await ddlLimiter(() => +// ConnectorMapping[client]?.ensureSchemaExists?.(dbConfig, ...schemas), +// ); +// } -/** - * Provides a `Knex.Config` object with the provided database schema for a given - * client. Currently only supported by `pg`. - */ -export function createSchemaOverride( - client: string, - name: string, -): Partial<Knex.Config | undefined> { - try { - return ConnectorMapping[client]?.createSchemaOverride?.(name); - } catch (e) { - throw new InputError( - `Unable to create database schema override for '${client}' connector`, - e, - ); - } -} +// /** +// * Provides a `Knex.Config` object with the provided database name for a given +// * client. +// */ +// export function createNameOverride( +// client: string, +// name: string, +// ): Partial<Knex.Config> { +// try { +// return ConnectorMapping[client].createNameOverride(name); +// } catch (e) { +// throw new InputError( +// `Unable to create database name override for '${client}' connector`, +// e, +// ); +// } +// } -/** - * Parses a connection string for a given client and provides a connection config. - */ -export function parseConnectionString( - connectionString: string, - client?: string, -): Knex.StaticConnectionConfig { - if (typeof client === 'undefined' || client === null) { - throw new InputError( - 'Database connection string client type auto-detection is not yet supported.', - ); - } +// /** +// * Provides a `Knex.Config` object with the provided database schema for a given +// * client. Currently only supported by `pg`. +// */ +// export function createSchemaOverride( +// client: string, +// name: string, +// ): Partial<Knex.Config | undefined> { +// try { +// return ConnectorMapping[client]?.createSchemaOverride?.(name); +// } catch (e) { +// throw new InputError( +// `Unable to create database schema override for '${client}' connector`, +// e, +// ); +// } +// } - try { - return ConnectorMapping[client].parseConnectionString(connectionString); - } catch (e) { - throw new InputError( - `Unable to parse connection string for '${client}' connector`, - ); - } -} +// /** +// * Parses a connection string for a given client and provides a connection config. +// */ +// export function parseConnectionString( +// connectionString: string, +// client?: string, +// ): Knex.StaticConnectionConfig { +// if (typeof client === 'undefined' || client === null) { +// throw new InputError( +// 'Database connection string client type auto-detection is not yet supported.', +// ); +// } -/** - * Normalizes a connection config or string into an object which can be passed - * to Knex. - */ -export function normalizeConnection( - connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, - client: string, -): Partial<Knex.StaticConnectionConfig> { - if (typeof connection === 'undefined' || connection === null) { - return {}; - } +// try { +// return ConnectorMapping[client].parseConnectionString(connectionString); +// } catch (e) { +// throw new InputError( +// `Unable to parse connection string for '${client}' connector`, +// ); +// } +// } - return typeof connection === 'string' || connection instanceof String - ? parseConnectionString(connection as string, client) - : connection; -} +// /** +// * Normalizes a connection config or string into an object which can be passed +// * to Knex. +// */ +// export function normalizeConnection( +// connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, +// client: string, +// ): Partial<Knex.StaticConnectionConfig> { +// if (typeof connection === 'undefined' || connection === null) { +// return {}; +// } + +// return typeof connection === 'string' || connection instanceof String +// ? parseConnectionString(connection as string, client) +// : connection; +// } diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-common/src/database/connectors/mysql.ts index a4bac3f7e1..63464fa6f4 100644 --- a/packages/backend-common/src/database/connectors/mysql.ts +++ b/packages/backend-common/src/database/connectors/mysql.ts @@ -14,11 +14,17 @@ * limitations under the License. */ -import knexFactory, { Knex } from 'knex'; -import yn from 'yn'; - -import { Config } from '@backstage/config'; +import { + LifecycleService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; +import { Config, ConfigReader } from '@backstage/config'; import { InputError } from '@backstage/errors'; +import { JsonObject } from '@backstage/types'; +import knexFactory, { Knex } from 'knex'; +import { merge, omit } from 'lodash'; +import path from 'path'; +import yn from 'yn'; import { mergeDatabaseConfig } from '../config'; import { DatabaseConnector } from '../types'; import defaultNameOverride from './defaultNameOverride'; @@ -229,3 +235,325 @@ export const mysqlConnector: DatabaseConnector = Object.freeze({ parseConnectionString: parseMysqlConnectionString, dropDatabase: dropMysqlDatabase, }); + +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Provides a config lookup path for a plugin's config block. + */ +function pluginPath(pluginId: string): string { + return `plugin.${pluginId}`; +} + +function normalizeConnection( + connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, + client: string, +): Partial<Knex.StaticConnectionConfig> { + if (typeof connection === 'undefined' || connection === null) { + return {}; + } + + return typeof connection === 'string' || connection instanceof String + ? mysqlConnector.parseConnectionString(connection as string, client) + : connection; +} + +function createSchemaOverride( + client: string, + name: string, +): Partial<Knex.Config | undefined> { + try { + return mysqlConnector.createSchemaOverride?.(name); + } catch (e) { + throw new InputError( + `Unable to create database schema override for '${client}' connector`, + e, + ); + } +} + +function createNameOverride( + client: string, + name: string, +): Partial<Knex.Config> { + try { + return mysqlConnector.createNameOverride(name); + } catch (e) { + throw new InputError( + `Unable to create database name override for '${client}' connector`, + e, + ); + } +} + +export class MysqlConnector { + constructor( + private readonly config: Config, + private readonly prefix: string = 'backstage_plugin_', + ) {} + + async getClient( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Promise<Knex> { + const pluginConfig = new ConfigReader( + this.getConfigForPlugin(pluginId) as JsonObject, + ); + + const databaseName = this.getDatabaseName(pluginId); + if (databaseName && this.getEnsureExistsConfig(pluginId)) { + try { + await mysqlConnector.ensureDatabaseExists(pluginConfig, databaseName); + } catch (error) { + throw new Error( + `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, + ); + } + } + + let schemaOverrides; + if (this.getPluginDivisionModeConfig() === 'schema') { + schemaOverrides = this.getSchemaOverrides(pluginId); + if (this.getEnsureExistsConfig(pluginId)) { + try { + await mysqlConnector.ensureSchemaExists(pluginConfig, pluginId); + } catch (error) { + throw new Error( + `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`, + ); + } + } + } + + const databaseClientOverrides = mergeDatabaseConfig( + {}, + this.getDatabaseOverrides(pluginId), + schemaOverrides, + ); + + const client = mysqlConnector.createClient( + pluginConfig, + databaseClientOverrides, + deps, + ); + + return client; + } + + /** + * Provides the canonical database name for a given plugin. + * + * This method provides the effective database name which is determined using global + * and plugin specific database config. If no explicit database name is configured + * and `pluginDivisionMode` is not `schema`, this method will provide a generated name + * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is + * `schema`, it will fallback to using the default database for the knex instance. + * + * @param pluginId - Lookup the database name for given plugin + * @returns String representing the plugin's database name + */ + private getDatabaseName(pluginId: string): string | undefined { + const connection = this.getConnectionConfig(pluginId); + + if (this.getClientType(pluginId).client.includes('sqlite3')) { + const sqliteFilename: string | undefined = ( + connection as Knex.Sqlite3ConnectionConfig + ).filename; + + if (sqliteFilename === ':memory:') { + return sqliteFilename; + } + + const sqliteDirectory = + (connection as { directory?: string }).directory ?? '.'; + + return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`); + } + + const databaseName = (connection as Knex.ConnectionConfig)?.database; + + // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database + if (this.getPluginDivisionModeConfig() === 'schema') { + return databaseName; + } + + // all other supported databases should fallback to an auto-prefixed name + return databaseName ?? `${this.prefix}${pluginId}`; + } + + /** + * Provides the client type which should be used for a given plugin. + * + * The client type is determined by plugin specific config if present. + * Otherwise the base client is used as the fallback. + * + * @param pluginId - Plugin to get the client type for + * @returns Object with client type returned as `client` and boolean + * representing whether or not the client was overridden as + * `overridden` + */ + private getClientType(pluginId: string): { + client: string; + overridden: boolean; + } { + const pluginClient = this.config.getOptionalString( + `${pluginPath(pluginId)}.client`, + ); + + const baseClient = this.config.getString('client'); + const client = pluginClient ?? baseClient; + return { + client, + overridden: client !== baseClient, + }; + } + + private getRoleConfig(pluginId: string): string | undefined { + return ( + this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ?? + this.config.getOptionalString('role') + ); + } + + /** + * Provides the knexConfig which should be used for a given plugin. + * + * @param pluginId - Plugin to get the knexConfig for + * @returns The merged knexConfig value or undefined if it isn't specified + */ + private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined { + const pluginConfig = this.config + .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`) + ?.get<JsonObject>(); + + const baseConfig = this.config + .getOptionalConfig('knexConfig') + ?.get<JsonObject>(); + + return merge(baseConfig, pluginConfig); + } + + private getEnsureExistsConfig(pluginId: string): boolean { + const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true; + return ( + this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ?? + baseConfig + ); + } + + private getPluginDivisionModeConfig(): string { + return this.config.getOptionalString('pluginDivisionMode') ?? 'database'; + } + + /** + * Provides a Knex connection plugin config by combining base and plugin + * config. + * + * This method provides a baseConfig for a plugin database connector. If the + * client type has not been overridden, the global connection config will be + * included with plugin specific config as the base. Values from the plugin + * connection take precedence over the base. Base database name is omitted for + * all supported databases excluding SQLite unless `pluginDivisionMode` is set + * to `schema`. + */ + private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig { + const { client, overridden } = this.getClientType(pluginId); + + let baseConnection = normalizeConnection( + this.config.get('connection'), + this.config.getString('client'), + ); + + if ( + client.includes('sqlite3') && + 'filename' in baseConnection && + baseConnection.filename !== ':memory:' + ) { + throw new Error( + '`connection.filename` is not supported for the base sqlite connection. Prefer `connection.directory` or provide a filename for the plugin connection instead.', + ); + } + + // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The + // `database` property from the base connection is omitted unless `pluginDivisionMode` + // is set to `schema`. SQLite3's `filename` property is an exception as this is used as a + // directory elsewhere so we preserve `filename`. + if (this.getPluginDivisionModeConfig() !== 'schema') { + baseConnection = omit(baseConnection, 'database'); + } + + // get and normalize optional plugin specific database connection + const connection = normalizeConnection( + this.config.getOptional(`${pluginPath(pluginId)}.connection`), + client, + ); + + if (client === 'pg') { + ( + baseConnection as Knex.PgConnectionConfig + ).application_name ||= `backstage_plugin_${pluginId}`; + } + + return { + // include base connection if client type has not been overridden + ...(overridden ? {} : baseConnection), + ...connection, + } as Knex.StaticConnectionConfig; + } + + /** + * Provides a Knex database config for a given plugin. + * + * This method provides a Knex configuration object along with the plugin's + * client type. + * + * @param pluginId - The plugin that the database config should correspond with + */ + private getConfigForPlugin(pluginId: string): Knex.Config { + const { client } = this.getClientType(pluginId); + const role = this.getRoleConfig(pluginId); + + return { + ...this.getAdditionalKnexConfig(pluginId), + client, + connection: this.getConnectionConfig(pluginId), + ...(role && { role }), + }; + } + + /** + * Provides a partial `Knex.Config` database schema override for a given + * plugin. + * + * @param pluginId - Target plugin to get database schema override + * @returns Partial `Knex.Config` with database schema override + */ + private getSchemaOverrides(pluginId: string): Knex.Config | undefined { + return createSchemaOverride(this.getClientType(pluginId).client, pluginId); + } + + /** + * Provides a partial `Knex.Config`• database name override for a given plugin. + * + * @param pluginId - Target plugin to get database name override + * @returns Partial `Knex.Config` with database name override + */ + private getDatabaseOverrides(pluginId: string): Knex.Config { + const databaseName = this.getDatabaseName(pluginId); + return databaseName + ? createNameOverride(this.getClientType(pluginId).client, databaseName) + : {}; + } +} diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index dc9b19f6ad..0a3aa6cb74 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -14,15 +14,21 @@ * limitations under the License. */ +import { + LifecycleService, + PluginMetadataService, +} from '@backstage/backend-plugin-api'; +import { Config, ConfigReader } from '@backstage/config'; +import { ForwardedError, InputError } from '@backstage/errors'; +import { JsonObject } from '@backstage/types'; import knexFactory, { Knex } from 'knex'; - -import { Config } from '@backstage/config'; -import { ForwardedError } from '@backstage/errors'; +import { merge, omit } from 'lodash'; +import path from 'path'; +import { Client } from 'pg'; import { mergeDatabaseConfig } from '../config'; import { DatabaseConnector } from '../types'; import defaultNameOverride from './defaultNameOverride'; import defaultSchemaOverride from './defaultSchemaOverride'; -import { Client } from 'pg'; /** * Creates a knex postgres database connection @@ -217,11 +223,6 @@ export async function dropPgDatabase( ); } -/** - * PostgreSQL database connector. - * - * Exposes database connector functionality via an immutable object. - */ export const pgConnector: DatabaseConnector = Object.freeze({ createClient: createPgDatabaseClient, ensureDatabaseExists: ensurePgDatabaseExists, @@ -231,3 +232,325 @@ export const pgConnector: DatabaseConnector = Object.freeze({ parseConnectionString: parsePgConnectionString, dropDatabase: dropPgDatabase, }); + +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Provides a config lookup path for a plugin's config block. + */ +function pluginPath(pluginId: string): string { + return `plugin.${pluginId}`; +} + +function normalizeConnection( + connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, + client: string, +): Partial<Knex.StaticConnectionConfig> { + if (typeof connection === 'undefined' || connection === null) { + return {}; + } + + return typeof connection === 'string' || connection instanceof String + ? pgConnector.parseConnectionString(connection as string, client) + : connection; +} + +function createSchemaOverride( + client: string, + name: string, +): Partial<Knex.Config | undefined> { + try { + return pgConnector.createSchemaOverride?.(name); + } catch (e) { + throw new InputError( + `Unable to create database schema override for '${client}' connector`, + e, + ); + } +} + +function createNameOverride( + client: string, + name: string, +): Partial<Knex.Config> { + try { + return pgConnector.createNameOverride(name); + } catch (e) { + throw new InputError( + `Unable to create database name override for '${client}' connector`, + e, + ); + } +} + +export class PgConnector { + constructor( + private readonly config: Config, + private readonly prefix: string = 'backstage_plugin_', + ) {} + + async getClient( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Promise<Knex> { + const pluginConfig = new ConfigReader( + this.getConfigForPlugin(pluginId) as JsonObject, + ); + + const databaseName = this.getDatabaseName(pluginId); + if (databaseName && this.getEnsureExistsConfig(pluginId)) { + try { + await pgConnector.ensureDatabaseExists(pluginConfig, databaseName); + } catch (error) { + throw new Error( + `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, + ); + } + } + + let schemaOverrides; + if (this.getPluginDivisionModeConfig() === 'schema') { + schemaOverrides = this.getSchemaOverrides(pluginId); + if (this.getEnsureExistsConfig(pluginId)) { + try { + await pgConnector.ensureSchemaExists(pluginConfig, pluginId); + } catch (error) { + throw new Error( + `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`, + ); + } + } + } + + const databaseClientOverrides = mergeDatabaseConfig( + {}, + this.getDatabaseOverrides(pluginId), + schemaOverrides, + ); + + const client = pgConnector.createClient( + pluginConfig, + databaseClientOverrides, + deps, + ); + + return client; + } + + /** + * Provides the canonical database name for a given plugin. + * + * This method provides the effective database name which is determined using global + * and plugin specific database config. If no explicit database name is configured + * and `pluginDivisionMode` is not `schema`, this method will provide a generated name + * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is + * `schema`, it will fallback to using the default database for the knex instance. + * + * @param pluginId - Lookup the database name for given plugin + * @returns String representing the plugin's database name + */ + private getDatabaseName(pluginId: string): string | undefined { + const connection = this.getConnectionConfig(pluginId); + + if (this.getClientType(pluginId).client.includes('sqlite3')) { + const sqliteFilename: string | undefined = ( + connection as Knex.Sqlite3ConnectionConfig + ).filename; + + if (sqliteFilename === ':memory:') { + return sqliteFilename; + } + + const sqliteDirectory = + (connection as { directory?: string }).directory ?? '.'; + + return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`); + } + + const databaseName = (connection as Knex.ConnectionConfig)?.database; + + // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database + if (this.getPluginDivisionModeConfig() === 'schema') { + return databaseName; + } + + // all other supported databases should fallback to an auto-prefixed name + return databaseName ?? `${this.prefix}${pluginId}`; + } + + /** + * Provides the client type which should be used for a given plugin. + * + * The client type is determined by plugin specific config if present. + * Otherwise the base client is used as the fallback. + * + * @param pluginId - Plugin to get the client type for + * @returns Object with client type returned as `client` and boolean + * representing whether or not the client was overridden as + * `overridden` + */ + private getClientType(pluginId: string): { + client: string; + overridden: boolean; + } { + const pluginClient = this.config.getOptionalString( + `${pluginPath(pluginId)}.client`, + ); + + const baseClient = this.config.getString('client'); + const client = pluginClient ?? baseClient; + return { + client, + overridden: client !== baseClient, + }; + } + + private getRoleConfig(pluginId: string): string | undefined { + return ( + this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ?? + this.config.getOptionalString('role') + ); + } + + /** + * Provides the knexConfig which should be used for a given plugin. + * + * @param pluginId - Plugin to get the knexConfig for + * @returns The merged knexConfig value or undefined if it isn't specified + */ + private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined { + const pluginConfig = this.config + .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`) + ?.get<JsonObject>(); + + const baseConfig = this.config + .getOptionalConfig('knexConfig') + ?.get<JsonObject>(); + + return merge(baseConfig, pluginConfig); + } + + private getEnsureExistsConfig(pluginId: string): boolean { + const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true; + return ( + this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ?? + baseConfig + ); + } + + private getPluginDivisionModeConfig(): string { + return this.config.getOptionalString('pluginDivisionMode') ?? 'database'; + } + + /** + * Provides a Knex connection plugin config by combining base and plugin + * config. + * + * This method provides a baseConfig for a plugin database connector. If the + * client type has not been overridden, the global connection config will be + * included with plugin specific config as the base. Values from the plugin + * connection take precedence over the base. Base database name is omitted for + * all supported databases excluding SQLite unless `pluginDivisionMode` is set + * to `schema`. + */ + private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig { + const { client, overridden } = this.getClientType(pluginId); + + let baseConnection = normalizeConnection( + this.config.get('connection'), + this.config.getString('client'), + ); + + if ( + client.includes('sqlite3') && + 'filename' in baseConnection && + baseConnection.filename !== ':memory:' + ) { + throw new Error( + '`connection.filename` is not supported for the base sqlite connection. Prefer `connection.directory` or provide a filename for the plugin connection instead.', + ); + } + + // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The + // `database` property from the base connection is omitted unless `pluginDivisionMode` + // is set to `schema`. SQLite3's `filename` property is an exception as this is used as a + // directory elsewhere so we preserve `filename`. + if (this.getPluginDivisionModeConfig() !== 'schema') { + baseConnection = omit(baseConnection, 'database'); + } + + // get and normalize optional plugin specific database connection + const connection = normalizeConnection( + this.config.getOptional(`${pluginPath(pluginId)}.connection`), + client, + ); + + if (client === 'pg') { + ( + baseConnection as Knex.PgConnectionConfig + ).application_name ||= `backstage_plugin_${pluginId}`; + } + + return { + // include base connection if client type has not been overridden + ...(overridden ? {} : baseConnection), + ...connection, + } as Knex.StaticConnectionConfig; + } + + /** + * Provides a Knex database config for a given plugin. + * + * This method provides a Knex configuration object along with the plugin's + * client type. + * + * @param pluginId - The plugin that the database config should correspond with + */ + private getConfigForPlugin(pluginId: string): Knex.Config { + const { client } = this.getClientType(pluginId); + const role = this.getRoleConfig(pluginId); + + return { + ...this.getAdditionalKnexConfig(pluginId), + client, + connection: this.getConnectionConfig(pluginId), + ...(role && { role }), + }; + } + + /** + * Provides a partial `Knex.Config` database schema override for a given + * plugin. + * + * @param pluginId - Target plugin to get database schema override + * @returns Partial `Knex.Config` with database schema override + */ + private getSchemaOverrides(pluginId: string): Knex.Config | undefined { + return createSchemaOverride(this.getClientType(pluginId).client, pluginId); + } + + /** + * Provides a partial `Knex.Config`• database name override for a given plugin. + * + * @param pluginId - Target plugin to get database name override + * @returns Partial `Knex.Config` with database name override + */ + private getDatabaseOverrides(pluginId: string): Knex.Config { + const databaseName = this.getDatabaseName(pluginId); + return databaseName + ? createNameOverride(this.getClientType(pluginId).client, databaseName) + : {}; + } +} diff --git a/packages/backend-common/src/database/connectors/sqlite3.ts b/packages/backend-common/src/database/connectors/sqlite3.ts index 3cb0f57de6..5714b036e3 100644 --- a/packages/backend-common/src/database/connectors/sqlite3.ts +++ b/packages/backend-common/src/database/connectors/sqlite3.ts @@ -14,17 +14,20 @@ * limitations under the License. */ -import { Config } from '@backstage/config'; -import { ensureDirSync } from 'fs-extra'; -import knexFactory, { Knex } from 'knex'; -import path from 'path'; import { DevDataStore } from '@backstage/backend-dev-utils'; -import { mergeDatabaseConfig } from '../config'; -import { DatabaseConnector } from '../types'; import { LifecycleService, PluginMetadataService, } from '@backstage/backend-plugin-api'; +import { Config, ConfigReader } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { JsonObject } from '@backstage/types'; +import { ensureDirSync } from 'fs-extra'; +import knexFactory, { Knex } from 'knex'; +import { merge, omit } from 'lodash'; +import path from 'path'; +import { mergeDatabaseConfig } from '../config'; +import { DatabaseConnector } from '../types'; /** * Creates a knex SQLite3 database connection @@ -159,8 +162,330 @@ export function parseSqliteConnectionString( * * Exposes database connector functionality via an immutable object. */ -export const sqlite3Connector: DatabaseConnector = Object.freeze({ +export const sqliteConnector: DatabaseConnector = Object.freeze({ createClient: createSqliteDatabaseClient, createNameOverride: createSqliteNameOverride, parseConnectionString: parseSqliteConnectionString, }); + +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Provides a config lookup path for a plugin's config block. + */ +function pluginPath(pluginId: string): string { + return `plugin.${pluginId}`; +} + +function normalizeConnection( + connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, + client: string, +): Partial<Knex.StaticConnectionConfig> { + if (typeof connection === 'undefined' || connection === null) { + return {}; + } + + return typeof connection === 'string' || connection instanceof String + ? sqliteConnector.parseConnectionString(connection as string, client) + : connection; +} + +function createSchemaOverride( + client: string, + name: string, +): Partial<Knex.Config | undefined> { + try { + return sqliteConnector.createSchemaOverride?.(name); + } catch (e) { + throw new InputError( + `Unable to create database schema override for '${client}' connector`, + e, + ); + } +} + +function createNameOverride( + client: string, + name: string, +): Partial<Knex.Config> { + try { + return sqliteConnector.createNameOverride(name); + } catch (e) { + throw new InputError( + `Unable to create database name override for '${client}' connector`, + e, + ); + } +} + +export class Sqlite3Connector implements DatabaseConnector { + constructor( + private readonly config: Config, + private readonly prefix: string = 'backstage_plugin_', + ) {} + + async getClient( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Promise<Knex> { + const pluginConfig = new ConfigReader( + this.getConfigForPlugin(pluginId) as JsonObject, + ); + + const databaseName = this.getDatabaseName(pluginId); + if (databaseName && this.getEnsureExistsConfig(pluginId)) { + try { + await sqliteConnector.ensureDatabaseExists(pluginConfig, databaseName); + } catch (error) { + throw new Error( + `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, + ); + } + } + + let schemaOverrides; + if (this.getPluginDivisionModeConfig() === 'schema') { + schemaOverrides = this.getSchemaOverrides(pluginId); + if (this.getEnsureExistsConfig(pluginId)) { + try { + await sqliteConnector.ensureSchemaExists(pluginConfig, pluginId); + } catch (error) { + throw new Error( + `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`, + ); + } + } + } + + const databaseClientOverrides = mergeDatabaseConfig( + {}, + this.getDatabaseOverrides(pluginId), + schemaOverrides, + ); + + const client = sqliteConnector.createClient( + pluginConfig, + databaseClientOverrides, + deps, + ); + + return client; + } + + /** + * Provides the canonical database name for a given plugin. + * + * This method provides the effective database name which is determined using global + * and plugin specific database config. If no explicit database name is configured + * and `pluginDivisionMode` is not `schema`, this method will provide a generated name + * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is + * `schema`, it will fallback to using the default database for the knex instance. + * + * @param pluginId - Lookup the database name for given plugin + * @returns String representing the plugin's database name + */ + private getDatabaseName(pluginId: string): string | undefined { + const connection = this.getConnectionConfig(pluginId); + + if (this.getClientType(pluginId).client.includes('sqlite3')) { + const sqliteFilename: string | undefined = ( + connection as Knex.Sqlite3ConnectionConfig + ).filename; + + if (sqliteFilename === ':memory:') { + return sqliteFilename; + } + + const sqliteDirectory = + (connection as { directory?: string }).directory ?? '.'; + + return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`); + } + + const databaseName = (connection as Knex.ConnectionConfig)?.database; + + // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database + if (this.getPluginDivisionModeConfig() === 'schema') { + return databaseName; + } + + // all other supported databases should fallback to an auto-prefixed name + return databaseName ?? `${this.prefix}${pluginId}`; + } + + /** + * Provides the client type which should be used for a given plugin. + * + * The client type is determined by plugin specific config if present. + * Otherwise the base client is used as the fallback. + * + * @param pluginId - Plugin to get the client type for + * @returns Object with client type returned as `client` and boolean + * representing whether or not the client was overridden as + * `overridden` + */ + private getClientType(pluginId: string): { + client: string; + overridden: boolean; + } { + const pluginClient = this.config.getOptionalString( + `${pluginPath(pluginId)}.client`, + ); + + const baseClient = this.config.getString('client'); + const client = pluginClient ?? baseClient; + return { + client, + overridden: client !== baseClient, + }; + } + + private getRoleConfig(pluginId: string): string | undefined { + return ( + this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ?? + this.config.getOptionalString('role') + ); + } + + /** + * Provides the knexConfig which should be used for a given plugin. + * + * @param pluginId - Plugin to get the knexConfig for + * @returns The merged knexConfig value or undefined if it isn't specified + */ + private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined { + const pluginConfig = this.config + .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`) + ?.get<JsonObject>(); + + const baseConfig = this.config + .getOptionalConfig('knexConfig') + ?.get<JsonObject>(); + + return merge(baseConfig, pluginConfig); + } + + private getEnsureExistsConfig(pluginId: string): boolean { + const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true; + return ( + this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ?? + baseConfig + ); + } + + private getPluginDivisionModeConfig(): string { + return this.config.getOptionalString('pluginDivisionMode') ?? 'database'; + } + + /** + * Provides a Knex connection plugin config by combining base and plugin + * config. + * + * This method provides a baseConfig for a plugin database connector. If the + * client type has not been overridden, the global connection config will be + * included with plugin specific config as the base. Values from the plugin + * connection take precedence over the base. Base database name is omitted for + * all supported databases excluding SQLite unless `pluginDivisionMode` is set + * to `schema`. + */ + private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig { + const { client, overridden } = this.getClientType(pluginId); + + let baseConnection = normalizeConnection( + this.config.get('connection'), + this.config.getString('client'), + ); + + if ( + client.includes('sqlite3') && + 'filename' in baseConnection && + baseConnection.filename !== ':memory:' + ) { + throw new Error( + '`connection.filename` is not supported for the base sqlite connection. Prefer `connection.directory` or provide a filename for the plugin connection instead.', + ); + } + + // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The + // `database` property from the base connection is omitted unless `pluginDivisionMode` + // is set to `schema`. SQLite3's `filename` property is an exception as this is used as a + // directory elsewhere so we preserve `filename`. + if (this.getPluginDivisionModeConfig() !== 'schema') { + baseConnection = omit(baseConnection, 'database'); + } + + // get and normalize optional plugin specific database connection + const connection = normalizeConnection( + this.config.getOptional(`${pluginPath(pluginId)}.connection`), + client, + ); + + if (client === 'pg') { + ( + baseConnection as Knex.PgConnectionConfig + ).application_name ||= `backstage_plugin_${pluginId}`; + } + + return { + // include base connection if client type has not been overridden + ...(overridden ? {} : baseConnection), + ...connection, + } as Knex.StaticConnectionConfig; + } + + /** + * Provides a Knex database config for a given plugin. + * + * This method provides a Knex configuration object along with the plugin's + * client type. + * + * @param pluginId - The plugin that the database config should correspond with + */ + private getConfigForPlugin(pluginId: string): Knex.Config { + const { client } = this.getClientType(pluginId); + const role = this.getRoleConfig(pluginId); + + return { + ...this.getAdditionalKnexConfig(pluginId), + client, + connection: this.getConnectionConfig(pluginId), + ...(role && { role }), + }; + } + + /** + * Provides a partial `Knex.Config` database schema override for a given + * plugin. + * + * @param pluginId - Target plugin to get database schema override + * @returns Partial `Knex.Config` with database schema override + */ + private getSchemaOverrides(pluginId: string): Knex.Config | undefined { + return createSchemaOverride(this.getClientType(pluginId).client, pluginId); + } + + /** + * Provides a partial `Knex.Config`• database name override for a given plugin. + * + * @param pluginId - Target plugin to get database name override + * @returns Partial `Knex.Config` with database name override + */ + private getDatabaseOverrides(pluginId: string): Knex.Config { + const databaseName = this.getDatabaseName(pluginId); + return databaseName + ? createNameOverride(this.getClientType(pluginId).client, databaseName) + : {}; + } +} diff --git a/packages/backend-common/src/database/types.ts b/packages/backend-common/src/database/types.ts index 3e9832aec5..c73540a9c1 100644 --- a/packages/backend-common/src/database/types.ts +++ b/packages/backend-common/src/database/types.ts @@ -24,11 +24,11 @@ import { Knex } from 'knex'; export type { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api'; /** - * DatabaseConnector manages an underlying Knex database driver. + * Manages an underlying Knex database driver. */ export interface DatabaseConnector { /** - * createClient provides an instance of a knex database connector. + * Provides an instance of a knex database connector. */ createClient( dbConfig: Config, @@ -38,27 +38,29 @@ export interface DatabaseConnector { pluginMetadata: PluginMetadataService; }, ): Knex; + /** - * createNameOverride provides a partial knex config sufficient to override a - * database name. + * Provides a partial knex config sufficient to override a database name. */ createNameOverride(name: string): Partial<Knex.Config>; + /** - * createSchemaOverride provides a partial knex config sufficient to override a - * PostgreSQL schema name within utilizing the `searchPath` knex configuration. + * Provides a partial knex config sufficient to override a PostgreSQL schema + * name within utilizing the `searchPath` knex configuration. */ createSchemaOverride?(name: string): Partial<Knex.Config>; + /** - * parseConnectionString produces a knex connection config object representing - * a database connection string. + * Produces a knex connection config object representing a database connection + * string. */ parseConnectionString( connectionString: string, client?: string, ): Knex.StaticConnectionConfig; + /** - * ensureDatabaseExists performs a side-effect to ensure database names passed in are - * present. + * Performs a side-effect to ensure database names passed in are present. * * Calling this function on databases which already exist should do nothing. * Missing databases should be created if needed. @@ -69,8 +71,7 @@ export interface DatabaseConnector { ): Promise<void>; /** - * ensureSchemaExists performs a side-effect to ensure schema names passed in are - * present. + * Performs a side-effect to ensure schema names passed in are present. * * Calling this function on schemas which already exist should do nothing. * Missing schemas should be created if needed. @@ -80,5 +81,8 @@ export interface DatabaseConnector { ...schemas: Array<string> ): Promise<void>; + /** + * Deletes databases. + */ dropDatabase?(dbConfig: Config, ...databases: Array<string>): Promise<void>; } From c936bfc67cdea60edf8f6c6704cf9ec7d8616a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Sun, 28 Apr 2024 11:47:53 +0200 Subject: [PATCH 146/234] slim down the origin database manager MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- .../src/database/DatabaseManager.test.ts | 2 +- .../src/database/DatabaseManager.ts | 275 ++---------------- .../connectors/defaultNameOverride.test.ts | 1 + .../connectors/defaultNameOverride.ts | 1 + .../connectors/defaultSchemaOverride.test.ts | 1 + .../connectors/defaultSchemaOverride.ts | 1 + .../src/database/connectors/index.ts | 1 + .../mergeDatabaseConfig.test.ts} | 4 +- .../mergeDatabaseConfig.ts} | 0 .../src/database/connectors/mysql.ts | 6 +- .../src/database/connectors/postgres.test.ts | 2 +- .../src/database/connectors/postgres.ts | 6 +- .../src/database/connectors/sqlite3.ts | 6 +- packages/backend-common/src/database/index.ts | 2 +- packages/backend-common/src/database/types.ts | 10 + 15 files changed, 50 insertions(+), 268 deletions(-) rename packages/backend-common/src/database/{config.test.ts => connectors/mergeDatabaseConfig.test.ts} (97%) rename packages/backend-common/src/database/{config.ts => connectors/mergeDatabaseConfig.ts} (100%) diff --git a/packages/backend-common/src/database/DatabaseManager.test.ts b/packages/backend-common/src/database/DatabaseManager.test.ts index bf4af17d05..6a461fad55 100644 --- a/packages/backend-common/src/database/DatabaseManager.test.ts +++ b/packages/backend-common/src/database/DatabaseManager.test.ts @@ -16,12 +16,12 @@ import { ConfigReader } from '@backstage/config'; import { omit } from 'lodash'; import path from 'path'; +import { DatabaseManager } from './DatabaseManager'; import { createDatabaseClient, ensureDatabaseExists, ensureSchemaExists, } from './connection'; -import { DatabaseManager } from './DatabaseManager'; jest.mock('./connection', () => ({ ...jest.requireActual('./connection'), diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts index 776f05aed4..a261cd13fc 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -14,23 +14,19 @@ * limitations under the License. */ -import { Config, ConfigReader } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; -import { Knex } from 'knex'; -import { merge, omit } from 'lodash'; -import { mergeDatabaseConfig } from './config'; -import { PluginDatabaseManager } from './types'; -import path from 'path'; import { DatabaseService, LifecycleService, LoggerService, PluginMetadataService, } from '@backstage/backend-plugin-api'; +import { Config } from '@backstage/config'; import { stringifyError } from '@backstage/errors'; +import { Knex } from 'knex'; +import { MysqlConnector } from './connectors/mysql'; import { PgConnector } from './connectors/postgres'; import { Sqlite3Connector } from './connectors/sqlite3'; -import { MysqlConnector } from './connectors/mysql'; +import { Connector, PluginDatabaseManager } from './types'; /** * Provides a config lookup path for a plugin's config block. @@ -81,15 +77,15 @@ export class DatabaseManager implements LegacyRootDatabaseService { options?: DatabaseManagerOptions, ): DatabaseManager { const databaseConfig = config.getConfig('backend.database'); + const prefix = databaseConfig.getOptionalString('prefix'); return new DatabaseManager( databaseConfig, - databaseConfig.getOptionalString('prefix'), { - pg: new PgConnector(config, prefix, options), - sqlite3: new Sqlite3Connector(config, prefix, options), - 'better-sqlite3': new Sqlite3Connector(config, prefix, options), - mysql: new MysqlConnector(config, prefix, options), - mysql2: new MysqlConnector(config, prefix, options), + pg: new PgConnector(config, prefix), + sqlite3: new Sqlite3Connector(config, prefix), + 'better-sqlite3': new Sqlite3Connector(config, prefix), + mysql: new MysqlConnector(config, prefix), + mysql2: new MysqlConnector(config, prefix), }, options, ); @@ -97,19 +93,7 @@ export class DatabaseManager implements LegacyRootDatabaseService { private constructor( private readonly config: Config, - private readonly prefix: string = 'backstage_plugin_', - private readonly connectors: Record< - string, - { - getClient( - pluginId: string, - deps?: { - lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; - }, - ): Promise<Knex>; - } - >, + private readonly connectors: Record<string, Connector>, private readonly options?: DatabaseManagerOptions, private readonly databaseCache: Map<string, Promise<Knex>> = new Map(), ) {} @@ -131,54 +115,15 @@ export class DatabaseManager implements LegacyRootDatabaseService { const client = this.getClientType(pluginId).client; const connector = this.connectors[client]; if (!connector) { - throw new Error(`Unsupported database client type '${client}'`); + throw new Error( + `Unsupported database client type '${client}' specified for plugin '${pluginId}'`, + ); } - const getClient = () => this.getDatabase(pluginId, deps); + const getClient = () => this.getDatabase(pluginId, connector, deps); const migrations = { skip: false, ...this.options?.migrations }; return { getClient, migrations }; } - /** - * Provides the canonical database name for a given plugin. - * - * This method provides the effective database name which is determined using global - * and plugin specific database config. If no explicit database name is configured - * and `pluginDivisionMode` is not `schema`, this method will provide a generated name - * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is - * `schema`, it will fallback to using the default database for the knex instance. - * - * @param pluginId - Lookup the database name for given plugin - * @returns String representing the plugin's database name - */ - private getDatabaseName(pluginId: string): string | undefined { - const connection = this.getConnectionConfig(pluginId); - - if (this.getClientType(pluginId).client.includes('sqlite3')) { - const sqliteFilename: string | undefined = ( - connection as Knex.Sqlite3ConnectionConfig - ).filename; - - if (sqliteFilename === ':memory:') { - return sqliteFilename; - } - - const sqliteDirectory = - (connection as { directory?: string }).directory ?? '.'; - - return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`); - } - - const databaseName = (connection as Knex.ConnectionConfig)?.database; - - // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database - if (this.getPluginDivisionModeConfig() === 'schema') { - return databaseName; - } - - // all other supported databases should fallback to an auto-prefixed name - return databaseName ?? `${this.prefix}${pluginId}`; - } - /** * Provides the client type which should be used for a given plugin. * @@ -206,143 +151,6 @@ export class DatabaseManager implements LegacyRootDatabaseService { }; } - private getRoleConfig(pluginId: string): string | undefined { - return ( - this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ?? - this.config.getOptionalString('role') - ); - } - - /** - * Provides the knexConfig which should be used for a given plugin. - * - * @param pluginId - Plugin to get the knexConfig for - * @returns The merged knexConfig value or undefined if it isn't specified - */ - private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined { - const pluginConfig = this.config - .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`) - ?.get<JsonObject>(); - - const baseConfig = this.config - .getOptionalConfig('knexConfig') - ?.get<JsonObject>(); - - return merge(baseConfig, pluginConfig); - } - - private getEnsureExistsConfig(pluginId: string): boolean { - const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true; - return ( - this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ?? - baseConfig - ); - } - - private getPluginDivisionModeConfig(): string { - return this.config.getOptionalString('pluginDivisionMode') ?? 'database'; - } - - /** - * Provides a Knex connection plugin config by combining base and plugin - * config. - * - * This method provides a baseConfig for a plugin database connector. If the - * client type has not been overridden, the global connection config will be - * included with plugin specific config as the base. Values from the plugin - * connection take precedence over the base. Base database name is omitted for - * all supported databases excluding SQLite unless `pluginDivisionMode` is set - * to `schema`. - */ - private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig { - const { client, overridden } = this.getClientType(pluginId); - - let baseConnection = normalizeConnection( - this.config.get('connection'), - this.config.getString('client'), - ); - - if ( - client.includes('sqlite3') && - 'filename' in baseConnection && - baseConnection.filename !== ':memory:' - ) { - throw new Error( - '`connection.filename` is not supported for the base sqlite connection. Prefer `connection.directory` or provide a filename for the plugin connection instead.', - ); - } - - // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The - // `database` property from the base connection is omitted unless `pluginDivisionMode` - // is set to `schema`. SQLite3's `filename` property is an exception as this is used as a - // directory elsewhere so we preserve `filename`. - if (this.getPluginDivisionModeConfig() !== 'schema') { - baseConnection = omit(baseConnection, 'database'); - } - - // get and normalize optional plugin specific database connection - const connection = normalizeConnection( - this.config.getOptional(`${pluginPath(pluginId)}.connection`), - client, - ); - - if (client === 'pg') { - ( - baseConnection as Knex.PgConnectionConfig - ).application_name ||= `backstage_plugin_${pluginId}`; - } - - return { - // include base connection if client type has not been overridden - ...(overridden ? {} : baseConnection), - ...connection, - } as Knex.StaticConnectionConfig; - } - - /** - * Provides a Knex database config for a given plugin. - * - * This method provides a Knex configuration object along with the plugin's - * client type. - * - * @param pluginId - The plugin that the database config should correspond with - */ - private getConfigForPlugin(pluginId: string): Knex.Config { - const { client } = this.getClientType(pluginId); - const role = this.getRoleConfig(pluginId); - - return { - ...this.getAdditionalKnexConfig(pluginId), - client, - connection: this.getConnectionConfig(pluginId), - ...(role && { role }), - }; - } - - /** - * Provides a partial `Knex.Config` database schema override for a given - * plugin. - * - * @param pluginId - Target plugin to get database schema override - * @returns Partial `Knex.Config` with database schema override - */ - private getSchemaOverrides(pluginId: string): Knex.Config | undefined { - return createSchemaOverride(this.getClientType(pluginId).client, pluginId); - } - - /** - * Provides a partial `Knex.Config`• database name override for a given plugin. - * - * @param pluginId - Target plugin to get database name override - * @returns Partial `Knex.Config` with database name override - */ - private getDatabaseOverrides(pluginId: string): Knex.Config { - const databaseName = this.getDatabaseName(pluginId); - return databaseName - ? createNameOverride(this.getClientType(pluginId).client, databaseName) - : {}; - } - /** * Provides a scoped Knex client for a plugin as per application config. * @@ -352,6 +160,7 @@ export class DatabaseManager implements LegacyRootDatabaseService { */ private async getDatabase( pluginId: string, + connector: Connector, deps?: { lifecycle: LifecycleService; pluginMetadata: PluginMetadataService; @@ -361,55 +170,13 @@ export class DatabaseManager implements LegacyRootDatabaseService { return this.databaseCache.get(pluginId)!; } - const clientPromise = Promise.resolve().then(async () => { - const pluginConfig = new ConfigReader( - this.getConfigForPlugin(pluginId) as JsonObject, - ); - - const databaseName = this.getDatabaseName(pluginId); - if (databaseName && this.getEnsureExistsConfig(pluginId)) { - try { - await ensureDatabaseExists(pluginConfig, databaseName); - } catch (error) { - throw new Error( - `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, - ); - } - } - - let schemaOverrides; - if (this.getPluginDivisionModeConfig() === 'schema') { - schemaOverrides = this.getSchemaOverrides(pluginId); - if (this.getEnsureExistsConfig(pluginId)) { - try { - await ensureSchemaExists(pluginConfig, pluginId); - } catch (error) { - throw new Error( - `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`, - ); - } - } - } - - const databaseClientOverrides = mergeDatabaseConfig( - {}, - this.getDatabaseOverrides(pluginId), - schemaOverrides, - ); - - const client = createDatabaseClient( - pluginConfig, - databaseClientOverrides, - deps, - ); - if (process.env.NODE_ENV !== 'test') { - this.startKeepaliveLoop(pluginId, client); - } - return client; - }); - + const clientPromise = connector.getClient(pluginId, deps).then(); this.databaseCache.set(pluginId, clientPromise); + if (process.env.NODE_ENV !== 'test') { + clientPromise.then(client => this.startKeepaliveLoop(pluginId, client)); + } + return clientPromise; } diff --git a/packages/backend-common/src/database/connectors/defaultNameOverride.test.ts b/packages/backend-common/src/database/connectors/defaultNameOverride.test.ts index 1da8e6c11e..59f7b5b137 100644 --- a/packages/backend-common/src/database/connectors/defaultNameOverride.test.ts +++ b/packages/backend-common/src/database/connectors/defaultNameOverride.test.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import defaultNameOverride from './defaultNameOverride'; describe('defaultNameOverride()', () => { diff --git a/packages/backend-common/src/database/connectors/defaultNameOverride.ts b/packages/backend-common/src/database/connectors/defaultNameOverride.ts index bc467eb7f6..3f610c51c6 100644 --- a/packages/backend-common/src/database/connectors/defaultNameOverride.ts +++ b/packages/backend-common/src/database/connectors/defaultNameOverride.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Knex } from 'knex'; /** diff --git a/packages/backend-common/src/database/connectors/defaultSchemaOverride.test.ts b/packages/backend-common/src/database/connectors/defaultSchemaOverride.test.ts index 2db5ecd454..505b9051f7 100644 --- a/packages/backend-common/src/database/connectors/defaultSchemaOverride.test.ts +++ b/packages/backend-common/src/database/connectors/defaultSchemaOverride.test.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import defaultSchemaOverride from './defaultSchemaOverride'; describe('defaultNameOverride()', () => { diff --git a/packages/backend-common/src/database/connectors/defaultSchemaOverride.ts b/packages/backend-common/src/database/connectors/defaultSchemaOverride.ts index 4e76308542..85c6f1ec65 100644 --- a/packages/backend-common/src/database/connectors/defaultSchemaOverride.ts +++ b/packages/backend-common/src/database/connectors/defaultSchemaOverride.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Knex } from 'knex'; /** diff --git a/packages/backend-common/src/database/connectors/index.ts b/packages/backend-common/src/database/connectors/index.ts index df84ec66ba..9d18e7460a 100644 --- a/packages/backend-common/src/database/connectors/index.ts +++ b/packages/backend-common/src/database/connectors/index.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export * from './mysql'; export * from './postgres'; export * from './sqlite3'; diff --git a/packages/backend-common/src/database/config.test.ts b/packages/backend-common/src/database/connectors/mergeDatabaseConfig.test.ts similarity index 97% rename from packages/backend-common/src/database/config.test.ts rename to packages/backend-common/src/database/connectors/mergeDatabaseConfig.test.ts index a5bf4a79d2..84177a1208 100644 --- a/packages/backend-common/src/database/config.test.ts +++ b/packages/backend-common/src/database/connectors/mergeDatabaseConfig.test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { mergeDatabaseConfig } from './config'; +import { mergeDatabaseConfig } from './mergeDatabaseConfig'; -describe('config', () => { +describe('mergeDatabaseConfig', () => { describe('mergeDatabaseConfig', () => { it('does not mutate the input object', () => { const input = { diff --git a/packages/backend-common/src/database/config.ts b/packages/backend-common/src/database/connectors/mergeDatabaseConfig.ts similarity index 100% rename from packages/backend-common/src/database/config.ts rename to packages/backend-common/src/database/connectors/mergeDatabaseConfig.ts diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-common/src/database/connectors/mysql.ts index 63464fa6f4..a7cbad271f 100644 --- a/packages/backend-common/src/database/connectors/mysql.ts +++ b/packages/backend-common/src/database/connectors/mysql.ts @@ -25,9 +25,9 @@ import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; import path from 'path'; import yn from 'yn'; -import { mergeDatabaseConfig } from '../config'; -import { DatabaseConnector } from '../types'; +import { Connector, DatabaseConnector } from '../types'; import defaultNameOverride from './defaultNameOverride'; +import { mergeDatabaseConfig } from './mergeDatabaseConfig'; /** * Creates a knex mysql database connection @@ -295,7 +295,7 @@ function createNameOverride( } } -export class MysqlConnector { +export class MysqlConnector implements Connector { constructor( private readonly config: Config, private readonly prefix: string = 'backstage_plugin_', diff --git a/packages/backend-common/src/database/connectors/postgres.test.ts b/packages/backend-common/src/database/connectors/postgres.test.ts index 39c9af5ef3..1e07984967 100644 --- a/packages/backend-common/src/database/connectors/postgres.test.ts +++ b/packages/backend-common/src/database/connectors/postgres.test.ts @@ -16,8 +16,8 @@ import { Config, ConfigReader } from '@backstage/config'; import { - createPgDatabaseClient, buildPgDatabaseConfig, + createPgDatabaseClient, getPgConnectionConfig, parsePgConnectionString, } from './postgres'; diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index 0a3aa6cb74..4e3ed9aa1d 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -25,10 +25,10 @@ import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; import path from 'path'; import { Client } from 'pg'; -import { mergeDatabaseConfig } from '../config'; -import { DatabaseConnector } from '../types'; +import { Connector, DatabaseConnector } from '../types'; import defaultNameOverride from './defaultNameOverride'; import defaultSchemaOverride from './defaultSchemaOverride'; +import { mergeDatabaseConfig } from './mergeDatabaseConfig'; /** * Creates a knex postgres database connection @@ -292,7 +292,7 @@ function createNameOverride( } } -export class PgConnector { +export class PgConnector implements Connector { constructor( private readonly config: Config, private readonly prefix: string = 'backstage_plugin_', diff --git a/packages/backend-common/src/database/connectors/sqlite3.ts b/packages/backend-common/src/database/connectors/sqlite3.ts index 5714b036e3..514af6d69f 100644 --- a/packages/backend-common/src/database/connectors/sqlite3.ts +++ b/packages/backend-common/src/database/connectors/sqlite3.ts @@ -26,8 +26,8 @@ import { ensureDirSync } from 'fs-extra'; import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; import path from 'path'; -import { mergeDatabaseConfig } from '../config'; -import { DatabaseConnector } from '../types'; +import { Connector, DatabaseConnector } from '../types'; +import { mergeDatabaseConfig } from './mergeDatabaseConfig'; /** * Creates a knex SQLite3 database connection @@ -227,7 +227,7 @@ function createNameOverride( } } -export class Sqlite3Connector implements DatabaseConnector { +export class Sqlite3Connector implements Connector { constructor( private readonly config: Config, private readonly prefix: string = 'backstage_plugin_', diff --git a/packages/backend-common/src/database/index.ts b/packages/backend-common/src/database/index.ts index 330429f0ef..f8bdc22f6d 100644 --- a/packages/backend-common/src/database/index.ts +++ b/packages/backend-common/src/database/index.ts @@ -22,8 +22,8 @@ export * from './DatabaseManager'; */ export { createDatabaseClient, - ensureDatabaseExists, dropDatabase, + ensureDatabaseExists, } from './connection'; export type { PluginDatabaseManager } from './types'; diff --git a/packages/backend-common/src/database/types.ts b/packages/backend-common/src/database/types.ts index c73540a9c1..967de6966e 100644 --- a/packages/backend-common/src/database/types.ts +++ b/packages/backend-common/src/database/types.ts @@ -86,3 +86,13 @@ export interface DatabaseConnector { */ dropDatabase?(dbConfig: Config, ...databases: Array<string>): Promise<void>; } + +export interface Connector { + getClient( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): Promise<Knex>; +} From 5aad3c0d594a2c859ff8d024bee58d445328fee2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Sun, 28 Apr 2024 12:05:14 +0200 Subject: [PATCH 147/234] strip out client-type checks in each connector MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- .../src/database/DatabaseManager.ts | 2 +- .../src/database/connectors/mysql.ts | 39 ++---------------- .../src/database/connectors/postgres.ts | 41 +++---------------- .../src/database/connectors/sqlite3.ts | 6 --- packages/backend-common/src/database/index.ts | 10 ----- 5 files changed, 10 insertions(+), 88 deletions(-) diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts index a261cd13fc..66fef056bc 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -170,7 +170,7 @@ export class DatabaseManager implements LegacyRootDatabaseService { return this.databaseCache.get(pluginId)!; } - const clientPromise = connector.getClient(pluginId, deps).then(); + const clientPromise = connector.getClient(pluginId, deps); this.databaseCache.set(pluginId, clientPromise); if (process.env.NODE_ENV !== 'test') { diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-common/src/database/connectors/mysql.ts index a7cbad271f..418466e3c6 100644 --- a/packages/backend-common/src/database/connectors/mysql.ts +++ b/packages/backend-common/src/database/connectors/mysql.ts @@ -367,21 +367,6 @@ export class MysqlConnector implements Connector { private getDatabaseName(pluginId: string): string | undefined { const connection = this.getConnectionConfig(pluginId); - if (this.getClientType(pluginId).client.includes('sqlite3')) { - const sqliteFilename: string | undefined = ( - connection as Knex.Sqlite3ConnectionConfig - ).filename; - - if (sqliteFilename === ':memory:') { - return sqliteFilename; - } - - const sqliteDirectory = - (connection as { directory?: string }).directory ?? '.'; - - return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`); - } - const databaseName = (connection as Knex.ConnectionConfig)?.database; // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database @@ -464,9 +449,8 @@ export class MysqlConnector implements Connector { * This method provides a baseConfig for a plugin database connector. If the * client type has not been overridden, the global connection config will be * included with plugin specific config as the base. Values from the plugin - * connection take precedence over the base. Base database name is omitted for - * all supported databases excluding SQLite unless `pluginDivisionMode` is set - * to `schema`. + * connection take precedence over the base. Base database name is omitted + * unless `pluginDivisionMode` is set to `schema`. */ private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig { const { client, overridden } = this.getClientType(pluginId); @@ -476,20 +460,9 @@ export class MysqlConnector implements Connector { this.config.getString('client'), ); - if ( - client.includes('sqlite3') && - 'filename' in baseConnection && - baseConnection.filename !== ':memory:' - ) { - throw new Error( - '`connection.filename` is not supported for the base sqlite connection. Prefer `connection.directory` or provide a filename for the plugin connection instead.', - ); - } - // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The // `database` property from the base connection is omitted unless `pluginDivisionMode` - // is set to `schema`. SQLite3's `filename` property is an exception as this is used as a - // directory elsewhere so we preserve `filename`. + // is set to `schema`. if (this.getPluginDivisionModeConfig() !== 'schema') { baseConnection = omit(baseConnection, 'database'); } @@ -500,12 +473,6 @@ export class MysqlConnector implements Connector { client, ); - if (client === 'pg') { - ( - baseConnection as Knex.PgConnectionConfig - ).application_name ||= `backstage_plugin_${pluginId}`; - } - return { // include base connection if client type has not been overridden ...(overridden ? {} : baseConnection), diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index 4e3ed9aa1d..5cc81591ad 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -364,21 +364,6 @@ export class PgConnector implements Connector { private getDatabaseName(pluginId: string): string | undefined { const connection = this.getConnectionConfig(pluginId); - if (this.getClientType(pluginId).client.includes('sqlite3')) { - const sqliteFilename: string | undefined = ( - connection as Knex.Sqlite3ConnectionConfig - ).filename; - - if (sqliteFilename === ':memory:') { - return sqliteFilename; - } - - const sqliteDirectory = - (connection as { directory?: string }).directory ?? '.'; - - return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`); - } - const databaseName = (connection as Knex.ConnectionConfig)?.database; // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database @@ -461,9 +446,8 @@ export class PgConnector implements Connector { * This method provides a baseConfig for a plugin database connector. If the * client type has not been overridden, the global connection config will be * included with plugin specific config as the base. Values from the plugin - * connection take precedence over the base. Base database name is omitted for - * all supported databases excluding SQLite unless `pluginDivisionMode` is set - * to `schema`. + * connection take precedence over the base. Base database name is omitted + * unless `pluginDivisionMode` is set to `schema`. */ private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig { const { client, overridden } = this.getClientType(pluginId); @@ -473,20 +457,9 @@ export class PgConnector implements Connector { this.config.getString('client'), ); - if ( - client.includes('sqlite3') && - 'filename' in baseConnection && - baseConnection.filename !== ':memory:' - ) { - throw new Error( - '`connection.filename` is not supported for the base sqlite connection. Prefer `connection.directory` or provide a filename for the plugin connection instead.', - ); - } - // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The // `database` property from the base connection is omitted unless `pluginDivisionMode` - // is set to `schema`. SQLite3's `filename` property is an exception as this is used as a - // directory elsewhere so we preserve `filename`. + // is set to `schema`. if (this.getPluginDivisionModeConfig() !== 'schema') { baseConnection = omit(baseConnection, 'database'); } @@ -497,11 +470,9 @@ export class PgConnector implements Connector { client, ); - if (client === 'pg') { - ( - baseConnection as Knex.PgConnectionConfig - ).application_name ||= `backstage_plugin_${pluginId}`; - } + ( + baseConnection as Knex.PgConnectionConfig + ).application_name ||= `backstage_plugin_${pluginId}`; return { // include base connection if client type has not been overridden diff --git a/packages/backend-common/src/database/connectors/sqlite3.ts b/packages/backend-common/src/database/connectors/sqlite3.ts index 514af6d69f..a9c2b93d1c 100644 --- a/packages/backend-common/src/database/connectors/sqlite3.ts +++ b/packages/backend-common/src/database/connectors/sqlite3.ts @@ -432,12 +432,6 @@ export class Sqlite3Connector implements Connector { client, ); - if (client === 'pg') { - ( - baseConnection as Knex.PgConnectionConfig - ).application_name ||= `backstage_plugin_${pluginId}`; - } - return { // include base connection if client type has not been overridden ...(overridden ? {} : baseConnection), diff --git a/packages/backend-common/src/database/index.ts b/packages/backend-common/src/database/index.ts index f8bdc22f6d..464cf4a4de 100644 --- a/packages/backend-common/src/database/index.ts +++ b/packages/backend-common/src/database/index.ts @@ -16,15 +16,5 @@ export * from './DatabaseManager'; -/* - * Undocumented API surface from connection is being reduced for future deprecation. - * Avoid exporting additional symbols. - */ -export { - createDatabaseClient, - dropDatabase, - ensureDatabaseExists, -} from './connection'; - export type { PluginDatabaseManager } from './types'; export { isDatabaseConflictError } from './util'; From 419d4cac7ccca602108b29ef119a3d667f5c0363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Sun, 28 Apr 2024 22:17:12 +0200 Subject: [PATCH 148/234] clean up and expose dropDatabase again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- packages/backend-common/api-report.md | 20 +- .../src/database/DatabaseManager.test.ts | 106 ++++++--- .../src/database/DatabaseManager.ts | 121 +++++++--- .../backend-common/src/database/connection.ts | 218 ------------------ .../src/database/connectors/mysql.ts | 80 ++----- .../src/database/connectors/postgres.ts | 36 ++- .../src/database/connectors/sqlite3.ts | 110 ++------- packages/backend-common/src/database/index.ts | 6 +- packages/backend-common/src/database/types.ts | 2 + 9 files changed, 218 insertions(+), 481 deletions(-) delete mode 100644 packages/backend-common/src/database/connection.ts diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index f9e0d76aab..9e64b84777 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -34,8 +34,6 @@ import { HostDiscovery as HostDiscovery_2 } from '@backstage/backend-app-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { IdentityService } from '@backstage/backend-plugin-api'; import { isChildPath } from '@backstage/cli-common'; -import { Knex } from 'knex'; -import knexFactory from 'knex'; import { KubeConfig } from '@kubernetes/client-node'; import { LifecycleService } from '@backstage/backend-plugin-api'; import { LoadConfigOptionsRemote } from '@backstage/config-loader'; @@ -225,16 +223,6 @@ export interface ContainerRunner { runContainer(opts: RunContainerOptions): Promise<void>; } -// @public -export function createDatabaseClient( - dbConfig: Config, - overrides?: Partial<Knex.Config>, - deps?: { - lifecycle: LifecycleService; - pluginMetadata: PluginMetadataService; - }, -): knexFactory.Knex<any, any[]>; - // @public export function createLegacyAuthAdapters< TOptions extends { @@ -315,13 +303,7 @@ export class DockerContainerRunner implements ContainerRunner { // @public export function dropDatabase( dbConfig: Config, - ...databases: Array<string> -): Promise<void>; - -// @public -export function ensureDatabaseExists( - dbConfig: Config, - ...databases: Array<string> + ...databaseNames: string[] ): Promise<void>; // @public diff --git a/packages/backend-common/src/database/DatabaseManager.test.ts b/packages/backend-common/src/database/DatabaseManager.test.ts index 6a461fad55..ba911b68b9 100644 --- a/packages/backend-common/src/database/DatabaseManager.test.ts +++ b/packages/backend-common/src/database/DatabaseManager.test.ts @@ -13,44 +13,91 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { ConfigReader } from '@backstage/config'; -import { omit } from 'lodash'; -import path from 'path'; -import { DatabaseManager } from './DatabaseManager'; -import { - createDatabaseClient, - ensureDatabaseExists, - ensureSchemaExists, -} from './connection'; +import { DatabaseManagerImpl } from './DatabaseManager'; +import { Connector } from './types'; -jest.mock('./connection', () => ({ - ...jest.requireActual('./connection'), - createDatabaseClient: jest.fn(), - ensureDatabaseExists: jest.fn(), - ensureSchemaExists: jest.fn(), -})); +describe('DatabaseManagerImpl', () => { + afterEach(() => { + jest.clearAllMocks(); + }); -describe('DatabaseManager', () => { - // This is similar to the ts-jest `mocked` helper. - const mocked = (f: Function) => f as jest.Mock; + it('calls the right connector, only once per plugin id', async () => { + const connector1 = { + getClient: jest.fn(), + dropDatabase: jest.fn(), + } satisfies Connector; + const connector2 = { + getClient: jest.fn(), + dropDatabase: jest.fn(), + } satisfies Connector; - afterEach(() => jest.resetAllMocks()); + const impl = new DatabaseManagerImpl( + new ConfigReader({ + client: 'pg', + }), + { + pg: connector1, + notpg: connector2, + }, + ); - describe('DatabaseManager.fromConfig', () => { - const backendConfig = { - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', + await impl.forPlugin('plugin1').getClient(); + expect(connector1.getClient).toHaveBeenCalledTimes(1); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector2.getClient).toHaveBeenCalledTimes(0); + + await impl.forPlugin('plugin1').getClient(); + expect(connector1.getClient).toHaveBeenCalledTimes(1); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector2.getClient).toHaveBeenCalledTimes(0); + + await impl.forPlugin('plugin2').getClient(); + expect(connector1.getClient).toHaveBeenCalledTimes(2); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin2', undefined); + expect(connector2.getClient).toHaveBeenCalledTimes(0); + }); + + it('respects per-plugin overridden connectors', async () => { + const connector1 = { + getClient: jest.fn(), + dropDatabase: jest.fn(), + } satisfies Connector; + const connector2 = { + getClient: jest.fn(), + dropDatabase: jest.fn(), + } satisfies Connector; + + const impl = new DatabaseManagerImpl( + new ConfigReader({ + client: 'pg', + plugin: { + plugin2: { + client: 'mysql', }, }, + }), + { + pg: connector1, + mysql: connector2, }, - }; + ); + await impl.forPlugin('plugin1').getClient(); + expect(connector1.getClient).toHaveBeenCalledTimes(1); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector2.getClient).toHaveBeenCalledTimes(0); + + await impl.forPlugin('plugin2').getClient(); + expect(connector1.getClient).toHaveBeenCalledTimes(1); + expect(connector1.getClient).toHaveBeenLastCalledWith('plugin1', undefined); + expect(connector2.getClient).toHaveBeenCalledTimes(1); + expect(connector2.getClient).toHaveBeenLastCalledWith('plugin2', undefined); + }); + + // eslint-disable-next-line jest/no-commented-out-tests + /* it('accesses the backend.database key', () => { const config = new ConfigReader(backendConfig); const getConfigSpy = jest.spyOn(config, 'getConfig'); @@ -848,4 +895,5 @@ describe('DatabaseManager', () => { ); }); }); + */ }); diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts index 66fef056bc..dd36cc3b4a 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -54,44 +54,10 @@ export type LegacyRootDatabaseService = { }; /** - * Manages database connections for Backstage backend plugins. - * - * @public - * @remarks - * - * The database manager allows the user to set connection and client settings on - * a per pluginId basis by defining a database config block under - * `plugin.<pluginId>` in addition to top level defaults. Optionally, a user may - * set `prefix` which is used to prefix generated database names if config is - * not provided. + * Testable implementation class for {@link DatabaseManager} below. */ -export class DatabaseManager implements LegacyRootDatabaseService { - /** - * Creates a {@link DatabaseManager} from `backend.database` config. - * - * @param config - The loaded application configuration. - * @param options - An optional configuration object. - */ - static fromConfig( - config: Config, - options?: DatabaseManagerOptions, - ): DatabaseManager { - const databaseConfig = config.getConfig('backend.database'); - const prefix = databaseConfig.getOptionalString('prefix'); - return new DatabaseManager( - databaseConfig, - { - pg: new PgConnector(config, prefix), - sqlite3: new Sqlite3Connector(config, prefix), - 'better-sqlite3': new Sqlite3Connector(config, prefix), - mysql: new MysqlConnector(config, prefix), - mysql2: new MysqlConnector(config, prefix), - }, - options, - ); - } - - private constructor( +export class DatabaseManagerImpl implements LegacyRootDatabaseService { + constructor( private readonly config: Config, private readonly connectors: Record<string, Connector>, private readonly options?: DatabaseManagerOptions, @@ -204,3 +170,84 @@ export class DatabaseManager implements LegacyRootDatabaseService { }, 60 * 1000); } } + +// NOTE: This class looks odd but is kept around for API compatibility reasons +/** + * Manages database connections for Backstage backend plugins. + * + * @public + * @remarks + * + * The database manager allows the user to set connection and client settings on + * a per pluginId basis by defining a database config block under + * `plugin.<pluginId>` in addition to top level defaults. Optionally, a user may + * set `prefix` which is used to prefix generated database names if config is + * not provided. + */ +export class DatabaseManager implements LegacyRootDatabaseService { + /** + * Creates a {@link DatabaseManager} from `backend.database` config. + * + * @param config - The loaded application configuration. + * @param options - An optional configuration object. + */ + static fromConfig( + config: Config, + options?: DatabaseManagerOptions, + ): DatabaseManager { + const databaseConfig = config.getConfig('backend.database'); + const prefix = + databaseConfig.getOptionalString('prefix') || 'backstage_plugin_'; + return new DatabaseManager( + new DatabaseManagerImpl( + databaseConfig, + { + pg: new PgConnector(databaseConfig, prefix), + sqlite3: new Sqlite3Connector(databaseConfig), + 'better-sqlite3': new Sqlite3Connector(databaseConfig), + mysql: new MysqlConnector(databaseConfig, prefix), + mysql2: new MysqlConnector(databaseConfig, prefix), + }, + options, + ), + ); + } + + private constructor(private readonly impl: DatabaseManagerImpl) {} + + /** + * Generates a PluginDatabaseManager for consumption by plugins. + * + * @param pluginId - The plugin that the database manager should be created for. Plugin names + * should be unique as they are used to look up database config overrides under + * `backend.database.plugin`. + */ + forPlugin( + pluginId: string, + deps?: { + lifecycle: LifecycleService; + pluginMetadata: PluginMetadataService; + }, + ): PluginDatabaseManager { + return this.impl.forPlugin(pluginId, deps); + } +} + +/** + * Helper for deleting databases, only exists for backend-test-utils for now. + * + * @public + */ +export async function dropDatabase( + dbConfig: Config, + ...databaseNames: string[] +): Promise<void> { + const client = dbConfig.getString('client'); + const prefix = dbConfig.getOptionalString('prefix') || 'backstage_plugin_'; + + if (client === 'pg') { + await new PgConnector(dbConfig, prefix).dropDatabase(...databaseNames); + } else if (client === 'mysql' || client === 'mysql2') { + await new MysqlConnector(dbConfig, prefix).dropDatabase(...databaseNames); + } +} diff --git a/packages/backend-common/src/database/connection.ts b/packages/backend-common/src/database/connection.ts deleted file mode 100644 index 875734aa19..0000000000 --- a/packages/backend-common/src/database/connection.ts +++ /dev/null @@ -1,218 +0,0 @@ -/* - * 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. - */ - -// * Copyright 2020 The 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 { Config } from '@backstage/config'; -// import { JsonObject } from '@backstage/types'; -// import { InputError } from '@backstage/errors'; -// import knexFactory, { Knex } from 'knex'; -// import limiterFactory from 'p-limit'; -// import { mergeDatabaseConfig } from './config'; -// import { DatabaseConnector } from './types'; - -// import { mysqlConnector, pgConnector, sqlite3Connector } from './connectors'; -// import { -// LifecycleService, -// PluginMetadataService, -// } from '@backstage/backend-plugin-api'; - -// type DatabaseClient = -// | 'pg' -// | 'better-sqlite3' -// | 'sqlite3' -// | 'mysql' -// | 'mysql2' -// | string; - -// // This limits the number of concurrent CREATE DATABASE and CREATE SCHEMA -// // commands, globally, to just one. This is overly defensive, and was added as -// // an attempt to counteract the pool issues on recent node versions. See -// // https://github.com/backstage/backstage/pull/19988 -// const ddlLimiter = limiterFactory(1); - -// /** -// * Mapping of client type to supported database connectors -// * -// * Database connectors can be aliased here, for example mysql2 uses -// * the same connector as mysql. -// */ -// const ConnectorMapping: Record<DatabaseClient, DatabaseConnector> = { -// pg: pgConnector, -// 'better-sqlite3': sqlite3Connector, -// sqlite3: sqlite3Connector, -// mysql: mysqlConnector, -// mysql2: mysqlConnector, -// }; - -// /** -// * Creates a knex database connection -// * -// * @public -// * @param dbConfig - The database config -// * @param overrides - Additional options to merge with the config -// */ -// export function createDatabaseClient( -// dbConfig: Config, -// overrides?: Partial<Knex.Config>, -// deps?: { -// lifecycle: LifecycleService; -// pluginMetadata: PluginMetadataService; -// }, -// ) { -// const client: DatabaseClient = dbConfig.getString('client'); - -// return ( -// ConnectorMapping[client]?.createClient(dbConfig, overrides, deps) ?? -// knexFactory(mergeDatabaseConfig(dbConfig.get(), overrides)) -// ); -// } - -// /** -// * Ensures that the given databases all exist, creating them if they do not. -// * -// * @public -// */ -// export async function ensureDatabaseExists( -// dbConfig: Config, -// ...databases: Array<string> -// ): Promise<void> { -// const client: DatabaseClient = dbConfig.getString('client'); - -// return await ddlLimiter(() => -// ConnectorMapping[client]?.ensureDatabaseExists?.(dbConfig, ...databases), -// ); -// } - -// /** -// * Drops the given databases. -// * -// * @public -// */ -// export async function dropDatabase( -// dbConfig: Config, -// ...databases: Array<string> -// ): Promise<void> { -// const client: DatabaseClient = dbConfig.getString('client'); - -// return await ddlLimiter(() => -// ConnectorMapping[client]?.dropDatabase?.(dbConfig, ...databases), -// ); -// } - -// /** -// * Ensures that the given schemas all exist, creating them if they do not. -// * -// * @public -// */ -// export async function ensureSchemaExists( -// dbConfig: Config, -// ...schemas: Array<string> -// ): Promise<void> { -// const client: DatabaseClient = dbConfig.getString('client'); - -// return await ddlLimiter(() => -// ConnectorMapping[client]?.ensureSchemaExists?.(dbConfig, ...schemas), -// ); -// } - -// /** -// * Provides a `Knex.Config` object with the provided database name for a given -// * client. -// */ -// export function createNameOverride( -// client: string, -// name: string, -// ): Partial<Knex.Config> { -// try { -// return ConnectorMapping[client].createNameOverride(name); -// } catch (e) { -// throw new InputError( -// `Unable to create database name override for '${client}' connector`, -// e, -// ); -// } -// } - -// /** -// * Provides a `Knex.Config` object with the provided database schema for a given -// * client. Currently only supported by `pg`. -// */ -// export function createSchemaOverride( -// client: string, -// name: string, -// ): Partial<Knex.Config | undefined> { -// try { -// return ConnectorMapping[client]?.createSchemaOverride?.(name); -// } catch (e) { -// throw new InputError( -// `Unable to create database schema override for '${client}' connector`, -// e, -// ); -// } -// } - -// /** -// * Parses a connection string for a given client and provides a connection config. -// */ -// export function parseConnectionString( -// connectionString: string, -// client?: string, -// ): Knex.StaticConnectionConfig { -// if (typeof client === 'undefined' || client === null) { -// throw new InputError( -// 'Database connection string client type auto-detection is not yet supported.', -// ); -// } - -// try { -// return ConnectorMapping[client].parseConnectionString(connectionString); -// } catch (e) { -// throw new InputError( -// `Unable to parse connection string for '${client}' connector`, -// ); -// } -// } - -// /** -// * Normalizes a connection config or string into an object which can be passed -// * to Knex. -// */ -// export function normalizeConnection( -// connection: Knex.StaticConnectionConfig | JsonObject | string | undefined, -// client: string, -// ): Partial<Knex.StaticConnectionConfig> { -// if (typeof connection === 'undefined' || connection === null) { -// return {}; -// } - -// return typeof connection === 'string' || connection instanceof String -// ? parseConnectionString(connection as string, client) -// : connection; -// } diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-common/src/database/connectors/mysql.ts index 418466e3c6..25594d5021 100644 --- a/packages/backend-common/src/database/connectors/mysql.ts +++ b/packages/backend-common/src/database/connectors/mysql.ts @@ -23,7 +23,6 @@ import { InputError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; -import path from 'path'; import yn from 'yn'; import { Connector, DatabaseConnector } from '../types'; import defaultNameOverride from './defaultNameOverride'; @@ -236,17 +235,6 @@ export const mysqlConnector: DatabaseConnector = Object.freeze({ dropDatabase: dropMysqlDatabase, }); -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /** * Provides a config lookup path for a plugin's config block. */ @@ -267,20 +255,6 @@ function normalizeConnection( : connection; } -function createSchemaOverride( - client: string, - name: string, -): Partial<Knex.Config | undefined> { - try { - return mysqlConnector.createSchemaOverride?.(name); - } catch (e) { - throw new InputError( - `Unable to create database schema override for '${client}' connector`, - e, - ); - } -} - function createNameOverride( client: string, name: string, @@ -298,7 +272,7 @@ function createNameOverride( export class MysqlConnector implements Connector { constructor( private readonly config: Config, - private readonly prefix: string = 'backstage_plugin_', + private readonly prefix: string, ) {} async getClient( @@ -315,7 +289,7 @@ export class MysqlConnector implements Connector { const databaseName = this.getDatabaseName(pluginId); if (databaseName && this.getEnsureExistsConfig(pluginId)) { try { - await mysqlConnector.ensureDatabaseExists(pluginConfig, databaseName); + await mysqlConnector.ensureDatabaseExists!(pluginConfig, databaseName); } catch (error) { throw new Error( `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, @@ -323,24 +297,16 @@ export class MysqlConnector implements Connector { } } - let schemaOverrides; - if (this.getPluginDivisionModeConfig() === 'schema') { - schemaOverrides = this.getSchemaOverrides(pluginId); - if (this.getEnsureExistsConfig(pluginId)) { - try { - await mysqlConnector.ensureSchemaExists(pluginConfig, pluginId); - } catch (error) { - throw new Error( - `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`, - ); - } - } + const pluginDivisionMode = this.getPluginDivisionModeConfig(); + if (pluginDivisionMode !== 'database') { + throw new Error( + `The MySQL driver does not suppoert plugin division mode '${pluginDivisionMode}'`, + ); } const databaseClientOverrides = mergeDatabaseConfig( {}, this.getDatabaseOverrides(pluginId), - schemaOverrides, ); const client = mysqlConnector.createClient( @@ -352,29 +318,24 @@ export class MysqlConnector implements Connector { return client; } + async dropDatabase(...databaseNames: string[]): Promise<void> { + return await dropMysqlDatabase(this.config, ...databaseNames); + } + /** * Provides the canonical database name for a given plugin. * - * This method provides the effective database name which is determined using global - * and plugin specific database config. If no explicit database name is configured - * and `pluginDivisionMode` is not `schema`, this method will provide a generated name - * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is - * `schema`, it will fallback to using the default database for the knex instance. + * This method provides the effective database name which is determined using + * global and plugin specific database config. If no explicit database name, + * this method will provide a generated name which is the pluginId prefixed + * with 'backstage_plugin_'. * * @param pluginId - Lookup the database name for given plugin * @returns String representing the plugin's database name */ private getDatabaseName(pluginId: string): string | undefined { const connection = this.getConnectionConfig(pluginId); - const databaseName = (connection as Knex.ConnectionConfig)?.database; - - // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database - if (this.getPluginDivisionModeConfig() === 'schema') { - return databaseName; - } - - // all other supported databases should fallback to an auto-prefixed name return databaseName ?? `${this.prefix}${pluginId}`; } @@ -500,17 +461,6 @@ export class MysqlConnector implements Connector { }; } - /** - * Provides a partial `Knex.Config` database schema override for a given - * plugin. - * - * @param pluginId - Target plugin to get database schema override - * @returns Partial `Knex.Config` with database schema override - */ - private getSchemaOverrides(pluginId: string): Knex.Config | undefined { - return createSchemaOverride(this.getClientType(pluginId).client, pluginId); - } - /** * Provides a partial `Knex.Config`• database name override for a given plugin. * diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index 5cc81591ad..39a1073a30 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -23,7 +23,6 @@ import { ForwardedError, InputError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; -import path from 'path'; import { Client } from 'pg'; import { Connector, DatabaseConnector } from '../types'; import defaultNameOverride from './defaultNameOverride'; @@ -216,11 +215,15 @@ export async function dropPgDatabase( ...databases: Array<string> ) { const admin = createPgDatabaseClient(dbConfig); - await Promise.all( - databases.map(async database => { - await admin.raw(`DROP DATABASE ??`, [database]); - }), - ); + try { + await Promise.all( + databases.map(async database => { + await admin.raw(`DROP DATABASE ??`, [database]); + }), + ); + } finally { + await admin.destroy(); + } } export const pgConnector: DatabaseConnector = Object.freeze({ @@ -233,17 +236,6 @@ export const pgConnector: DatabaseConnector = Object.freeze({ dropDatabase: dropPgDatabase, }); -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /** * Provides a config lookup path for a plugin's config block. */ @@ -295,7 +287,7 @@ function createNameOverride( export class PgConnector implements Connector { constructor( private readonly config: Config, - private readonly prefix: string = 'backstage_plugin_', + private readonly prefix: string, ) {} async getClient( @@ -312,7 +304,7 @@ export class PgConnector implements Connector { const databaseName = this.getDatabaseName(pluginId); if (databaseName && this.getEnsureExistsConfig(pluginId)) { try { - await pgConnector.ensureDatabaseExists(pluginConfig, databaseName); + await pgConnector.ensureDatabaseExists!(pluginConfig, databaseName); } catch (error) { throw new Error( `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, @@ -325,7 +317,7 @@ export class PgConnector implements Connector { schemaOverrides = this.getSchemaOverrides(pluginId); if (this.getEnsureExistsConfig(pluginId)) { try { - await pgConnector.ensureSchemaExists(pluginConfig, pluginId); + await pgConnector.ensureSchemaExists!(pluginConfig, pluginId); } catch (error) { throw new Error( `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`, @@ -349,6 +341,10 @@ export class PgConnector implements Connector { return client; } + async dropDatabase(...databaseNames: string[]): Promise<void> { + return await dropPgDatabase(this.config, ...databaseNames); + } + /** * Provides the canonical database name for a given plugin. * diff --git a/packages/backend-common/src/database/connectors/sqlite3.ts b/packages/backend-common/src/database/connectors/sqlite3.ts index a9c2b93d1c..46ff756854 100644 --- a/packages/backend-common/src/database/connectors/sqlite3.ts +++ b/packages/backend-common/src/database/connectors/sqlite3.ts @@ -168,17 +168,6 @@ export const sqliteConnector: DatabaseConnector = Object.freeze({ parseConnectionString: parseSqliteConnectionString, }); -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /** * Provides a config lookup path for a plugin's config block. */ @@ -199,20 +188,6 @@ function normalizeConnection( : connection; } -function createSchemaOverride( - client: string, - name: string, -): Partial<Knex.Config | undefined> { - try { - return sqliteConnector.createSchemaOverride?.(name); - } catch (e) { - throw new InputError( - `Unable to create database schema override for '${client}' connector`, - e, - ); - } -} - function createNameOverride( client: string, name: string, @@ -228,10 +203,7 @@ function createNameOverride( } export class Sqlite3Connector implements Connector { - constructor( - private readonly config: Config, - private readonly prefix: string = 'backstage_plugin_', - ) {} + constructor(private readonly config: Config) {} async getClient( pluginId: string, @@ -244,35 +216,16 @@ export class Sqlite3Connector implements Connector { this.getConfigForPlugin(pluginId) as JsonObject, ); - const databaseName = this.getDatabaseName(pluginId); - if (databaseName && this.getEnsureExistsConfig(pluginId)) { - try { - await sqliteConnector.ensureDatabaseExists(pluginConfig, databaseName); - } catch (error) { - throw new Error( - `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, - ); - } - } - - let schemaOverrides; - if (this.getPluginDivisionModeConfig() === 'schema') { - schemaOverrides = this.getSchemaOverrides(pluginId); - if (this.getEnsureExistsConfig(pluginId)) { - try { - await sqliteConnector.ensureSchemaExists(pluginConfig, pluginId); - } catch (error) { - throw new Error( - `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`, - ); - } - } + const pluginDivisionMode = this.getPluginDivisionModeConfig(); + if (pluginDivisionMode !== 'database') { + throw new Error( + `The SQLite driver does not suppoert plugin division mode '${pluginDivisionMode}'`, + ); } const databaseClientOverrides = mergeDatabaseConfig( {}, this.getDatabaseOverrides(pluginId), - schemaOverrides, ); const client = sqliteConnector.createClient( @@ -284,6 +237,10 @@ export class Sqlite3Connector implements Connector { return client; } + async dropDatabase(..._databaseNames: string[]): Promise<void> { + // do nothing + } + /** * Provides the canonical database name for a given plugin. * @@ -299,30 +256,18 @@ export class Sqlite3Connector implements Connector { private getDatabaseName(pluginId: string): string | undefined { const connection = this.getConnectionConfig(pluginId); - if (this.getClientType(pluginId).client.includes('sqlite3')) { - const sqliteFilename: string | undefined = ( - connection as Knex.Sqlite3ConnectionConfig - ).filename; + const sqliteFilename: string | undefined = ( + connection as Knex.Sqlite3ConnectionConfig + ).filename; - if (sqliteFilename === ':memory:') { - return sqliteFilename; - } - - const sqliteDirectory = - (connection as { directory?: string }).directory ?? '.'; - - return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`); + if (sqliteFilename === ':memory:') { + return sqliteFilename; } - const databaseName = (connection as Knex.ConnectionConfig)?.database; + const sqliteDirectory = + (connection as { directory?: string }).directory ?? '.'; - // `pluginDivisionMode` as `schema` should use overridden databaseName if supplied or fallback to default knex database - if (this.getPluginDivisionModeConfig() === 'schema') { - return databaseName; - } - - // all other supported databases should fallback to an auto-prefixed name - return databaseName ?? `${this.prefix}${pluginId}`; + return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`); } /** @@ -377,14 +322,6 @@ export class Sqlite3Connector implements Connector { return merge(baseConfig, pluginConfig); } - private getEnsureExistsConfig(pluginId: string): boolean { - const baseConfig = this.config.getOptionalBoolean('ensureExists') ?? true; - return ( - this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ?? - baseConfig - ); - } - private getPluginDivisionModeConfig(): string { return this.config.getOptionalString('pluginDivisionMode') ?? 'database'; } @@ -459,17 +396,6 @@ export class Sqlite3Connector implements Connector { }; } - /** - * Provides a partial `Knex.Config` database schema override for a given - * plugin. - * - * @param pluginId - Target plugin to get database schema override - * @returns Partial `Knex.Config` with database schema override - */ - private getSchemaOverrides(pluginId: string): Knex.Config | undefined { - return createSchemaOverride(this.getClientType(pluginId).client, pluginId); - } - /** * Provides a partial `Knex.Config`• database name override for a given plugin. * diff --git a/packages/backend-common/src/database/index.ts b/packages/backend-common/src/database/index.ts index 464cf4a4de..a0a61ccdd0 100644 --- a/packages/backend-common/src/database/index.ts +++ b/packages/backend-common/src/database/index.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -export * from './DatabaseManager'; +export { DatabaseManager, dropDatabase } from './DatabaseManager'; +export type { + DatabaseManagerOptions, + LegacyRootDatabaseService, +} from './DatabaseManager'; export type { PluginDatabaseManager } from './types'; export { isDatabaseConflictError } from './util'; diff --git a/packages/backend-common/src/database/types.ts b/packages/backend-common/src/database/types.ts index 967de6966e..a9cceaa1f9 100644 --- a/packages/backend-common/src/database/types.ts +++ b/packages/backend-common/src/database/types.ts @@ -95,4 +95,6 @@ export interface Connector { pluginMetadata: PluginMetadataService; }, ): Promise<Knex>; + + dropDatabase(...databaseNames: string[]): Promise<void>; } From db744f7a5195581434185650279608b57a94e701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Mon, 29 Apr 2024 09:52:46 +0200 Subject: [PATCH 149/234] clean up manager tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- .../src/database/DatabaseManager.test.ts | 808 +----------------- 1 file changed, 16 insertions(+), 792 deletions(-) diff --git a/packages/backend-common/src/database/DatabaseManager.test.ts b/packages/backend-common/src/database/DatabaseManager.test.ts index ba911b68b9..347ec742c0 100644 --- a/packages/backend-common/src/database/DatabaseManager.test.ts +++ b/packages/backend-common/src/database/DatabaseManager.test.ts @@ -96,804 +96,28 @@ describe('DatabaseManagerImpl', () => { expect(connector2.getClient).toHaveBeenLastCalledWith('plugin2', undefined); }); - // eslint-disable-next-line jest/no-commented-out-tests - /* - it('accesses the backend.database key', () => { - const config = new ConfigReader(backendConfig); - const getConfigSpy = jest.spyOn(config, 'getConfig'); - DatabaseManager.fromConfig(config); + it('retains the migration skip info', async () => { + const connector = { + getClient: jest.fn(), + dropDatabase: jest.fn(), + } satisfies Connector; - expect(getConfigSpy).toHaveBeenCalledWith('backend.database'); + const impl1 = new DatabaseManagerImpl(new ConfigReader({ client: 'pg' }), { + pg: connector, }); - it('handles default options', () => { - const config = new ConfigReader(backendConfig); - const database = DatabaseManager.fromConfig(config); - const client = database.forPlugin('test'); + const impl2 = new DatabaseManagerImpl( + new ConfigReader({ client: 'pg' }), + { pg: connector }, + { migrations: { skip: true } }, + ); - expect(client.migrations?.skip).toBe(false); + expect((await impl1.forPlugin('plugin1')).migrations).toEqual({ + skip: false, }); - it('handles migrations options', () => { - const config = new ConfigReader(backendConfig); - const database = DatabaseManager.fromConfig(config, { - migrations: { skip: true }, - }); - const client = database.forPlugin('test'); - - expect(client.migrations?.skip).toBe(true); + expect((await impl2.forPlugin('plugin1')).migrations).toEqual({ + skip: true, }); }); - - describe('DatabaseManager.forPlugin', () => { - const config = { - backend: { - database: { - client: 'pg', - prefix: 'test_prefix_', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - plugin: { - testdbname: { - connection: { - database: 'database_name_overridden', - }, - }, - differentclient: { - client: 'better-sqlite3', - connection: { - filename: 'plugin_with_different_client', - }, - }, - differentclientconnstring: { - client: 'better-sqlite3', - connection: ':memory:', - }, - stringoverride: { - connection: 'postgresql://testuser:testpass@acme:5432/userdbname', - }, - }, - }, - }, - }; - let manager: DatabaseManager; - - beforeEach(() => { - manager = DatabaseManager.fromConfig(new ConfigReader(config)); - }); - - it('connects to a plugin database using default config', async () => { - const pluginId = 'pluginwithoutconfig'; - - await manager.forPlugin(pluginId).getClient(); - expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(1); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - // default config should be passed through to underlying connector - expect(baseConfig.get()).toMatchObject({ - client: 'pg', - connection: omit(config.backend.database.connection, ['database']), - }); - - // override using database name generated from pluginId and prefix - expect(overrides).toMatchObject({ - connection: { - database: `${config.backend.database.prefix}${pluginId}`, - }, - }); - }); - - it('provides a plugin db which uses components from top level connection string', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: 'postgresql://foo:bar@acme:5432/foodb', - }, - }, - }), - ); - - await testManager.forPlugin('pluginwithoutconfig').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - // parsed connection string **without** db name should be passed through - expect(baseConfig.get()).toMatchObject({ - connection: { - host: 'acme', - user: 'foo', - password: 'bar', - port: '5432', - application_name: 'backstage_plugin_pluginwithoutconfig', - }, - }); - - // we expect a pg database name override with ${prefix} followed by pluginId - expect(overrides).toHaveProperty( - 'connection.database', - expect.stringContaining('pluginwithoutconfig'), - ); - }); - - it('provides an inmemory sqlite database if top level is also inmemory and plugin config is not present', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', - }, - }, - }), - ); - - await testManager.forPlugin('pluginwithoutconfig').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.filename', - expect.stringContaining(':memory:'), - ); - }); - - it('throws if top level sqlite filename is provided', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: 'some-file-path', - }, - }, - }), - ); - - await expect( - testManager.forPlugin('pluginwithoutconfig').getClient(), - ).rejects.toBeInstanceOf(Error); - }); - - it('creates plugin-specific sqlite files when plugin config is not present', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: { - directory: 'sqlite-files', - }, - }, - }, - }), - ); - - await testManager.forPlugin('pluginwithoutconfig').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.filename', - path.join('sqlite-files', 'pluginwithoutconfig.sqlite'), - ); - }); - - it('uses sqlite directory from top level config and filename from plugin config', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: { - directory: 'sqlite-files', - }, - plugin: { - test: { - connection: { - filename: 'other.sqlite', - }, - }, - }, - }, - }, - }), - ); - - await testManager.forPlugin('test').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.filename', - path.join('sqlite-files', 'other.sqlite'), - ); - }); - - it('uses sqlite directory and filename from plugin config', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - connection: { - directory: 'sqlite-files', - }, - plugin: { - test: { - connection: { - directory: 'custom-sqlite-files', - filename: 'other.sqlite', - }, - }, - }, - }, - }, - }), - ); - - await testManager.forPlugin('test').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.filename', - path.join('custom-sqlite-files', 'other.sqlite'), - ); - }); - - it('connects to a plugin database using a specific database name', async () => { - // testdbname.connection.database is set in config - await manager.forPlugin('testdbname').getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_baseConfig, overrides] = mockCalls[0]; - - // simple case where only database name is overridden - expect(overrides).toMatchObject({ - connection: { - database: 'database_name_overridden', - }, - }); - }); - - it('ensure plugin specific database is created', async () => { - const pluginId = 'testdbname'; - // testdbname.connection.database is set in config - await manager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(ensureDatabaseExists).mock.calls.splice(-1); - const [_, dbname] = mockCalls[0]; - - expect(dbname).toEqual( - config.backend.database.plugin[pluginId].connection.database, - ); - }); - - it('provides different plugins with their own databases', async () => { - await manager.forPlugin('plugin1').getClient(); - await manager.forPlugin('plugin2').getClient(); - - expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(2); - - const mockCalls = mocked(createDatabaseClient).mock.calls; - const [plugin1CallArgs, plugin2CallArgs] = mockCalls; - - // database name overrides should be different - expect(plugin1CallArgs[1].connection.database).not.toEqual( - plugin2CallArgs[1].connection.database, - ); - }); - - it('returns the same client for the same pluginId', async () => { - const [client1, client2] = await Promise.all([ - manager.forPlugin('plugin1').getClient(), - manager.forPlugin('plugin1').getClient(), - ]); - expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(1); - - expect(client1).toBe(client2); - }); - - it('uses plugin connection as base if default client is different from plugin client', async () => { - const pluginId = 'differentclient'; - await manager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, _overrides] = mockCalls[0]; - - // plugin connection should be used as base config, client is different - expect(baseConfig.get()).toMatchObject({ - client: 'better-sqlite3', - connection: config.backend.database.plugin[pluginId].connection, - }); - }); - - it('provides database client specific base and override when client set under plugin', async () => { - const pluginId = 'differentclient'; - await manager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - // plugin client should be better-sqlite3 - expect(baseConfig.get().client).toEqual('better-sqlite3'); - - // SQLite uses 'filename' instead of 'database' - expect(overrides).toHaveProperty( - 'connection.filename', - 'plugin_with_different_client', - ); - }); - - it('provides database client specific base from plugin connection string when client set under plugin', async () => { - const pluginId = 'differentclientconnstring'; - await manager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - expect(baseConfig.get().client).toEqual('better-sqlite3'); - - expect(overrides).toHaveProperty('connection.filename', ':memory:'); - }); - - it('generates a database name override when prefix is not explicitly set', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - - await testManager.forPlugin('testplugin').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_baseConfig, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.database', - expect.stringContaining('backstage_plugin_'), - ); - }); - - it('generates a database name override when prefix is not explicitly set for mysql', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'mysql', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - - await testManager.forPlugin('testplugin').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_baseConfig, overrides] = mockCalls[0]; - - expect(overrides).toHaveProperty( - 'connection.database', - expect.stringContaining('backstage_plugin_'), - ); - }); - - it('uses values from plugin connection string if top level client should be used', async () => { - const pluginId = 'stringoverride'; - await manager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - // plugin client should be pg - expect(baseConfig.get().client).toEqual('pg'); - - expect(overrides).toHaveProperty( - 'connection.database', - expect.stringContaining('userdbname'), - ); - }); - - it('plugin sets schema override for pg client', async () => { - const overrideConfig = { - backend: { - database: { - client: 'pg', - pluginDivisionMode: 'schema', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }; - const testManager = DatabaseManager.fromConfig( - new ConfigReader(overrideConfig), - ); - const pluginId = 'schemaoverride'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - expect(baseConfig.get()).toMatchObject({ - client: 'pg', - connection: config.backend.database.connection, - }); - - expect(overrides).toMatchObject({ - searchPath: [pluginId], - }); - }); - - it('plugin does not provide schema override for non pg client', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'better-sqlite3', - pluginDivisionMode: 'schema', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - const pluginId = 'any-plugin'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - expect(baseConfig.get()).toMatchObject({ - client: 'better-sqlite3', - connection: config.backend.database.connection, - }); - - expect(overrides).not.toHaveProperty('searchPath'); - }); - - it('plugin does not provide schema override if pluginDivisionMode is set to database', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - pluginDivisionMode: 'database', - connection: 'some-file-path', - }, - }, - }), - ); - - const pluginId = 'any-plugin'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_baseConfig, overrides] = mockCalls[0]; - - expect(overrides).not.toHaveProperty('searchPath'); - }); - - it('plugin does not provide schema override if pluginDivisionMode is not set', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - - const pluginId = 'schemaoverride'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [_baseConfig, overrides] = mockCalls[0]; - - expect(overrides).not.toHaveProperty('searchPath'); - }); - - it('pluginDivisionMode ensures that each plugin schema exists', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - pluginDivisionMode: 'schema', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - const pluginId = 'testdbname'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(ensureSchemaExists).mock.calls; - const [_, schemaName] = mockCalls[0]; - - expect(schemaName).toEqual('testdbname'); - }); - - it('pluginDivisionMode allows connection overrides for plugins', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - pluginDivisionMode: 'schema', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - plugin: { - testdbname: { - connection: { - database: 'database_name_overridden', - host: 'newhost', - }, - }, - }, - }, - }, - }), - ); - const pluginId = 'testdbname'; - await testManager.forPlugin(pluginId).getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - - expect(baseConfig.get()).toMatchObject({ - client: 'pg', - connection: { - database: 'database_name_overridden', - host: 'newhost', - user: 'foo', - password: 'bar', - }, - }); - expect(overrides).toHaveProperty('searchPath', ['testdbname']); - expect(overrides).toHaveProperty( - 'connection.database', - 'database_name_overridden', - ); - }); - - it('ensureExists does not create database or schema when false', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - pluginDivisionMode: 'schema', - ensureExists: false, - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - }, - }, - }, - }), - ); - const pluginId = 'testdbname'; - await testManager.forPlugin(pluginId).getClient(); - - expect(mocked(ensureDatabaseExists)).toHaveBeenCalledTimes(0); - expect(mocked(ensureSchemaExists)).toHaveBeenCalledTimes(0); - }); - - it('fetches and merges additional knex config', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - database: 'foodb', - }, - knexConfig: { - something: false, - }, - plugin: { - testdbname: { - knexConfig: { - debug: true, - }, - }, - }, - }, - }, - }), - ); - await testManager.forPlugin('testdbname').getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig] = mockCalls[0]; - - expect(baseConfig.data).toEqual( - expect.objectContaining({ - debug: true, - something: false, - }), - ); - }); - - it('sets the owner config for plugin using default config', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - database: 'foodb', - }, - role: 'backstage', - plugin: { - testowner: {}, - }, - }, - }, - }), - ); - await testManager.forPlugin('testowner').getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig] = mockCalls[0]; - - expect(baseConfig.data.role).toEqual('backstage'); - }); - - it('sets the owner config for plugin using plugin config', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - database: 'foodb', - }, - role: 'backstage', - plugin: { - testowner: { - role: 'backstage-plugin', - }, - }, - }, - }, - }), - ); - await testManager.forPlugin('testowner').getClient(); - - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig] = mockCalls[0]; - - expect(baseConfig.data.role).toEqual('backstage-plugin'); - }); - - it('Defaults the application_name for postgres clients', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: {}, - }, - }, - }), - ); - - await testManager.forPlugin('testplugin').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, _] = mockCalls[0]; - expect(baseConfig.get()).toMatchObject({ - connection: { - application_name: 'backstage_plugin_testplugin', - }, - }); - }); - - it('Allows manually setting the application_name for postgres clients', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - application_name: 'backstage_custom_app_name', - }, - }, - }, - }), - ); - - await testManager.forPlugin('testplugin').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, overrides] = mockCalls[0]; - expect(baseConfig.get().connection.application_name).toBe( - 'backstage_custom_app_name', - ); - expect(overrides.connection.application_name).toBeUndefined(); - }); - - it('Allows manually setting the application_name for individual plugin client', async () => { - const testManager = DatabaseManager.fromConfig( - new ConfigReader({ - backend: { - database: { - client: 'pg', - connection: { - host: 'localhost', - user: 'foo', - password: 'bar', - database: 'foodb', - application_name: 'backstage_custom_app_name', - }, - plugin: { - overrideplugin: { - connection: { - application_name: 'custom_plugin', - }, - }, - }, - }, - }, - }), - ); - - await testManager.forPlugin('overrideplugin').getClient(); - const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1); - const [baseConfig, _] = mockCalls[0]; - expect(baseConfig.get().connection.application_name).toBe( - 'custom_plugin', - ); - }); - }); - */ }); From ed83f855352307de4a67b878af97b6dbdb191d5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Mon, 29 Apr 2024 10:16:44 +0200 Subject: [PATCH 150/234] add changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- .changeset/mean-ravens-dance.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/mean-ravens-dance.md diff --git a/.changeset/mean-ravens-dance.md b/.changeset/mean-ravens-dance.md new file mode 100644 index 0000000000..e409f57c79 --- /dev/null +++ b/.changeset/mean-ravens-dance.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Internal refactor of the database code From 2cc750d36766a43c277760af681a28e373867243 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Fri, 19 Apr 2024 00:55:19 -0600 Subject: [PATCH 151/234] feat: integration support for harness Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- .changeset/empty-beers-relax.md | 5 + .changeset/tasty-rats-explain.md | 5 + .../config/vocabularies/Backstage/accept.txt | 2 + docs/integrations/harness/locations.md | 33 +++ microsite/sidebars.json | 5 + packages/backend-common/api-report.md | 26 +++ .../src/reading/HarnessCodeUrlReader.test.ts | 204 ++++++++++++++++++ .../src/reading/HarnessUrlReader.ts | 124 +++++++++++ .../backend-common/src/reading/UrlReaders.ts | 3 +- packages/backend-common/src/reading/index.ts | 1 + packages/integration/api-report.md | 55 ++++- packages/integration/config.d.ts | 24 +++ .../integration/src/ScmIntegrations.test.ts | 9 + packages/integration/src/ScmIntegrations.ts | 7 + .../src/harness/HarnessIntegration.test.ts | 128 +++++++++++ .../src/harness/HarnessIntegration.ts | 58 +++++ .../integration/src/harness/config.test.ts | 108 ++++++++++ packages/integration/src/harness/config.ts | 78 +++++++ packages/integration/src/harness/core.test.ts | 95 ++++++++ packages/integration/src/harness/core.ts | 135 ++++++++++++ packages/integration/src/harness/index.ts | 19 ++ packages/integration/src/index.ts | 1 + packages/integration/src/registry.ts | 2 + 23 files changed, 1125 insertions(+), 2 deletions(-) create mode 100644 .changeset/empty-beers-relax.md create mode 100644 .changeset/tasty-rats-explain.md create mode 100644 docs/integrations/harness/locations.md create mode 100644 packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts create mode 100644 packages/backend-common/src/reading/HarnessUrlReader.ts create mode 100644 packages/integration/src/harness/HarnessIntegration.test.ts create mode 100644 packages/integration/src/harness/HarnessIntegration.ts create mode 100644 packages/integration/src/harness/config.test.ts create mode 100644 packages/integration/src/harness/config.ts create mode 100644 packages/integration/src/harness/core.test.ts create mode 100644 packages/integration/src/harness/core.ts create mode 100644 packages/integration/src/harness/index.ts diff --git a/.changeset/empty-beers-relax.md b/.changeset/empty-beers-relax.md new file mode 100644 index 0000000000..526f88c1c7 --- /dev/null +++ b/.changeset/empty-beers-relax.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +This patch adds HarnessURLReader to the available classes. It currently only reads single files via Harness codes public repo api. diff --git a/.changeset/tasty-rats-explain.md b/.changeset/tasty-rats-explain.md new file mode 100644 index 0000000000..aee8057915 --- /dev/null +++ b/.changeset/tasty-rats-explain.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': minor +--- + +This patch brings Harness Code as a valid integration via the ScmIntgration interface. It adds harness code to the relevant static properties ( get integration by name, get integration by type) for plugs to be able to reference the same harness code server diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 026e5a9b18..2e42ff7998 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -152,6 +152,8 @@ graphviz Hackathons haproxy hardcoded +Harness +harness Helidon Henneke Heroku diff --git a/docs/integrations/harness/locations.md b/docs/integrations/harness/locations.md new file mode 100644 index 0000000000..8911d7918c --- /dev/null +++ b/docs/integrations/harness/locations.md @@ -0,0 +1,33 @@ +--- +id: locations +title: Harness Locations +sidebar_label: Locations +description: Integrating source code stored in Harness Code into the Backstage catalog +--- + +The Harness Code integration supports loading catalog entities from a hosted repository. Entities can be added to +[static catalog configuration](../../features/software-catalog/configuration.md), +registered with the +[catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) +plugin. + +## Configuration + +To use this integration, add configuration to your root `app-config.yaml`: + +```yaml +integrations: + harness: + - host: app.harness.io + token: ${HARNESS_CODE_BEARER_TOKEN} +``` + +Directly under the `harnessCode` key is a list of provider configurations, where you +can list the Gitea instances you want to be able to fetch +data from. Each entry is a structure with up to four elements: + +- `host`: The host of the Harness Code instance that you want to match on. +- `baseUrl` (optional): Needed if the Harness Code instance is not reachable at + the base of the `host` option (e.g. `https://app.harness.io`). This is the address that you would open in a browser. +- `username` (optional): The gitea username to use in API requests. +- `token` (optional): The password or api token to authenticate with. diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 32863c61db..8291549153 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -238,6 +238,11 @@ "label": "Gitea", "items": ["integrations/gitea/locations"] }, + { + "type": "subcategory", + "label": "Harness", + "ids": ["integrations/harness/locations"] + }, { "type": "category", "label": "Google GCS", diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index f9e0d76aab..37bfda9861 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -30,6 +30,7 @@ import { GiteaIntegration } from '@backstage/integration'; import { GithubCredentialsProvider } from '@backstage/integration'; import { GithubIntegration } from '@backstage/integration'; import { GitLabIntegration } from '@backstage/integration'; +import { HarnessIntegration } from '@backstage/integration'; import { HostDiscovery as HostDiscovery_2 } from '@backstage/backend-app-api'; import { HttpAuthService } from '@backstage/backend-plugin-api'; import { IdentityService } from '@backstage/backend-plugin-api'; @@ -530,6 +531,31 @@ export class GitlabUrlReader implements UrlReader { toString(): string; } +// @public +export class HarnessUrlReader implements UrlReader { + constructor(integration: HarnessIntegration); + // (undocumented) + static factory: ReaderFactory; + // (undocumented) + read(url: string): Promise<Buffer>; + // (undocumented) + readTree(): Promise<ReadTreeResponse>; + // (undocumented) + readUrl(url: string, options?: ReadUrlOptions): Promise<ReadUrlResponse>; + // (undocumented) + search(): Promise<SearchResponse>; + // (undocumented) + toString(): string; +} + +// @public +export type HarnessIntegrationConfig = { + host: string; + baseUrl?: string; + username?: string; + token?: string; +}; + // @public export const HostDiscovery: typeof HostDiscovery_2; diff --git a/packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts b/packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts new file mode 100644 index 0000000000..3e8b70a6aa --- /dev/null +++ b/packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts @@ -0,0 +1,204 @@ +/* + * 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 { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { HarnessIntegration, readHarnessConfig } from '@backstage/integration'; +import { JsonObject } from '@backstage/types'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { getVoidLogger } from '../logging'; +import { UrlReaderPredicateTuple } from './types'; +import { DefaultReadTreeResponseFactory } from './tree'; +import getRawBody from 'raw-body'; +import { HarnessUrlReader } from './HarnessUrlReader'; +import { NotFoundError } from '@backstage/errors'; + +const treeResponseFactory = DefaultReadTreeResponseFactory.create({ + config: new ConfigReader({}), +}); + +jest.mock('../scm', () => ({ + Git: { + fromAuth: () => ({ + clone: jest.fn(() => Promise.resolve({})), + }), + }, +})); + +const harnessProcessor = new HarnessUrlReader( + new HarnessIntegration( + readHarnessConfig( + new ConfigReader({ + host: 'app.harness.io', + }), + ), + ), +); + +const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { + return HarnessUrlReader.factory({ + config: new ConfigReader(config), + logger: getVoidLogger(), + treeResponseFactory, + }); +}; + +describe('HarnessUrlReader', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + afterAll(() => { + jest.clearAllMocks(); + }); + + describe('reader factory', () => { + it('creates a reader.', () => { + const readers = createReader({ + integrations: { + harness: [{ host: 'app.harness.io' }], + }, + }); + expect(readers).toHaveLength(1); + }); + + it('should not create a default entry.', () => { + const readers = createReader({ + integrations: {}, + }); + expect(readers).toHaveLength(0); + }); + }); + + describe('predicates', () => { + it('returns true for the configured host', () => { + const readers = createReader({ + integrations: { + harness: [{ host: 'app.harness.io' }], + }, + }); + const predicate = readers[0].predicate; + + expect(predicate(new URL('https://app.harness.io/path'))).toBe(true); + }); + + it('returns false for a different host.', () => { + const readers = createReader({ + integrations: { + harness: [{ host: 'app.harness.io' }], + }, + }); + const predicate = readers[0].predicate; + + expect(predicate(new URL('https://github.com/path'))).toBe(false); + }); + }); + + describe('readUrl', () => { + const responseBuffer = Buffer.from('Apache License'); + const harnessApiResponse = (content: any) => { + return JSON.stringify({ + encoding: 'base64', + content: Buffer.from(content).toString('base64'), + }); + }; + + it.skip('should be able to read file contents as buffer', async () => { + worker.use( + rest.get( + 'https://app.harness.io/api/v1/repos/owner/project/contents/LICENSE', + (req, res, ctx) => { + // Test utils prefers matching URL directly but it is part of Gitea's API + if (req.url.searchParams.get('ref') === 'branch2') { + return res( + ctx.status(200), + ctx.body(harnessApiResponse(responseBuffer.toString())), + ); + } + + return res(ctx.status(500)); + }, + ), + ); + + const result = await harnessProcessor.readUrl( + 'https://app.harness.io/owner/project/src/branch/branch2/LICENSE', + ); + const buffer = await result.buffer(); + expect(buffer.toString()).toBe(responseBuffer.toString()); + }); + + it.skip('should be able to read file contents as stream', async () => { + worker.use( + rest.get( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/LICENSE.txt', + (req, res, ctx) => { + if (req.url.searchParams.get('ref') === 'refMain') { + return res( + ctx.status(200), + ctx.body(harnessApiResponse(responseBuffer.toString())), + ); + } + + return res(ctx.status(500)); + }, + ), + ); + + const result = await harnessProcessor.readUrl( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/LICENSE.TXT', + ); + const fromStream = await getRawBody(result.stream!()); + expect(fromStream.toString()).toBe(responseBuffer.toString()); + }); + + it.skip('should raise NotFoundError on 404.', async () => { + worker.use( + rest.get( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + (_, res, ctx) => { + return res(ctx.status(404, 'File not found.')); + }, + ), + ); + + await expect( + harnessProcessor.readUrl( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + ), + ).rejects.toThrow(NotFoundError); + }); + + it.skip('should throw an error on non 404 errors.', async () => { + worker.use( + rest.get( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + (_, res, ctx) => { + return res(ctx.status(500, 'Error!!!')); + }, + ), + ); + + await expect( + harnessProcessor.readUrl( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + ), + ).rejects.toThrow( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain, 500 Error!!!', + ); + }); + }); +}); diff --git a/packages/backend-common/src/reading/HarnessUrlReader.ts b/packages/backend-common/src/reading/HarnessUrlReader.ts new file mode 100644 index 0000000000..fd01e4f8a2 --- /dev/null +++ b/packages/backend-common/src/reading/HarnessUrlReader.ts @@ -0,0 +1,124 @@ +/* + * 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 { + getHarnessRequestOptions, + getHarnessFileContentsUrl, + HarnessIntegration, + ScmIntegrations, +} from '@backstage/integration'; +import { ReadUrlOptions, ReadUrlResponse } from './types'; +import { + ReaderFactory, + ReadTreeResponse, + SearchResponse, + UrlReader, +} from './types'; +import fetch, { Response } from 'node-fetch'; +import { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; +import { + AuthenticationError, + NotFoundError, + NotModifiedError, +} from '@backstage/errors'; +import { Readable } from 'stream'; + +/** + * Implements a {@link UrlReader} for the Harness code v1 api. + * + * @public + */ +export class HarnessUrlReader implements UrlReader { + static factory: ReaderFactory = ({ config }) => { + return ScmIntegrations.fromConfig(config) + .harness.list() + .map(integration => { + const reader = new HarnessUrlReader(integration); + const predicate = (url: URL) => { + return url.host === integration.config.host; + }; + return { reader, predicate }; + }); + }; + + constructor(private readonly integration: HarnessIntegration) {} + + async read(url: string): Promise<Buffer> { + const response = await this.readUrl(url); + return response.buffer(); + } + + async readUrl( + url: string, + options?: ReadUrlOptions, + ): Promise<ReadUrlResponse> { + let response: Response; + const blobUrl = getHarnessFileContentsUrl(this.integration.config, url); + + try { + response = await fetch(blobUrl, { + method: 'GET', + ...getHarnessRequestOptions(this.integration.config), + signal: options?.signal as any, + }); + } catch (e) { + throw new Error(`Unable to read ${blobUrl}, ${e}`); + } + + if (response.ok) { + // Harness Code returns an object with the file contents encoded, not the file itself + const jsonResponse = await response.json(); + if (jsonResponse?.content?.encoding === 'base64') { + return ReadUrlResponseFactory.fromReadable( + Readable.from(Buffer.from(jsonResponse?.content?.data, 'base64')), + { + etag: response.headers.get('ETag') ?? undefined, + }, + ); + } + + throw new Error(`Unknown encoding: ${jsonResponse?.content?.encoding}`); + } + + const message = `${url} x ${blobUrl}, ${response.status} ${response.statusText}`; + if (response.status === 404) { + throw new NotFoundError(message); + } + + if (response.status === 304) { + throw new NotModifiedError(); + } + + if (response.status === 403) { + throw new AuthenticationError(); + } + + throw new Error(message); + } + + readTree(): Promise<ReadTreeResponse> { + throw new Error('HarnessUrlReader readTree not implemented.'); + } + search(): Promise<SearchResponse> { + throw new Error('HarnessUrlReader search not implemented.'); + } + + toString() { + const { host } = this.integration.config; + return `harness{host=${host},authed=${Boolean( + this.integration.config.token, + )}}`; + } +} diff --git a/packages/backend-common/src/reading/UrlReaders.ts b/packages/backend-common/src/reading/UrlReaders.ts index e987eb9186..7aaaf120b4 100644 --- a/packages/backend-common/src/reading/UrlReaders.ts +++ b/packages/backend-common/src/reading/UrlReaders.ts @@ -31,6 +31,7 @@ import { GoogleGcsUrlReader } from './GoogleGcsUrlReader'; import { AwsS3UrlReader } from './AwsS3UrlReader'; import { GiteaUrlReader } from './GiteaUrlReader'; import { AwsCodeCommitUrlReader } from './AwsCodeCommitUrlReader'; +import { HarnessUrlReader } from './HarnessUrlReader'; /** * Creation options for {@link @backstage/backend-plugin-api#UrlReaderService}. @@ -61,7 +62,6 @@ export class UrlReaders { const treeResponseFactory = DefaultReadTreeResponseFactory.create({ config, }); - for (const factory of factories ?? []) { const tuples = factory({ config, logger: logger, treeResponseFactory }); @@ -94,6 +94,7 @@ export class UrlReaders { GiteaUrlReader.factory, GitlabUrlReader.factory, GoogleGcsUrlReader.factory, + HarnessUrlReader.factory, AwsS3UrlReader.factory, AwsCodeCommitUrlReader.factory, FetchUrlReader.factory, diff --git a/packages/backend-common/src/reading/index.ts b/packages/backend-common/src/reading/index.ts index 21e82da9b5..a99bb6dda1 100644 --- a/packages/backend-common/src/reading/index.ts +++ b/packages/backend-common/src/reading/index.ts @@ -22,6 +22,7 @@ export { GerritUrlReader } from './GerritUrlReader'; export { GithubUrlReader } from './GithubUrlReader'; export { GitlabUrlReader } from './GitlabUrlReader'; export { GiteaUrlReader } from './GiteaUrlReader'; +export { HarnessUrlReader } from './HarnessUrlReader'; export { AwsS3UrlReader } from './AwsS3UrlReader'; export { FetchUrlReader } from './FetchUrlReader'; export { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index 6b691dff80..a693c1314c 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -513,6 +513,19 @@ export function getGitLabRequestOptions(config: GitLabIntegrationConfig): { headers: Record<string, string>; }; +// @public +export function getHarnessFileContentsUrl( + config: HarnessIntegrationConfig, + url: string, +): string; + +// @public +export function getHarnessRequestOptions( + config: HarnessIntegrationConfig, +): { + headers?: Record<string, string>; +}; + // @public export class GiteaIntegration implements ScmIntegration { constructor(config: GiteaIntegrationConfig); @@ -539,7 +552,7 @@ export type GiteaIntegrationConfig = { host: string; baseUrl?: string; username?: string; - password?: string; + token?: string; }; // @public @@ -674,6 +687,35 @@ export type GoogleGcsIntegrationConfig = { privateKey?: string; }; +// @public +export class HarnessIntegration implements ScmIntegration { + constructor(config: HarnessIntegrationConfig); + // (undocumented) + readonly config: HarnessIntegrationConfig; + // (undocumented) + static factory: ScmIntegrationsFactory<HarnessIntegration>; + // (undocumented) + resolveEditUrl(url: string): string; + // (undocumented) + resolveUrl(options: { + url: string; + base: string; + lineNumber?: number | undefined; + }): string; + // (undocumented) + get title(): string; + // (undocumented) + get type(): string; +} + +// @public +export type HarnessIntegrationConfig = { + host: string; + baseUrl?: string; + username?: string; + token?: string; +}; + // @public export interface IntegrationsByType { // (undocumented) @@ -696,6 +738,8 @@ export interface IntegrationsByType { github: ScmIntegrationsGroup<GithubIntegration>; // (undocumented) gitlab: ScmIntegrationsGroup<GitLabIntegration>; + // (undocumented) + harness: ScmIntegrationsGroup<HarnessIntegration>; } // @public @@ -839,6 +883,11 @@ export function readGoogleGcsIntegrationConfig( config: Config, ): GoogleGcsIntegrationConfig; +// @public +export function readHarnessConfig( + config: Config, +): HarnessIntegrationConfig; + // @public @deprecated (undocumented) export const replaceGitHubUrlType: typeof replaceGithubUrlType; @@ -889,6 +938,8 @@ export interface ScmIntegrationRegistry github: ScmIntegrationsGroup<GithubIntegration>; // (undocumented) gitlab: ScmIntegrationsGroup<GitLabIntegration>; + // (undocumented) + harness: ScmIntegrationsGroup<HarnessIntegration>; resolveEditUrl(url: string): string; resolveUrl(options: { url: string; @@ -927,6 +978,8 @@ export class ScmIntegrations implements ScmIntegrationRegistry { // (undocumented) get gitlab(): ScmIntegrationsGroup<GitLabIntegration>; // (undocumented) + get harness(): ScmIntegrationsGroup<HarnessIntegration>; + // (undocumented) list(): ScmIntegration[]; // (undocumented) resolveEditUrl(url: string): string; diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 44303bf25c..01f1f44d73 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -345,5 +345,29 @@ export interface Config { */ password?: string; }>; + /** Integration configuration for Harness Code */ + harness?: Array<{ + /** + * The hostname of the given Harness Code instance + * @visibility frontend + */ + host: string; + /** + * The base url for the Gitea instance. + * @visibility frontend + */ + baseUrl?: string; + + /** + * The username to use for authenticated requests. + * @visibility secret + */ + username?: string; + /** + * Harness Code token used to authenticate requests. This can be either a generated access token. + * @visibility secret + */ + token?: string; + }>; }; } diff --git a/packages/integration/src/ScmIntegrations.test.ts b/packages/integration/src/ScmIntegrations.test.ts index acf602e38d..c758816c03 100644 --- a/packages/integration/src/ScmIntegrations.test.ts +++ b/packages/integration/src/ScmIntegrations.test.ts @@ -38,6 +38,7 @@ import { ScmIntegrations } from './ScmIntegrations'; import { GiteaIntegration, GiteaIntegrationConfig } from './gitea'; import { AwsCodeCommitIntegration } from './awsCodeCommit/AwsCodeCommitIntegration'; import { AwsCodeCommitIntegrationConfig } from './awsCodeCommit'; +import { HarnessIntegration, HarnessIntegrationConfig } from './harness'; describe('ScmIntegrations', () => { const awsS3 = new AwsS3Integration({ @@ -80,6 +81,10 @@ describe('ScmIntegrations', () => { host: 'gitea.local', } as GiteaIntegrationConfig); + const harness = new HarnessIntegration({ + host: 'harness.local', + } as HarnessIntegrationConfig); + const i = new ScmIntegrations({ awsS3: basicIntegrations([awsS3], item => item.config.host), awsCodeCommit: basicIntegrations([awsCodeCommit], item => item.config.host), @@ -94,6 +99,7 @@ describe('ScmIntegrations', () => { github: basicIntegrations([github], item => item.config.host), gitlab: basicIntegrations([gitlab], item => item.config.host), gitea: basicIntegrations([gitea], item => item.config.host), + harness: basicIntegrations([harness], item => item.config.host), }); it('can get the specifics', () => { @@ -113,6 +119,7 @@ describe('ScmIntegrations', () => { expect(i.github.byUrl('https://github.local')).toBe(github); expect(i.gitlab.byUrl('https://gitlab.local')).toBe(gitlab); expect(i.gitea.byUrl('https://gitea.local')).toBe(gitea); + expect(i.harness.byUrl('https://harness.local')).toBe(harness); }); it('can list', () => { @@ -128,6 +135,7 @@ describe('ScmIntegrations', () => { github, gitlab, gitea, + harness, ]), ); }); @@ -143,6 +151,7 @@ describe('ScmIntegrations', () => { expect(i.byUrl('https://github.local')).toBe(github); expect(i.byUrl('https://gitlab.local')).toBe(gitlab); expect(i.byUrl('https://gitea.local')).toBe(gitea); + expect(i.byUrl('https://harness.local')).toBe(harness); expect(i.byHost('awss3.local')).toBe(awsS3); expect(i.byHost('awscodecommit.local')).toBe(awsCodeCommit); diff --git a/packages/integration/src/ScmIntegrations.ts b/packages/integration/src/ScmIntegrations.ts index 6a1faaa75b..e0d430d834 100644 --- a/packages/integration/src/ScmIntegrations.ts +++ b/packages/integration/src/ScmIntegrations.ts @@ -28,6 +28,7 @@ import { defaultScmResolveUrl } from './helpers'; import { ScmIntegration, ScmIntegrationsGroup } from './types'; import { ScmIntegrationRegistry } from './registry'; import { GiteaIntegration } from './gitea'; +import { HarnessIntegration } from './harness/HarnessIntegration'; /** * The set of supported integrations. @@ -48,6 +49,7 @@ export interface IntegrationsByType { github: ScmIntegrationsGroup<GithubIntegration>; gitlab: ScmIntegrationsGroup<GitLabIntegration>; gitea: ScmIntegrationsGroup<GiteaIntegration>; + harness: ScmIntegrationsGroup<HarnessIntegration>; } /** @@ -70,6 +72,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry { github: GithubIntegration.factory({ config }), gitlab: GitLabIntegration.factory({ config }), gitea: GiteaIntegration.factory({ config }), + harness: HarnessIntegration.factory({ config }), }); } @@ -120,6 +123,10 @@ export class ScmIntegrations implements ScmIntegrationRegistry { return this.byType.gitea; } + get harness(): ScmIntegrationsGroup<HarnessIntegration> { + return this.byType.harness; + } + list(): ScmIntegration[] { return Object.values(this.byType).flatMap( i => i.list() as ScmIntegration[], diff --git a/packages/integration/src/harness/HarnessIntegration.test.ts b/packages/integration/src/harness/HarnessIntegration.test.ts new file mode 100644 index 0000000000..9014c0bb79 --- /dev/null +++ b/packages/integration/src/harness/HarnessIntegration.test.ts @@ -0,0 +1,128 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { HarnessIntegration } from './HarnessIntegration'; + +describe('HarnessIntegration', () => { + it('has a working factory', () => { + const integrations = HarnessIntegration.factory({ + config: new ConfigReader({ + integrations: { + harness: [ + { + host: 'app.harness.io', + username: 'git', + baseUrl: 'https://app.harness.io/route', + token: '1234', + }, + ], + }, + }), + }); + expect(integrations.list().length).toBe(1); + expect(integrations.list()[0].config.host).toBe('app.harness.io'); + expect(integrations.list()[0].config.baseUrl).toBe( + 'https://app.harness.io/route', + ); + }); + + it('returns the basics', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + expect(integration.type).toBe('harness'); + expect(integration.title).toBe('app.harness.io'); + }); + + describe('resolveUrl', () => { + it('works for valid urls, ignoring line number', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + + expect( + integration.resolveUrl({ + url: 'https://app.harness.io/catalog-info.yaml', + base: 'https://app.harness.io/catalog-info.yaml', + lineNumber: 9, + }), + ).toBe('https://app.harness.io/catalog-info.yaml'); + }); + + it('handles line numbers', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + + expect( + integration.resolveUrl({ + url: '', + base: 'https://app.harness.io/catalog-info.yaml#4', + lineNumber: 9, + }), + ).toBe('https://app.harness.io/catalog-info.yaml#L9'); + }); + }); + + describe('resolves with a relative url', () => { + it('works for valid urls', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + + expect( + integration.resolveUrl({ + url: './skeleton', + base: 'https://app.harness.io/git/plugins/repo/+/refs/heads/master/template.yaml', + }), + ).toBe( + 'https://app.harness.io/git/plugins/repo/+/refs/heads/master/skeleton', + ); + }); + }); + + describe('resolves with an absolute url', () => { + it('works for valid urls', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + + expect( + integration.resolveUrl({ + url: '/catalog-info.yaml', + base: 'https://app.harness.io/git/repo/+/refs/heads/master/', + }), + ).toBe( + 'https://app.harness.io/git/repo/+/refs/heads/master/catalog-info.yaml', + ); + }); + }); + + it('resolve edit URL', () => { + const integration = new HarnessIntegration({ + host: 'app.harness.io', + }); + + expect( + integration.resolveEditUrl( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/edit/refMain/~/all-apis.yaml', + ), + ).toBe( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/edit/all-apis.yaml', + ); + }); +}); diff --git a/packages/integration/src/harness/HarnessIntegration.ts b/packages/integration/src/harness/HarnessIntegration.ts new file mode 100644 index 0000000000..5d1b274149 --- /dev/null +++ b/packages/integration/src/harness/HarnessIntegration.ts @@ -0,0 +1,58 @@ +/* + * 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 { basicIntegrations, defaultScmResolveUrl } from '../helpers'; +import { ScmIntegration, ScmIntegrationsFactory } from '../types'; +import { HarnessIntegrationConfig, readHarnessConfig } from './config'; +import { getHarnessEditContentsUrl } from './core'; + +/** + * A Harness Code based integration. + * + * @public + */ +export class HarnessIntegration implements ScmIntegration { + static factory: ScmIntegrationsFactory<HarnessIntegration> = ({ config }) => { + const configs = config.getOptionalConfigArray('integrations.harness') ?? []; + const harnessConfigs = configs.map(c => readHarnessConfig(c)); + + return basicIntegrations( + harnessConfigs.map(c => new HarnessIntegration(c)), + (harness: HarnessIntegration) => harness.config.host, + ); + }; + + constructor(readonly config: HarnessIntegrationConfig) {} + + get type(): string { + return 'harness'; + } + + get title(): string { + return this.config.host; + } + + resolveUrl(options: { + url: string; + base: string; + lineNumber?: number | undefined; + }): string { + return defaultScmResolveUrl(options); + } + + resolveEditUrl(url: string): string { + return getHarnessEditContentsUrl(this.config, url); + } +} diff --git a/packages/integration/src/harness/config.test.ts b/packages/integration/src/harness/config.test.ts new file mode 100644 index 0000000000..6482e89de8 --- /dev/null +++ b/packages/integration/src/harness/config.test.ts @@ -0,0 +1,108 @@ +/* + * 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 { Config, ConfigReader } from '@backstage/config'; +import { loadConfigSchema } from '@backstage/config-loader'; +import { HarnessIntegrationConfig, readHarnessConfig } from './config'; + +describe('readHarnessConfig', () => { + function buildConfig(data: Partial<HarnessIntegrationConfig>): Config { + return new ConfigReader(data); + } + + async function buildFrontendConfig( + data: Partial<HarnessIntegrationConfig>, + ): Promise<Config> { + const fullSchema = await loadConfigSchema({ + dependencies: ['@backstage/integration'], + }); + const serializedSchema = fullSchema.serialize() as { + schemas: { value: { properties?: { integrations?: object } } }[]; + }; + const schema = await loadConfigSchema({ + serialized: { + ...serializedSchema, // only include schemas that apply to integrations + schemas: serializedSchema.schemas.filter( + s => s.value?.properties?.integrations, + ), + }, + }); + const processed = schema.process( + [{ data: { integrations: { harness: [data] } }, context: 'app' }], + { visibility: ['frontend'] }, + ); + return new ConfigReader((processed[0].data as any).integrations.harness[0]); + } + + it('reads all values', () => { + const output = readHarnessConfig( + buildConfig({ + host: 'a.com', + baseUrl: 'https://a.com/route/api', + username: 'u', + token: 'p', + }), + ); + expect(output).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com/route/api', + username: 'u', + token: 'p', + }); + }); + + it('can create a default value if the API base URL is missing', () => { + const output = readHarnessConfig( + buildConfig({ + host: 'a.com', + }), + ); + expect(output).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com', + username: undefined, + token: undefined, + }); + }); + + it('rejects funky configs', () => { + const valid: any = { + host: 'a.com', + }; + expect(() => readHarnessConfig(buildConfig({ ...valid, host: 2 }))).toThrow( + /host/, + ); + expect(() => + readHarnessConfig(buildConfig({ ...valid, baseUrl: 2 })), + ).toThrow(/baseUrl/); + }); + + it('works on the frontend', async () => { + expect( + readHarnessConfig( + await buildFrontendConfig({ + host: 'a.com', + baseUrl: 'https://a.com/route', + username: 'u', + token: 'p', + }), + ), + ).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com/route', + }); + }); +}); diff --git a/packages/integration/src/harness/config.ts b/packages/integration/src/harness/config.ts new file mode 100644 index 0000000000..75482a9b39 --- /dev/null +++ b/packages/integration/src/harness/config.ts @@ -0,0 +1,78 @@ +/* + * 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 { Config } from '@backstage/config'; +import { trimEnd } from 'lodash'; +import { isValidHost } from '../helpers'; + +/** + * The configuration for a single Gitea integration. + * + * @public + */ +export type HarnessIntegrationConfig = { + /** + * The host of the target that this matches on, e.g. "app.harness.io" + */ + host: string; + /** + * The optional base URL of the Harness code instance. It is assumed that https + * is used and that the base path is "/" on the host. If that is not the + * case set the complete base url to the Harness code instance, e.g. + * "https://harnesscode.website.com/". This is the url that you would open + * in a browser. + */ + baseUrl?: string; + /** + * The username to use for requests to harness code. + */ + username?: string; + + /** + * The password or http token to use for authentication. + */ + token?: string; +}; + +/** + * Parses a location config block for use in HarnessIntegration + * + * @public + */ +export function readHarnessConfig(config: Config): HarnessIntegrationConfig { + const host = config.getString('host'); + let baseUrl = config.getOptionalString('baseUrl'); + const username = config.getOptionalString('username'); + const token = config.getOptionalString('token'); + if (!isValidHost(host)) { + throw new Error( + `Invalid Harness Code integration config, '${host}' is not a valid host`, + ); + } + + if (baseUrl) { + baseUrl = trimEnd(baseUrl, '/'); + } else { + baseUrl = `https://${host}`; + } + + return { + host, + baseUrl, + username, + token, + }; +} diff --git a/packages/integration/src/harness/core.test.ts b/packages/integration/src/harness/core.test.ts new file mode 100644 index 0000000000..6502fd3e48 --- /dev/null +++ b/packages/integration/src/harness/core.test.ts @@ -0,0 +1,95 @@ +/* + * Copyright 2020 The 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 { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { HarnessIntegrationConfig } from './config'; +import { + getHarnessEditContentsUrl, + getHarnessFileContentsUrl, + getHarnessRequestOptions, +} from './core'; + +describe('Harness code core', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + describe('getHarnessFileContentsUrl', () => { + it('can create an url from arguments', () => { + const config: HarnessIntegrationConfig = { + host: 'app.harness.io', + }; + expect( + getHarnessFileContentsUrl( + config, + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + ), + ).toEqual( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain', + ); + }); + }); + + describe('getHarnessEditContentsUrl', () => { + it('can create an url from arguments', () => { + const config: HarnessIntegrationConfig = { + host: 'app.harness.io', + }; + expect( + getHarnessEditContentsUrl( + config, + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/edit/refMain/~/all-apis.yaml', + ), + ).toEqual( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/edit/all-apis.yaml', + ); + }); + }); + + describe('getGerritRequestOptions', () => { + it('adds token header when only a token is specified', () => { + const authRequest: HarnessIntegrationConfig = { + host: 'gerrit.com', + token: 'P', + }; + const anonymousRequest: HarnessIntegrationConfig = { + host: 'gerrit.com', + }; + expect( + (getHarnessRequestOptions(authRequest).headers as any).Authorization, + ).toEqual('Bearer P'); + expect( + getHarnessRequestOptions(anonymousRequest).headers, + ).toBeUndefined(); + }); + + it('adds basic auth when username and token are specified', () => { + const authRequest: HarnessIntegrationConfig = { + host: 'gerrit.com', + username: 'username', + token: 'P', + }; + + const basicAuthentication = `basic ${Buffer.from( + `${authRequest.username}:${authRequest.token}`, + ).toString('base64')}`; + + expect( + (getHarnessRequestOptions(authRequest).headers as any).Authorization, + ).toEqual(basicAuthentication); + }); + }); +}); diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts new file mode 100644 index 0000000000..0c4fb01970 --- /dev/null +++ b/packages/integration/src/harness/core.ts @@ -0,0 +1,135 @@ +/* + * 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 { HarnessIntegrationConfig } from './config'; + +/** + * Given a URL pointing to a file, returns a URL + * for editing the contents of the data. + * + * @remarks + * + * Converts + * from: https://app.harness.io/a/b/src/branchname/path/to/c.yaml + * or: https://app.harness.io/a/b/_edit/branchname/path/to/c.yaml + * + * @param url - A URL pointing to a file + * @param config - The relevant provider config + * @public + */ +export function getHarnessEditContentsUrl( + config: HarnessIntegrationConfig, + url: string, +) { + try { + const baseUrl = config.baseUrl ?? `https://${config.host}`; + const [ + _blank, + _ng, + _account, + accountId, + _module, + _moduleName, + _org, + orgName, + _projects, + projectName, + _repos, + repoName, + _files, + _ref, + _branch, + ...path + ] = url.replace(baseUrl, '').split('/'); + const pathWithoutSlash = path.join('/').replace(/^\//, ''); + return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/edit/${pathWithoutSlash}`; + } catch (e) { + throw new Error(`Incorrect URL: ${url}, ${e}`); + } +} + +/** + * Given a URL pointing to a file, returns an api URL + * for fetching the contents of the data. + * + * @remarks + * + * Converts + * from: https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml + * to: https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain + * + * @param url - A URL pointing to a file + * @param config - The relevant provider config + * @public + */ +export function getHarnessFileContentsUrl( + config: HarnessIntegrationConfig, + url: string, +) { + try { + const baseUrl = config.baseUrl ?? `https://${config.host}`; + const [ + _blank, + _ng, + _account, + accountId, + _module, + _moduleName, + _org, + orgName, + _projects, + projectName, + _repos, + repoName, + _files, + ref, + _branch, + ...path + ] = url.replace(baseUrl, '').split('/'); + const pathWithoutSlash = path.join('/').replace(/^\//, ''); + return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/content/${pathWithoutSlash}?routingId=${accountId}&include_commit=false&ref=${ref}`; + } catch (e) { + throw new Error(`Incorrect URL: ${url}, ${e}`); + } +} + +/** + * Return request headers for a Harness Code provider. + * + * @param config - A Harness Code provider config + * @public + */ +export function getHarnessRequestOptions(config: HarnessIntegrationConfig): { + headers?: Record<string, string>; +} { + const headers: Record<string, string> = {}; + const { username, token } = config; + + if (!token) { + return headers; + } + + if (username) { + headers.Authorization = `basic ${Buffer.from( + `${username}:${token}`, + ).toString('base64')}`; + } else { + headers.Authorization = `Bearer ${token}`; + } + + return { + headers, + }; +} diff --git a/packages/integration/src/harness/index.ts b/packages/integration/src/harness/index.ts new file mode 100644 index 0000000000..264df657f0 --- /dev/null +++ b/packages/integration/src/harness/index.ts @@ -0,0 +1,19 @@ +/* + * 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. + */ +export { HarnessIntegration } from './HarnessIntegration'; +export { getHarnessRequestOptions, getHarnessFileContentsUrl } from './core'; +export { readHarnessConfig } from './config'; +export type { HarnessIntegrationConfig } from './config'; diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index b20477ee6a..32c573abef 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -31,6 +31,7 @@ export * from './gitea'; export * from './github'; export * from './gitlab'; export * from './googleGcs'; +export * from './harness'; export { defaultScmResolveUrl } from './helpers'; export { ScmIntegrations } from './ScmIntegrations'; export type { IntegrationsByType } from './ScmIntegrations'; diff --git a/packages/integration/src/registry.ts b/packages/integration/src/registry.ts index 00706acc1c..7e4b34cc34 100644 --- a/packages/integration/src/registry.ts +++ b/packages/integration/src/registry.ts @@ -25,6 +25,7 @@ import { GerritIntegration } from './gerrit/GerritIntegration'; import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; import { GiteaIntegration } from './gitea/GiteaIntegration'; +import { HarnessIntegration } from './harness/HarnessIntegration'; /** * Holds all registered SCM integrations, of all types. @@ -46,6 +47,7 @@ export interface ScmIntegrationRegistry github: ScmIntegrationsGroup<GithubIntegration>; gitlab: ScmIntegrationsGroup<GitLabIntegration>; gitea: ScmIntegrationsGroup<GiteaIntegration>; + harness: ScmIntegrationsGroup<HarnessIntegration>; /** * Resolves an absolute or relative URL in relation to a base URL. * From 4750bf66223a0befc33ab8226c7036a5f0d2e898 Mon Sep 17 00:00:00 2001 From: Calvin Lee <calvin.lee@harness.io> Date: Sun, 21 Apr 2024 19:39:59 -0600 Subject: [PATCH 152/234] Update .changeset/empty-beers-relax.md Co-authored-by: Himanshu Mishra <himanshu@orkohunter.net> Signed-off-by: Calvin Lee <calvin.lee@harness.io> --- .changeset/empty-beers-relax.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/empty-beers-relax.md b/.changeset/empty-beers-relax.md index 526f88c1c7..a285b9e7b4 100644 --- a/.changeset/empty-beers-relax.md +++ b/.changeset/empty-beers-relax.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -This patch adds HarnessURLReader to the available classes. It currently only reads single files via Harness codes public repo api. +This patch adds HarnessURLReader. It only supports readUrl for now. readTree and search will be implemented next. From d422716946e6bd1ca2072f21b510add272eb0b59 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Sun, 21 Apr 2024 20:31:45 -0600 Subject: [PATCH 153/234] integration support for harness p2-comments Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- docs/integrations/harness/locations.md | 12 +++---- .../src/reading/HarnessUrlReader.ts | 2 +- packages/integration/api-report.md | 5 ++- .../src/harness/HarnessIntegration.test.ts | 5 --- .../integration/src/harness/config.test.ts | 11 ++----- packages/integration/src/harness/config.ts | 32 ++++++------------- packages/integration/src/harness/core.test.ts | 12 +++---- packages/integration/src/harness/core.ts | 17 ++++------ 8 files changed, 31 insertions(+), 65 deletions(-) diff --git a/docs/integrations/harness/locations.md b/docs/integrations/harness/locations.md index 8911d7918c..a3d0e2cfd4 100644 --- a/docs/integrations/harness/locations.md +++ b/docs/integrations/harness/locations.md @@ -20,14 +20,14 @@ integrations: harness: - host: app.harness.io token: ${HARNESS_CODE_BEARER_TOKEN} + apiKey: ${HARNESS_CODE_APIKEY} ``` -Directly under the `harnessCode` key is a list of provider configurations, where you -can list the Gitea instances you want to be able to fetch -data from. Each entry is a structure with up to four elements: +Directly under the `harness` key is a list of provider configurations, where you +can list the Harness instances you want to be able to fetch + +check out https://developer.harness.io/docs/platform/automation/api/add-and-manage-api-keys/ for more information - `host`: The host of the Harness Code instance that you want to match on. -- `baseUrl` (optional): Needed if the Harness Code instance is not reachable at - the base of the `host` option (e.g. `https://app.harness.io`). This is the address that you would open in a browser. -- `username` (optional): The gitea username to use in API requests. - `token` (optional): The password or api token to authenticate with. +- `apiKey` (optional): The apiKey to authenticate with. diff --git a/packages/backend-common/src/reading/HarnessUrlReader.ts b/packages/backend-common/src/reading/HarnessUrlReader.ts index fd01e4f8a2..95114ae9d6 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.ts @@ -118,7 +118,7 @@ export class HarnessUrlReader implements UrlReader { toString() { const { host } = this.integration.config; return `harness{host=${host},authed=${Boolean( - this.integration.config.token, + this.integration.config.token || this.integration.config.apiKey, )}}`; } } diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index a693c1314c..b40b66b0b6 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -552,7 +552,7 @@ export type GiteaIntegrationConfig = { host: string; baseUrl?: string; username?: string; - token?: string; + password?: string; }; // @public @@ -711,8 +711,7 @@ export class HarnessIntegration implements ScmIntegration { // @public export type HarnessIntegrationConfig = { host: string; - baseUrl?: string; - username?: string; + apiKey?: string; token?: string; }; diff --git a/packages/integration/src/harness/HarnessIntegration.test.ts b/packages/integration/src/harness/HarnessIntegration.test.ts index 9014c0bb79..2d85204f16 100644 --- a/packages/integration/src/harness/HarnessIntegration.test.ts +++ b/packages/integration/src/harness/HarnessIntegration.test.ts @@ -25,8 +25,6 @@ describe('HarnessIntegration', () => { harness: [ { host: 'app.harness.io', - username: 'git', - baseUrl: 'https://app.harness.io/route', token: '1234', }, ], @@ -35,9 +33,6 @@ describe('HarnessIntegration', () => { }); expect(integrations.list().length).toBe(1); expect(integrations.list()[0].config.host).toBe('app.harness.io'); - expect(integrations.list()[0].config.baseUrl).toBe( - 'https://app.harness.io/route', - ); }); it('returns the basics', () => { diff --git a/packages/integration/src/harness/config.test.ts b/packages/integration/src/harness/config.test.ts index 6482e89de8..4f6d8496e3 100644 --- a/packages/integration/src/harness/config.test.ts +++ b/packages/integration/src/harness/config.test.ts @@ -51,16 +51,14 @@ describe('readHarnessConfig', () => { const output = readHarnessConfig( buildConfig({ host: 'a.com', - baseUrl: 'https://a.com/route/api', - username: 'u', token: 'p', + apiKey: 'a', }), ); expect(output).toEqual({ host: 'a.com', - baseUrl: 'https://a.com/route/api', - username: 'u', token: 'p', + apiKey: 'a', }); }); @@ -72,8 +70,6 @@ describe('readHarnessConfig', () => { ); expect(output).toEqual({ host: 'a.com', - baseUrl: 'https://a.com', - username: undefined, token: undefined, }); }); @@ -95,14 +91,11 @@ describe('readHarnessConfig', () => { readHarnessConfig( await buildFrontendConfig({ host: 'a.com', - baseUrl: 'https://a.com/route', - username: 'u', token: 'p', }), ), ).toEqual({ host: 'a.com', - baseUrl: 'https://a.com/route', }); }); }); diff --git a/packages/integration/src/harness/config.ts b/packages/integration/src/harness/config.ts index 75482a9b39..748cd141c5 100644 --- a/packages/integration/src/harness/config.ts +++ b/packages/integration/src/harness/config.ts @@ -15,11 +15,10 @@ */ import { Config } from '@backstage/config'; -import { trimEnd } from 'lodash'; import { isValidHost } from '../helpers'; /** - * The configuration for a single Gitea integration. + * The configuration for a single Harness integration. * * @public */ @@ -28,23 +27,14 @@ export type HarnessIntegrationConfig = { * The host of the target that this matches on, e.g. "app.harness.io" */ host: string; - /** - * The optional base URL of the Harness code instance. It is assumed that https - * is used and that the base path is "/" on the host. If that is not the - * case set the complete base url to the Harness code instance, e.g. - * "https://harnesscode.website.com/". This is the url that you would open - * in a browser. - */ - baseUrl?: string; - /** - * The username to use for requests to harness code. - */ - username?: string; - /** * The password or http token to use for authentication. */ token?: string; + /** + * The API key to use for authentication. + */ + apiKey?: string; }; /** @@ -55,24 +45,20 @@ export type HarnessIntegrationConfig = { export function readHarnessConfig(config: Config): HarnessIntegrationConfig { const host = config.getString('host'); let baseUrl = config.getOptionalString('baseUrl'); - const username = config.getOptionalString('username'); const token = config.getOptionalString('token'); + const apiKey = config.getOptionalString('apiKey'); + if (!isValidHost(host)) { throw new Error( `Invalid Harness Code integration config, '${host}' is not a valid host`, ); } - if (baseUrl) { - baseUrl = trimEnd(baseUrl, '/'); - } else { - baseUrl = `https://${host}`; - } + baseUrl = `https://${host}`; return { host, - baseUrl, - username, + apiKey, token, }; } diff --git a/packages/integration/src/harness/core.test.ts b/packages/integration/src/harness/core.test.ts index 6502fd3e48..2a214cc06f 100644 --- a/packages/integration/src/harness/core.test.ts +++ b/packages/integration/src/harness/core.test.ts @@ -76,20 +76,16 @@ describe('Harness code core', () => { ).toBeUndefined(); }); - it('adds basic auth when username and token are specified', () => { + it('adds basic auth when apikey and token are specified', () => { const authRequest: HarnessIntegrationConfig = { host: 'gerrit.com', - username: 'username', token: 'P', + apiKey: 'a', }; - const basicAuthentication = `basic ${Buffer.from( - `${authRequest.username}:${authRequest.token}`, - ).toString('base64')}`; - expect( - (getHarnessRequestOptions(authRequest).headers as any).Authorization, - ).toEqual(basicAuthentication); + (getHarnessRequestOptions(authRequest).headers as any)['x-api-key'], + ).toEqual('a'); }); }); }); diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts index 0c4fb01970..324a57071c 100644 --- a/packages/integration/src/harness/core.ts +++ b/packages/integration/src/harness/core.ts @@ -34,7 +34,7 @@ export function getHarnessEditContentsUrl( url: string, ) { try { - const baseUrl = config.baseUrl ?? `https://${config.host}`; + const baseUrl = `https://${config.host}`; const [ _blank, _ng, @@ -61,9 +61,8 @@ export function getHarnessEditContentsUrl( } /** - * Given a URL pointing to a file, returns an api URL - * for fetching the contents of the data. - * + * Given a file path URL, + * it returns an API URL which returns the contents of the file. * @remarks * * Converts @@ -79,7 +78,7 @@ export function getHarnessFileContentsUrl( url: string, ) { try { - const baseUrl = config.baseUrl ?? `https://${config.host}`; + const baseUrl = `https://${config.host}`; const [ _blank, _ng, @@ -115,16 +114,14 @@ export function getHarnessRequestOptions(config: HarnessIntegrationConfig): { headers?: Record<string, string>; } { const headers: Record<string, string> = {}; - const { username, token } = config; + const { token, apiKey } = config; if (!token) { return headers; } - if (username) { - headers.Authorization = `basic ${Buffer.from( - `${username}:${token}`, - ).toString('base64')}`; + if (apiKey) { + headers['x-api-key'] = apiKey; } else { headers.Authorization = `Bearer ${token}`; } From 6a0e918dc69d6e46d350485c4fea14c54cea3847 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 23 Apr 2024 00:23:33 -0600 Subject: [PATCH 154/234] integration support for harness p3-comments Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- .../{HarnessCodeUrlReader.test.ts => HarnessUrlReader.test.ts} | 2 +- packages/integration/src/harness/core.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename packages/backend-common/src/reading/{HarnessCodeUrlReader.test.ts => HarnessUrlReader.test.ts} (98%) diff --git a/packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts similarity index 98% rename from packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts rename to packages/backend-common/src/reading/HarnessUrlReader.test.ts index 3e8b70a6aa..18c523b959 100644 --- a/packages/backend-common/src/reading/HarnessCodeUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -60,7 +60,7 @@ const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { describe('HarnessUrlReader', () => { const worker = setupServer(); setupRequestMockHandlers(worker); - + beforeAll(() => worker.listen({ onUnhandledRequest: 'bypass' })); afterAll(() => { jest.clearAllMocks(); }); diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts index 324a57071c..6de8039dd5 100644 --- a/packages/integration/src/harness/core.ts +++ b/packages/integration/src/harness/core.ts @@ -122,7 +122,7 @@ export function getHarnessRequestOptions(config: HarnessIntegrationConfig): { if (apiKey) { headers['x-api-key'] = apiKey; - } else { + } else if (token) { headers.Authorization = `Bearer ${token}`; } From ed8b5324dad91129efa850a42f2d27b77b340806 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 23 Apr 2024 16:41:40 -0600 Subject: [PATCH 155/234] integration support for harness p4-fixed test Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- .../src/reading/HarnessUrlReader.test.ts | 136 +++++++++--------- packages/integration/src/harness/core.ts | 8 +- 2 files changed, 71 insertions(+), 73 deletions(-) diff --git a/packages/backend-common/src/reading/HarnessUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts index 18c523b959..4d43cebaec 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -25,7 +25,6 @@ import { UrlReaderPredicateTuple } from './types'; import { DefaultReadTreeResponseFactory } from './tree'; import getRawBody from 'raw-body'; import { HarnessUrlReader } from './HarnessUrlReader'; -import { NotFoundError } from '@backstage/errors'; const treeResponseFactory = DefaultReadTreeResponseFactory.create({ config: new ConfigReader({}), @@ -44,6 +43,7 @@ const harnessProcessor = new HarnessUrlReader( readHarnessConfig( new ConfigReader({ host: 'app.harness.io', + token: 'p', }), ), ), @@ -56,9 +56,60 @@ const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { treeResponseFactory, }); }; +const responseBuffer = Buffer.from('Apache License'); +const harnessApiResponse = (content: any) => { + return JSON.stringify({ + content: { + data: Buffer.from(content).toString('base64'), + encoding: 'base64', + }, + }); +}; + +const handlers = [ + rest.get( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/all-apis.yaml', + (req, res, ctx) => { + return res(ctx.status(500), ctx.json({ message: 'Error!!!' })); + }, + ), + rest.get( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/404error.yaml', + (req, res, ctx) => { + return res(ctx.status(404), ctx.json({ message: 'File not found.' })); + }, + ), + rest.get( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/stream.TXT', + (req, res, ctx) => { + return res( + ctx.status(200), + ctx.body(harnessApiResponse(responseBuffer.toString())), + ); + }, + ), + + rest.get( + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/buffer.TXT', + (req, res, ctx) => { + return res( + ctx.status(200), + ctx.body(harnessApiResponse(responseBuffer.toString())), + ); + }, + ), + rest.post('/api/login', (req, res, ctx) => { + const { username } = req.body; + + if (username === 'admin') { + return res(ctx.status(200), ctx.json({ token: 'fake-token' })); + } + return res(ctx.status(403), ctx.json({ message: 'Access Denied' })); + }), +]; describe('HarnessUrlReader', () => { - const worker = setupServer(); + const worker = setupServer(...handlers); setupRequestMockHandlers(worker); beforeAll(() => worker.listen({ onUnhandledRequest: 'bypass' })); afterAll(() => { @@ -107,97 +158,40 @@ describe('HarnessUrlReader', () => { }); }); - describe('readUrl', () => { - const responseBuffer = Buffer.from('Apache License'); - const harnessApiResponse = (content: any) => { - return JSON.stringify({ - encoding: 'base64', - content: Buffer.from(content).toString('base64'), - }); - }; - - it.skip('should be able to read file contents as buffer', async () => { - worker.use( - rest.get( - 'https://app.harness.io/api/v1/repos/owner/project/contents/LICENSE', - (req, res, ctx) => { - // Test utils prefers matching URL directly but it is part of Gitea's API - if (req.url.searchParams.get('ref') === 'branch2') { - return res( - ctx.status(200), - ctx.body(harnessApiResponse(responseBuffer.toString())), - ); - } - - return res(ctx.status(500)); - }, - ), - ); - + describe('readUrl part 1', () => { + it('should be able to read file contents as buffer', async () => { const result = await harnessProcessor.readUrl( - 'https://app.harness.io/owner/project/src/branch/branch2/LICENSE', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/buffer.TXT', ); const buffer = await result.buffer(); expect(buffer.toString()).toBe(responseBuffer.toString()); }); - it.skip('should be able to read file contents as stream', async () => { - worker.use( - rest.get( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/LICENSE.txt', - (req, res, ctx) => { - if (req.url.searchParams.get('ref') === 'refMain') { - return res( - ctx.status(200), - ctx.body(harnessApiResponse(responseBuffer.toString())), - ); - } - - return res(ctx.status(500)); - }, - ), - ); - + it('should be able to read file contents as stream', async () => { const result = await harnessProcessor.readUrl( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/LICENSE.TXT', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/stream.TXT', ); const fromStream = await getRawBody(result.stream!()); expect(fromStream.toString()).toBe(responseBuffer.toString()); }); - it.skip('should raise NotFoundError on 404.', async () => { - worker.use( - rest.get( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', - (_, res, ctx) => { - return res(ctx.status(404, 'File not found.')); - }, - ), - ); - + it('should raise NotFoundError on 404.', async () => { await expect( harnessProcessor.readUrl( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml', ), - ).rejects.toThrow(NotFoundError); + ).rejects.toThrow( + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/404error.yaml?routingId=accountId&include_commit=false&ref=refMain, 404 Not Found', + ); }); - it.skip('should throw an error on non 404 errors.', async () => { - worker.use( - rest.get( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', - (_, res, ctx) => { - return res(ctx.status(500, 'Error!!!')); - }, - ), - ); - + it('should throw an error on non 404 errors.', async () => { await expect( harnessProcessor.readUrl( 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', ), ).rejects.toThrow( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain, 500 Error!!!', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain, 500 Internal Server Error', ); }); }); diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts index 6de8039dd5..f762cebb56 100644 --- a/packages/integration/src/harness/core.ts +++ b/packages/integration/src/harness/core.ts @@ -93,12 +93,16 @@ export function getHarnessFileContentsUrl( _repos, repoName, _files, - ref, + _ref, _branch, ...path ] = url.replace(baseUrl, '').split('/'); + const urlParts = url.replace(baseUrl, '').split('/'); + const refAndPath = urlParts.slice(13); + const refIndex = refAndPath.findIndex(item => item === '~'); + const refString = refAndPath.slice(0, refIndex); const pathWithoutSlash = path.join('/').replace(/^\//, ''); - return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/content/${pathWithoutSlash}?routingId=${accountId}&include_commit=false&ref=${ref}`; + return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/content/${pathWithoutSlash}?routingId=${accountId}&include_commit=false&ref=${refString}`; } catch (e) { throw new Error(`Incorrect URL: ${url}, ${e}`); } From 0cf356671a830937d09872a933edcbc80e676841 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 23 Apr 2024 16:43:47 -0600 Subject: [PATCH 156/234] integration support for harness p4-fixed test Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- .../backend-common/src/reading/HarnessUrlReader.test.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/packages/backend-common/src/reading/HarnessUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts index 4d43cebaec..5e1d2d9b2e 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -98,14 +98,6 @@ const handlers = [ ); }, ), - rest.post('/api/login', (req, res, ctx) => { - const { username } = req.body; - - if (username === 'admin') { - return res(ctx.status(200), ctx.json({ token: 'fake-token' })); - } - return res(ctx.status(403), ctx.json({ message: 'Access Denied' })); - }), ]; describe('HarnessUrlReader', () => { From 1cfa4aa35ea08f16dc7635e87a608c6e8c41851c Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 23 Apr 2024 21:13:04 -0600 Subject: [PATCH 157/234] integration support for harness p5-fixed lint Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- packages/backend-common/api-report.md | 3 +-- .../src/reading/HarnessUrlReader.test.ts | 8 ++++---- packages/integration/config.d.ts | 10 ++-------- packages/integration/src/harness/config.test.ts | 3 --- packages/integration/src/harness/config.ts | 3 --- 5 files changed, 7 insertions(+), 20 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 37bfda9861..33145fc47f 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -551,8 +551,7 @@ export class HarnessUrlReader implements UrlReader { // @public export type HarnessIntegrationConfig = { host: string; - baseUrl?: string; - username?: string; + apiKey?: string; token?: string; }; diff --git a/packages/backend-common/src/reading/HarnessUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts index 5e1d2d9b2e..d6cf9ab018 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -69,19 +69,19 @@ const harnessApiResponse = (content: any) => { const handlers = [ rest.get( 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/all-apis.yaml', - (req, res, ctx) => { + (_req, res, ctx) => { return res(ctx.status(500), ctx.json({ message: 'Error!!!' })); }, ), rest.get( 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/404error.yaml', - (req, res, ctx) => { + (_req, res, ctx) => { return res(ctx.status(404), ctx.json({ message: 'File not found.' })); }, ), rest.get( 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/stream.TXT', - (req, res, ctx) => { + (_req, res, ctx) => { return res( ctx.status(200), ctx.body(harnessApiResponse(responseBuffer.toString())), @@ -91,7 +91,7 @@ const handlers = [ rest.get( 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/buffer.TXT', - (req, res, ctx) => { + (_req, res, ctx) => { return res( ctx.status(200), ctx.body(harnessApiResponse(responseBuffer.toString())), diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 01f1f44d73..58dd9153c4 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -353,16 +353,10 @@ export interface Config { */ host: string; /** - * The base url for the Gitea instance. - * @visibility frontend - */ - baseUrl?: string; - - /** - * The username to use for authenticated requests. + * The apikey to use for authenticated requests. * @visibility secret */ - username?: string; + apiKey?: string; /** * Harness Code token used to authenticate requests. This can be either a generated access token. * @visibility secret diff --git a/packages/integration/src/harness/config.test.ts b/packages/integration/src/harness/config.test.ts index 4f6d8496e3..93be1fcc32 100644 --- a/packages/integration/src/harness/config.test.ts +++ b/packages/integration/src/harness/config.test.ts @@ -81,9 +81,6 @@ describe('readHarnessConfig', () => { expect(() => readHarnessConfig(buildConfig({ ...valid, host: 2 }))).toThrow( /host/, ); - expect(() => - readHarnessConfig(buildConfig({ ...valid, baseUrl: 2 })), - ).toThrow(/baseUrl/); }); it('works on the frontend', async () => { diff --git a/packages/integration/src/harness/config.ts b/packages/integration/src/harness/config.ts index 748cd141c5..2f75915567 100644 --- a/packages/integration/src/harness/config.ts +++ b/packages/integration/src/harness/config.ts @@ -44,7 +44,6 @@ export type HarnessIntegrationConfig = { */ export function readHarnessConfig(config: Config): HarnessIntegrationConfig { const host = config.getString('host'); - let baseUrl = config.getOptionalString('baseUrl'); const token = config.getOptionalString('token'); const apiKey = config.getOptionalString('apiKey'); @@ -54,8 +53,6 @@ export function readHarnessConfig(config: Config): HarnessIntegrationConfig { ); } - baseUrl = `https://${host}`; - return { host, apiKey, From 2bf97f0aa15b1940fb2a485584ae14c773cf7591 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 23 Apr 2024 21:31:44 -0600 Subject: [PATCH 158/234] integration support for harness p5-fixed check Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- packages/integration/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/integration/package.json b/packages/integration/package.json index bbd0eecbe7..b57b94f982 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -50,6 +50,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/config-loader": "workspace:^", + "@backstage/test-utils": "workspace:^", "@types/luxon": "^3.0.0", "msw": "^1.0.0" }, diff --git a/yarn.lock b/yarn.lock index 3cf5a79175..60b03d4399 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4368,6 +4368,7 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/config-loader": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/test-utils": "workspace:^" "@octokit/auth-app": ^4.0.0 "@octokit/rest": ^19.0.3 "@types/luxon": ^3.0.0 From 049a69f223e9c986312750e55c4fab028398fc25 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 23 Apr 2024 21:46:58 -0600 Subject: [PATCH 159/234] integration support for harness p5-fixed api report Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- packages/integration/api-report.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index b40b66b0b6..2029dac600 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -520,9 +520,7 @@ export function getHarnessFileContentsUrl( ): string; // @public -export function getHarnessRequestOptions( - config: HarnessIntegrationConfig, -): { +export function getHarnessRequestOptions(config: HarnessIntegrationConfig): { headers?: Record<string, string>; }; @@ -711,8 +709,8 @@ export class HarnessIntegration implements ScmIntegration { // @public export type HarnessIntegrationConfig = { host: string; - apiKey?: string; token?: string; + apiKey?: string; }; // @public @@ -883,9 +881,7 @@ export function readGoogleGcsIntegrationConfig( ): GoogleGcsIntegrationConfig; // @public -export function readHarnessConfig( - config: Config, -): HarnessIntegrationConfig; +export function readHarnessConfig(config: Config): HarnessIntegrationConfig; // @public @deprecated (undocumented) export const replaceGitHubUrlType: typeof replaceGithubUrlType; From 7362e25a24086d47c28c98029cb4ff982af102f5 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 23 Apr 2024 21:59:08 -0600 Subject: [PATCH 160/234] integration support for harness p5-fixed api report Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- packages/backend-common/api-report.md | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 33145fc47f..1ae7a9dceb 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -531,6 +531,8 @@ export class GitlabUrlReader implements UrlReader { toString(): string; } +// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver +// // @public export class HarnessUrlReader implements UrlReader { constructor(integration: HarnessIntegration); @@ -548,13 +550,6 @@ export class HarnessUrlReader implements UrlReader { toString(): string; } -// @public -export type HarnessIntegrationConfig = { - host: string; - apiKey?: string; - token?: string; -}; - // @public export const HostDiscovery: typeof HostDiscovery_2; From a102a02c27a380513f04f8807c7eea2f5718998b Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 23 Apr 2024 22:42:20 -0600 Subject: [PATCH 161/234] integration support for harness p5-fixed api report Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- packages/backend-common/api-report.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 1ae7a9dceb..e815973fb1 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -531,8 +531,6 @@ export class GitlabUrlReader implements UrlReader { toString(): string; } -// Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver -// // @public export class HarnessUrlReader implements UrlReader { constructor(integration: HarnessIntegration); From a25b566c6453d2ad5f04e3b4a31b896a3864c797 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 23 Apr 2024 22:57:03 -0600 Subject: [PATCH 162/234] integration support for harness p5-fixed api report Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- packages/backend-common/src/reading/HarnessUrlReader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-common/src/reading/HarnessUrlReader.ts b/packages/backend-common/src/reading/HarnessUrlReader.ts index 95114ae9d6..55854d102f 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.ts @@ -36,7 +36,7 @@ import { import { Readable } from 'stream'; /** - * Implements a {@link UrlReader} for the Harness code v1 api. + * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for the Harness code v1 api. * * @public */ From 645580361b68f525c227c9c2f5fcedc3cb89c3f2 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 23 Apr 2024 23:06:29 -0600 Subject: [PATCH 163/234] integration support for harness p5-fixed api microsite Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- microsite/sidebars.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 8291549153..eadcbbf806 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -239,9 +239,9 @@ "items": ["integrations/gitea/locations"] }, { - "type": "subcategory", + "type": "category", "label": "Harness", - "ids": ["integrations/harness/locations"] + "items": ["integrations/harness/locations"] }, { "type": "category", From d01f3ab8901274470ff160e3bca38d3b22372f96 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Thu, 25 Apr 2024 11:41:13 -0600 Subject: [PATCH 164/234] integration support for harness p5-fixed api microsite-fake Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- packages/backend-common/src/reading/HarnessUrlReader.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend-common/src/reading/HarnessUrlReader.ts b/packages/backend-common/src/reading/HarnessUrlReader.ts index 55854d102f..7d65e674b8 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.ts @@ -38,6 +38,7 @@ import { Readable } from 'stream'; /** * Implements a {@link @backstage/backend-plugin-api#UrlReaderService} for the Harness code v1 api. * + * * @public */ export class HarnessUrlReader implements UrlReader { From 9093f35e8aa1a99c1d7620a2ca3da6d19deeeefb Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Mon, 29 Apr 2024 12:41:19 -0600 Subject: [PATCH 165/234] integration support for harness p5-fixed api microsite-fix content api Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- .../src/reading/HarnessUrlReader.test.ts | 19 +++++++------------ .../src/reading/HarnessUrlReader.ts | 10 +++++----- packages/integration/src/harness/core.test.ts | 2 +- packages/integration/src/harness/core.ts | 2 +- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/packages/backend-common/src/reading/HarnessUrlReader.test.ts b/packages/backend-common/src/reading/HarnessUrlReader.test.ts index d6cf9ab018..bb09baa140 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.test.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.test.ts @@ -58,29 +58,24 @@ const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { }; const responseBuffer = Buffer.from('Apache License'); const harnessApiResponse = (content: any) => { - return JSON.stringify({ - content: { - data: Buffer.from(content).toString('base64'), - encoding: 'base64', - }, - }); + return content; }; const handlers = [ rest.get( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/all-apis.yaml', + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/all-apis.yaml', (_req, res, ctx) => { return res(ctx.status(500), ctx.json({ message: 'Error!!!' })); }, ), rest.get( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/404error.yaml', + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/404error.yaml', (_req, res, ctx) => { return res(ctx.status(404), ctx.json({ message: 'File not found.' })); }, ), rest.get( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/stream.TXT', + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/stream.TXT', (_req, res, ctx) => { return res( ctx.status(200), @@ -90,7 +85,7 @@ const handlers = [ ), rest.get( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/content/buffer.TXT', + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/:path+/raw/buffer.TXT', (_req, res, ctx) => { return res( ctx.status(200), @@ -173,7 +168,7 @@ describe('HarnessUrlReader', () => { 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml', ), ).rejects.toThrow( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/404error.yaml?routingId=accountId&include_commit=false&ref=refMain, 404 Not Found', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/404error.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/raw/404error.yaml?routingId=accountId&git_ref=refMain, 404 Not Found', ); }); @@ -183,7 +178,7 @@ describe('HarnessUrlReader', () => { 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', ), ).rejects.toThrow( - 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain, 500 Internal Server Error', + 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml x https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/raw/all-apis.yaml?routingId=accountId&git_ref=refMain, 500 Internal Server Error', ); }); }); diff --git a/packages/backend-common/src/reading/HarnessUrlReader.ts b/packages/backend-common/src/reading/HarnessUrlReader.ts index 7d65e674b8..09291c5176 100644 --- a/packages/backend-common/src/reading/HarnessUrlReader.ts +++ b/packages/backend-common/src/reading/HarnessUrlReader.ts @@ -79,18 +79,18 @@ export class HarnessUrlReader implements UrlReader { } if (response.ok) { - // Harness Code returns an object with the file contents encoded, not the file itself - const jsonResponse = await response.json(); - if (jsonResponse?.content?.encoding === 'base64') { + // Harness Code returns the raw content object + const jsonResponse = { data: response.body }; + if (jsonResponse) { return ReadUrlResponseFactory.fromReadable( - Readable.from(Buffer.from(jsonResponse?.content?.data, 'base64')), + Readable.from(jsonResponse.data), { etag: response.headers.get('ETag') ?? undefined, }, ); } - throw new Error(`Unknown encoding: ${jsonResponse?.content?.encoding}`); + throw new Error(`Unknown json: ${jsonResponse}`); } const message = `${url} x ${blobUrl}, ${response.status} ${response.statusText}`; diff --git a/packages/integration/src/harness/core.test.ts b/packages/integration/src/harness/core.test.ts index 2a214cc06f..abfbb5d4c2 100644 --- a/packages/integration/src/harness/core.test.ts +++ b/packages/integration/src/harness/core.test.ts @@ -38,7 +38,7 @@ describe('Harness code core', () => { 'https://app.harness.io/ng/account/accountId/module/code/orgs/orgName/projects/projName/repos/repoName/files/refMain/~/all-apis.yaml', ), ).toEqual( - 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/content/all-apis.yaml?routingId=accountId&include_commit=false&ref=refMain', + 'https://app.harness.io/gateway/code/api/v1/repos/accountId/orgName/projName/repoName/+/raw/all-apis.yaml?routingId=accountId&git_ref=refMain', ); }); }); diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts index f762cebb56..8eba850ec2 100644 --- a/packages/integration/src/harness/core.ts +++ b/packages/integration/src/harness/core.ts @@ -102,7 +102,7 @@ export function getHarnessFileContentsUrl( const refIndex = refAndPath.findIndex(item => item === '~'); const refString = refAndPath.slice(0, refIndex); const pathWithoutSlash = path.join('/').replace(/^\//, ''); - return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/content/${pathWithoutSlash}?routingId=${accountId}&include_commit=false&ref=${refString}`; + return `${baseUrl}/gateway/code/api/v1/repos/${accountId}/${orgName}/${projectName}/${repoName}/+/raw/${pathWithoutSlash}?routingId=${accountId}&git_ref=${refString}`; } catch (e) { throw new Error(`Incorrect URL: ${url}, ${e}`); } From eea850a3a32101b73ce43c447f026dc14ba71f4e Mon Sep 17 00:00:00 2001 From: bnechyporenko <bnechyporenko@bol.com> Date: Tue, 30 Apr 2024 08:44:15 +0200 Subject: [PATCH 166/234] Capturing more event clicks for scaffolder Signed-off-by: bnechyporenko <bnechyporenko@bol.com> --- plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx index 33e0f5a2a0..5fbda65957 100644 --- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx +++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx @@ -182,7 +182,6 @@ export const Stepper = (stepperProps: StepperProps) => { setActiveStep(prevActiveStep => { const stepNum = prevActiveStep + 1; analytics.captureEvent('click', `Next Step (${stepNum})`); - analytics.captureEvent('click', `Next Step (${stepNum})`); return stepNum; }); } From 2444a50579d3b72dcecead6f8df18da5daffbe44 Mon Sep 17 00:00:00 2001 From: bnechyporenko <bnechyporenko@bol.com> Date: Tue, 30 Apr 2024 09:07:46 +0200 Subject: [PATCH 167/234] Capturing more event clicks for scaffolder Signed-off-by: bnechyporenko <bnechyporenko@bol.com> --- .../TemplateWizardPage.test.tsx | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.test.tsx index 5f112cf37d..269ff81271 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.test.tsx @@ -129,30 +129,19 @@ describe('TemplateWizardPage', () => { fireEvent.click(await findByRole('button', { name: 'Create' })); }); - // The "Next Step" button should have fired few events + // The "Next Step" button should have fired an event expect(analyticsMock.getEvents()[0]).toMatchObject({ action: 'click', subject: 'Next Step (1)', context: { entityRef: 'template:default/test' }, }); + // And the "Create" button should have fired an event expect(analyticsMock.getEvents()[1]).toMatchObject({ - action: 'click', - subject: 'Next Step (1)', - context: { entityRef: 'template:default/test' }, - }); - - // And the "Create" button should have fired few event - expect(analyticsMock.getEvents()[2]).toMatchObject({ action: 'create', subject: 'expected-name', context: { entityRef: 'template:default/test' }, - }); - - expect(analyticsMock.getEvents()[3]).toMatchObject({ - action: 'click', - subject: 'Create', - context: { entityRef: 'template:default/test' }, + value: 120, }); }); describe('scaffolder page context menu', () => { From 8786b93ef22f96255fcf5215f19d266f6298c63a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Tue, 30 Apr 2024 09:31:44 +0200 Subject: [PATCH 168/234] add back ddl limiter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- .../backend-common/src/database/connectors/mysql.ts | 8 ++++++-- .../src/database/connectors/postgres.ts | 12 +++++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/backend-common/src/database/connectors/mysql.ts b/packages/backend-common/src/database/connectors/mysql.ts index 25594d5021..1ad8b0b3be 100644 --- a/packages/backend-common/src/database/connectors/mysql.ts +++ b/packages/backend-common/src/database/connectors/mysql.ts @@ -23,11 +23,15 @@ import { InputError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; +import limiterFactory from 'p-limit'; import yn from 'yn'; import { Connector, DatabaseConnector } from '../types'; import defaultNameOverride from './defaultNameOverride'; import { mergeDatabaseConfig } from './mergeDatabaseConfig'; +// Limits the number of concurrent DDL operations to 1 +const ddlLimiter = limiterFactory(1); + /** * Creates a knex mysql database connection * @@ -174,7 +178,7 @@ export async function ensureMysqlDatabaseExists( let lastErr: Error | undefined = undefined; for (let i = 0; i < 3; i++) { try { - return await ensureDatabase(database); + return await ddlLimiter(() => ensureDatabase(database)); } catch (err) { lastErr = err; } @@ -214,7 +218,7 @@ export async function dropMysqlDatabase( }; await Promise.all( databases.map(async database => { - return await dropDatabase(database); + return await ddlLimiter(() => dropDatabase(database)); }), ); } finally { diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index 39a1073a30..1ddc096e05 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -23,12 +23,16 @@ import { ForwardedError, InputError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; import knexFactory, { Knex } from 'knex'; import { merge, omit } from 'lodash'; +import limiterFactory from 'p-limit'; import { Client } from 'pg'; import { Connector, DatabaseConnector } from '../types'; import defaultNameOverride from './defaultNameOverride'; import defaultSchemaOverride from './defaultSchemaOverride'; import { mergeDatabaseConfig } from './mergeDatabaseConfig'; +// Limits the number of concurrent DDL operations to 1 +const ddlLimiter = limiterFactory(1); + /** * Creates a knex postgres database connection * @@ -159,7 +163,7 @@ export async function ensurePgDatabaseExists( let lastErr: Error | undefined = undefined; for (let i = 0; i < 3; i++) { try { - return await ensureDatabase(database); + return await ddlLimiter(() => ensureDatabase(database)); } catch (err) { lastErr = err; } @@ -198,7 +202,9 @@ export async function ensurePgSchemaExists( } }; - await Promise.all(schemas.map(ensureSchema)); + await Promise.all( + schemas.map(database => ddlLimiter(() => ensureSchema(database))), + ); } finally { await admin.destroy(); } @@ -218,7 +224,7 @@ export async function dropPgDatabase( try { await Promise.all( databases.map(async database => { - await admin.raw(`DROP DATABASE ??`, [database]); + await ddlLimiter(() => admin.raw(`DROP DATABASE ??`, [database])); }), ); } finally { From 84bb2ed37df5312b0a0ed5b9195e2cd329487ec5 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 30 Apr 2024 02:57:29 -0600 Subject: [PATCH 169/234] integration support for harness - fixed comments Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- .changeset/empty-beers-relax.md | 2 +- .changeset/tasty-rats-explain.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/empty-beers-relax.md b/.changeset/empty-beers-relax.md index a285b9e7b4..bbfcef0933 100644 --- a/.changeset/empty-beers-relax.md +++ b/.changeset/empty-beers-relax.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -This patch adds HarnessURLReader. It only supports readUrl for now. readTree and search will be implemented next. +Added `HarnessURLReader` with `readUrl` support. diff --git a/.changeset/tasty-rats-explain.md b/.changeset/tasty-rats-explain.md index aee8057915..b97ee28fef 100644 --- a/.changeset/tasty-rats-explain.md +++ b/.changeset/tasty-rats-explain.md @@ -2,4 +2,4 @@ '@backstage/integration': minor --- -This patch brings Harness Code as a valid integration via the ScmIntgration interface. It adds harness code to the relevant static properties ( get integration by name, get integration by type) for plugs to be able to reference the same harness code server +Added `HarnessIntegration` via the `ScmIntegrations` interface. From 0a63f600a999b6aab2a3425a325c22e2578d6e3f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Tue, 30 Apr 2024 11:52:23 +0200 Subject: [PATCH 170/234] remove GOVERNANCE.md Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- GOVERNANCE.md | 233 ------------------ OWNERS.md | 2 +- docs/faq/technical.md | 2 +- ...021-06-22-spotify-backstage-is-growing.mdx | 4 +- microsite/blog/2023-04-26-kubecon-eu-2023.mdx | 2 +- .../blog/2024-04-19-community-plugins.mdx | 2 +- 6 files changed, 6 insertions(+), 239 deletions(-) delete mode 100644 GOVERNANCE.md diff --git a/GOVERNANCE.md b/GOVERNANCE.md deleted file mode 100644 index 2367dab99c..0000000000 --- a/GOVERNANCE.md +++ /dev/null @@ -1,233 +0,0 @@ -# Project Areas - -The Backstage project is divided into several project areas, each covering particular parts of the project. The main driver for each area is ownership of code in the main Backstage repository, as well as other repositories in the Backstage GitHub organization. Each area has a set of maintainers and repository content that they own. There may be no overlap in ownership between areas, which means that any given line in the GitHub code owners file should have only one owner specified. Each area is represented by a team in the Backstage GitHub organization. Apart from certain project-wide concerns, such as the release process, each area is self-governing and chooses their own ways of working. Project areas may also have special interest groups (SIGs) related to their area, but this is not required. - -The project areas as well as their maintainers are listed in the [OWNERS.md](./OWNERS.md) file. - -Each project area must have at least one maintainer. Project area maintainers may have shared ownership with the core maintainers, which in that case is considered an incubating area. The project area maintainers help drive work forward in the area, but they might not yet feel ready to take on ownership. The goal should generally be that these project area maintainers eventually become sole maintainers of the project area. This is to allow for a more smooth onboarding and transition of ownership, where members of the community might for example be new to open source maintainership. - -## Adding new project areas - -Project areas are added by nominating new maintainers for that area. See the sections for becoming a [Project Area Maintainer](#project-area-maintainer). - -Project areas may also by added by splitting existing areas. Every area that is created through this process must have at least one maintainer. - -## Removing project areas - -Project areas are removed by removing all maintainers for that area and removing the corresponding team from the Backstage GitHub organization. The project area can be re-added later if there is a need for it. Reasons for removal may include lack of activity, lack of maintainers, or lack of relevance to the project. - -# Project Roles - -## Contributor - -A Contributor contributes directly to the project and adds value to it. Contributions need not be code. People at the Contributor level may be new contributors, or they may only contribute occasionally. - -### Responsibilities - -- Follow the [CNCF CoC](https://github.com/cncf/foundation/blob/main/code-of-conduct.md) -- Follow the project [contributing guide](CONTRIBUTING.md) - -### How to get involved - -- Participate in community discussions -- Help other users -- Submit bug reports -- Comment on issues -- Try out new releases -- Attend community events - -### How to contribute - -- Report and sometimes resolve issues -- Occasionally submit PRs -- Contribute to the documentation -- Show up at meetings, take notes -- Answer questions from other community members -- Submit feedback on issues and PRs -- Test releases and patches and submit reviews -- Run or help run events -- Promote the project in public - -## Organization Member - -An org member is a frequent contributor that has become a member of the Backstage GitHub organization. In addition to the responsibilities of contributors, an org member is also expected to be reasonably active in the community through continuous contributions of any type. - -An Organization Member must meet the responsibilities and has the requirements of a Contributor. - -### Responsibilities - -- Continues to contribute regularly, as demonstrated by having at least 10 GitHub contributions per year in [Devstats](https://backstage.devstats.cncf.io/d/48/users-statistics-by-repository-group?orgId=1&var-period=y&var-metric=contributions&var-repogroup_name=All&from=now-1y&to=now&var-users=All), or contributions of a similar effort that might not be captured in Devstats. - -### Requirements - -- Must have at least 10 contributions to the projects in the form of: - - Accepted PRs - - Helpful PR reviews - - Resolving GitHub issues - - Or some equivalent contributions to the project -- Must have been contributing for at least 3 months -- Or is the member of a team that owns a project area, in which case the above requirements do not apply and the member is instead vetted by the project area maintainers - -### Becoming an Organization Member - -Open an issue towards [the community repository](https://github.com/backstage/community) using the [org membership request template](https://github.com/backstage/community/issues/new?template=org_member.yaml&title=Org+Member%3A+%3Cyour-github-login%3E). - -### Privileges - -- Membership in the Backstage GitHub organization - -## Plugin Maintainer - -A Plugin Maintainer is responsible for maintaining an individual Backstage plugin or module. This includes reviewing contributions and responding to issues towards an individual plugin, as well as keeping the plugin up to date. - -Plugin Maintainer is a lightweight form of ownership that is primarily reflected though code owners of the plugin packages in the [CODEOWNERS](./.github/CODEOWNERS) file. Each plugin can have one or more maintainers. If a plugin becomes a significant part of the Backstage ecosystem, it may be promoted to be a distinct project area instead. - -A Plugin Maintainer has all the rights and responsibilities of an Organization Member. - -### Responsibilities - -- Review the majority of PRs towards the plugin -- Respond to GitHub issues related to the plugin -- Keep the plugin up-to-date with Backstage libraries and other dependencies -- Follow the [reviewing guide](REVIEWING.md) - -### Requirements - -- Is an Organization Member -- Display knowledge of Backstage's review process and best practices for plugin design -- Is supportive of new and occasional contributors and helps get useful PRs in shape to merge - -### Privileges - -- GitHub code owner of the plugin directory, with rights to approve and merge PRs towards the plugin - -### Becoming a Plugin Maintainer - -To become a Plugin Maintainer, you first need to be an Organization Member. You can then file a pull request towards [CODEOWNERS.md](./.github/CODEOWNERS) requesting to be added as a code owner of the plugin directory. Existing code owners of that plugin alongside the core maintainers will then review the request. - -## Project Area Maintainer - -Project Area Maintainers are owners of a particular project area. They are expected to review and merge pull requests towards their area, and also drive development and manage tech health. A Project Area Maintainer also need to commit a certain number of hours per month towards the project, and exercise judgment for the good of the project, independent of their employer. Project Area Maintainers should also mentor new maintainers and participate in and lead community meetings related to their area. New Project Area Maintainers need to be approved by the existing project area maintainers, or the core maintainers if it is a new area. - -The maintainers of a project area may be represented by a team in external organization. In this case, the state as a project area maintainer is tied to the membership in that team. New members of the team may automatically be added as maintainers, as well as removed when they leave. This process is governed autonomously by the team of project area maintainers. - -A Project Area Maintainer has all the rights and responsibilities of an Organization Member. - -### Responsibilities - -- Review PRs towards their project area. Project area maintainers are expected to review at least 20 PRs per year, or the majority of all PRs towards the area, if it is less than 20 -- Follow the [reviewing guide](REVIEWING.md) -- Triage and respond to issues related to their project area -- Mentor new project area maintainers -- Write refactoring PRs -- Determine strategy and policy for the project area -- Participate in or leading community meetings related to their project area - -### Requirements - -- Is an Organization Member -- Have made at least 5 meaningful contributions towards the project area -- Demonstrates knowledge of their project area, and how it fits into the larger Backstage project -- Is able to exercise judgment for the good of the project, independent of their employer, friends, or team -- Mentors other contributors and project area maintainers -- Can commit to spending at least 16 hours per month working on the project, preferably distributed evenly across the month - -### Privileges - -- Approve and merge PRs towards their project area -- Drive the direction and roadmap of their project area - -### Becoming a Project Area Maintainer - -If you are interested in becoming a project area maintainer, reach out to the existing maintainers for that area. If you wish to become a maintainer for a new area, reach out to the core maintainers. - -Any current project area maintainer or core maintainer may nominate a new project area maintainer by opening a PR towards the [OWNERS.md](OWNERS.md) file. A majority of the project area maintainers for that area must approve the PR. If there are no existing maintainers for that area, the PR must be approved by a majority of the core maintainers. - -## Core Maintainer - -Core Maintainers are responsible for the Backstage project as a whole. They help review and merge project-level pull requests as well as coordinate work affecting multiple project areas. A core maintainer needs to commit the majority of their working time towards the project, and exercise judgment for the good of the project, independent of their employer. Core maintainers should also mentor and seek out new maintainers, lead community meetings, and communicate with the CNCF on behalf of the project. To become a core maintainer one needs to have been the maintainer of a number of different project areas, demonstrate a deep knowledge of large parts of the Backstage project, and be backed by the existing core maintainers. - -A Core Maintainer have all the rights and responsibilities of a Project Area Maintainer. - -### Responsibilities - -- Take part in the incoming issue and PR triage and review process. PRs are shared equally among all maintainers -- Mentor new Project Area Maintainers and Plugin Maintainers -- Drive refactoring and manage tech health across the entire project -- Participate in CNCF maintainer activities -- Respond to security incidents in accordance to our [security policy](./SECURITY.md) -- Determine strategy and policy for the project -- Participate in or leading community meetings - -### Requirements - -- Experience as a Project Area Maintainer for at least 6 months -- Demonstrates a broad knowledge of the project across multiple areas -- Is able to exercise judgment for the good of the project, independent of their employer, friends, or team -- Mentors other contributors -- Can commit to spending at least 10 days per month working on the project - -### Privileges - -- Approve PRs that fall outside any specific project area -- Merge PRs to any area of the project -- Represent the project in public as a Maintainer -- Communicate with the CNCF on behalf of the project -- Have a vote in Maintainer decision-making meetings - -### Becoming a Core Maintainer - -Any core maintainer or end user sponsor may nominate a new core maintainer by opening a PR towards the [OWNERS.md](OWNERS.md) file. Core maintainers must be approved by a majority of the existing core maintainers and end user sponsors. - -## End User Sponsors - -### Role of a Backstage End User Sponsor - -- Provide support for Backstage by removing blockers, securing funding, providing advocacy, feedback, and ensuring project continuity and long term success -- Assist Backstage maintainers in prioritizing upcoming roadmap items and planned work -- Provide neutral mediation for any disputes that arise as part of the project - -### Backstage End User Sponsor Membership - -The End User Sponsors group comprises at most 5 people. To be eligible for membership in the group, you or the company where you work you must: - -- Be responsible for and end user of a production Backstage deployment of non-trivial size -- Be active contributors to the open source project -- Be willing and able to attend regularly-scheduled End User Sponsor meetings -- Abide by [CNCF CoC](https://github.com/cncf/foundation/blob/main/code-of-conduct.md) - -Candidates for membership will be nominated by current Sponsor members or by Backstage maintainers. If there are more nominations than Sponsor seats remaining, existing sponsors shall vote on the candidates, and the candidates with the most votes will become Sponsors. Any ties will be broken by current Backstage sponsors. - -# Conflict resolution and voting - -In general, we prefer that technical issues and membership are amicably worked out between the persons involved. If a dispute cannot be decided independently, the sponsors and core maintainers can be called in to decide an issue. If the sponsors and maintainers themselves cannot decide an issue, the issue will be resolved by voting. - -In all cases in this document where voting is mentioned, the voting process is a simple majority in which each sponsor receives two votes and each core maintainer receives one vote. If such a majority is reached, the vote is said to have _passed_. - -## Inactivity - -It is important for contributors to be and stay active to set an example and show commitment to the project. Inactivity is harmful to the project as it may lead to unexpected delays, contributor attrition, and a loss of trust in the project. - -Inactivity is measured by periods of no contributions without explanation, for longer than: - -- Core Maintainer: 2 months -- Project Area Maintainer: 4 months -- Plugin Maintainer: 6 months -- Organization Member: 12 months - -Consequences of being inactive include: - -- Involuntary removal or demotion -- Being asked to move to Emeritus status - -## Involuntary Removal or Demotion - -Involuntary removal/demotion of a contributor happens when responsibilities and requirements aren't being met. This may include repeated patterns of inactivity, extended period of inactivity, a period of failing to meet the requirements of your role, and/or a violation of the Code of Conduct. This process is important because it protects the community and its deliverables while also opens up opportunities for new contributors to step in. - -Involuntary removal or demotion is handled through a vote by a majority of the current Core Maintainers. Some aspects of this process may be automated, such as removal after periods of inactivity. - -## Stepping Down/Emeritus Process - -If and when contributors' commitment levels change, contributors can consider stepping down (moving down the contributor ladder) vs moving to emeritus status (completely stepping away from the project). - -Contact the Maintainers about changing to Emeritus status, or reducing your contributor level. diff --git a/OWNERS.md b/OWNERS.md index ab35b5b1e5..d8967ca873 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -1,5 +1,5 @@ - See [CONTRIBUTING.md](CONTRIBUTING.md) for general contribution guidelines. -- See [GOVERNANCE.md](GOVERNANCE.md) for governance guidelines and responsibilities. +- See [GOVERNANCE.md](https://github.com/backstage/community/blob/main/GOVERNANCE.md) for governance guidelines and responsibilities. ## Core Maintainers diff --git a/docs/faq/technical.md b/docs/faq/technical.md index ffd99dadcd..d64d81d499 100644 --- a/docs/faq/technical.md +++ b/docs/faq/technical.md @@ -154,7 +154,7 @@ maintains Backstage in your own environment. For more information, see our [Owners](https://github.com/backstage/backstage/blob/master/OWNERS.md) and -[Governance](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md). +[Governance](https://github.com/backstage/community/blob/main/GOVERNANCE.md). ### Does Spotify provide a managed version of Backstage? diff --git a/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx b/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx index 6cae5c907e..308effaeab 100644 --- a/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx +++ b/microsite/blog/2021-06-22-spotify-backstage-is-growing.mdx @@ -66,9 +66,9 @@ Speaking of reviewers and maintainers… ## Adding reviewers and maintainers -[![GitHub logo](assets/21-06-22/gh-reviewers.png)](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#reviewers) +[![GitHub logo](assets/21-06-22/gh-reviewers.png)](https://github.com/backstage/community/blob/main/GOVERNANCE.md#reviewers) -We have introduced [reviewers](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#reviewers) to the project! By adding this new role, we’ve expanded the number of people who are permitted to approve and merge pull requests. This will offload some of the review work from the maintainers, simplifying and speeding up the review process for contributors. +We have introduced [reviewers](https://github.com/backstage/community/blob/main/GOVERNANCE.md#reviewers) to the project! By adding this new role, we’ve expanded the number of people who are permitted to approve and merge pull requests. This will offload some of the review work from the maintainers, simplifying and speeding up the review process for contributors. Of course, with these new efforts, we expect even more companies to adopt Backstage, which means the platform will continue to grow, and the number of PRs will continue to grow with it. As that happens, we hope to add to both the maintainer and reviewer teams in the future. diff --git a/microsite/blog/2023-04-26-kubecon-eu-2023.mdx b/microsite/blog/2023-04-26-kubecon-eu-2023.mdx index 7ee549c088..8f369c2d14 100644 --- a/microsite/blog/2023-04-26-kubecon-eu-2023.mdx +++ b/microsite/blog/2023-04-26-kubecon-eu-2023.mdx @@ -21,7 +21,7 @@ On Tuesday the Backstage maintainers hosted a jam-packed project meeting. The co ![Patrik and Ben onstage for the State of Backstage talk](assets/2023-04-26/IMG_0120.png) -Core maintainers [Ben Lambert](https://github.com/benjdlambert) and [Patrik Oldsberg](https://github.com/Rugvip) took center stage on Wednesday for the Backstage Maintainer Track: State of Backstage in 2023 talk. Backstage has officially hit over 1,000 adopters and 1,000 contributors – so it’s apt timing to modernize the governance model for the project. Taking pointers from the [CNCF Contributor Ladder Governance Template](https://contribute.cncf.io/maintainers/templates/), a new [Backstage Governance Model](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md) is now in effect! Patrik walked us through the new ladder model which introduces a number of changes, one being the addition of [project area maintainers](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#project-area-maintainer). This role lets members of the community take increased ownership over a specific area of interest, like Catalog, Discoverability, TechDocs, Helm Charts, and Kubernetes. New project areas that will be added include Permissions and Software Templates. New project areas can be proposed by nominating a project area maintainer for the area. The new model also adds an organization member role for contributors who want to take a more active role in the Backstage community. You can open an issue to become an organization member [here](https://github.com/backstage/community/issues/new/choose). +Core maintainers [Ben Lambert](https://github.com/benjdlambert) and [Patrik Oldsberg](https://github.com/Rugvip) took center stage on Wednesday for the Backstage Maintainer Track: State of Backstage in 2023 talk. Backstage has officially hit over 1,000 adopters and 1,000 contributors – so it’s apt timing to modernize the governance model for the project. Taking pointers from the [CNCF Contributor Ladder Governance Template](https://contribute.cncf.io/maintainers/templates/), a new [Backstage Governance Model](https://github.com/backstage/community/blob/main/GOVERNANCE.md) is now in effect! Patrik walked us through the new ladder model which introduces a number of changes, one being the addition of [project area maintainers](https://github.com/backstage/community/blob/main/GOVERNANCE.md#project-area-maintainer). This role lets members of the community take increased ownership over a specific area of interest, like Catalog, Discoverability, TechDocs, Helm Charts, and Kubernetes. New project areas that will be added include Permissions and Software Templates. New project areas can be proposed by nominating a project area maintainer for the area. The new model also adds an organization member role for contributors who want to take a more active role in the Backstage community. You can open an issue to become an organization member [here](https://github.com/backstage/community/issues/new/choose). ![Contributor ladder](assets/2023-04-26/contributor_ladder.png) diff --git a/microsite/blog/2024-04-19-community-plugins.mdx b/microsite/blog/2024-04-19-community-plugins.mdx index bb8b9c2313..49d1765bce 100644 --- a/microsite/blog/2024-04-19-community-plugins.mdx +++ b/microsite/blog/2024-04-19-community-plugins.mdx @@ -14,7 +14,7 @@ For those who depended on these plugins, migrating is as simple as `yarn backsta ## The community plugins repo -Some of you who have been around a while, or have seen our [Maintainer Track talks](https://www.youtube.com/watch?v=ONMBYnhxnNU) at KubeCon, might have seen [this RFC](https://github.com/backstage/backstage/issues/20266) which outlines some issues with the scale of the `backstage/backstage` monorepo, and us as maintainers being the de facto owners of all plugins without a [project area](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#project-area) or [plugin maintainer](https://github.com/backstage/backstage/blob/master/GOVERNANCE.md#project-area-maintainer). +Some of you who have been around a while, or have seen our [Maintainer Track talks](https://www.youtube.com/watch?v=ONMBYnhxnNU) at KubeCon, might have seen [this RFC](https://github.com/backstage/backstage/issues/20266) which outlines some issues with the scale of the `backstage/backstage` monorepo, and us as maintainers being the de facto owners of all plugins without a [project area](https://github.com/backstage/community/blob/main/GOVERNANCE.md#project-area) or [plugin maintainer](https://github.com/backstage/community/blob/main/GOVERNANCE.md#project-area-maintainer). There was some great discussion in this issue, and some great ideas. One of the ideas was to create a dedicated home for community plugins, with all the burden of release tooling and workspace tooling already set up, which is a pretty big barrier for people wanting to create plugins for Backstage in their own organization or personal account. These plugins would then have the ability to release independently of the main monorepo, and have their own release cadence, which is something that we've been looking at exploring for a while. From 1cd13b181b0ddde659a7d20c49626b9dd19d8386 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Tue, 30 Apr 2024 12:00:30 +0200 Subject: [PATCH 171/234] upgrade the changeset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- .changeset/mean-ravens-dance.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.changeset/mean-ravens-dance.md b/.changeset/mean-ravens-dance.md index e409f57c79..061e12cd9e 100644 --- a/.changeset/mean-ravens-dance.md +++ b/.changeset/mean-ravens-dance.md @@ -1,5 +1,7 @@ --- -'@backstage/backend-common': patch +'@backstage/backend-common': minor --- -Internal refactor of the database code +Internal refactor of the database code. + +**BREAKING**: The helper functions `createDatabaseClient` and `ensureDatabaseExists` have been removed from the public interface, since they have no usage within the repository and never were suitable for calling from the outside. Please consider using `coreServices.database` or `DatabaseManager` directly wherever possible instead. From 0478509ee24a4dd0a3c7ba26996c41a566e3f192 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Tue, 30 Apr 2024 11:22:01 +0000 Subject: [PATCH 172/234] Version Packages (next) --- .changeset/create-app-1714476054.md | 5 + .changeset/pre.json | 21 +- docs/releases/v1.27.0-next.1-changelog.md | 1487 +++++++++++++++++ package.json | 2 +- packages/app-defaults/CHANGELOG.md | 7 + packages/app-defaults/package.json | 2 +- packages/app-next-example-plugin/CHANGELOG.md | 8 + packages/app-next-example-plugin/package.json | 2 +- packages/app-next/CHANGELOG.md | 35 + packages/app-next/package.json | 2 +- packages/app/CHANGELOG.md | 33 + packages/app/package.json | 2 +- packages/backend-app-api/CHANGELOG.md | 14 + packages/backend-app-api/package.json | 2 +- packages/backend-common/CHANGELOG.md | 16 + packages/backend-common/package.json | 2 +- packages/backend-defaults/CHANGELOG.md | 8 + packages/backend-defaults/package.json | 2 +- .../CHANGELOG.md | 20 + .../package.json | 2 +- packages/backend-next/CHANGELOG.md | 35 + packages/backend-next/package.json | 2 +- packages/backend-openapi-utils/CHANGELOG.md | 7 + packages/backend-openapi-utils/package.json | 2 +- packages/backend-plugin-api/CHANGELOG.md | 8 + packages/backend-plugin-api/package.json | 2 +- packages/backend-tasks/CHANGELOG.md | 8 + packages/backend-tasks/package.json | 2 +- packages/backend-test-utils/CHANGELOG.md | 10 + packages/backend-test-utils/package.json | 2 +- packages/backend/CHANGELOG.md | 37 + packages/backend/package.json | 2 +- packages/core-compat-api/CHANGELOG.md | 7 + packages/core-compat-api/package.json | 2 +- packages/core-components/CHANGELOG.md | 7 + packages/core-components/package.json | 2 +- packages/create-app/CHANGELOG.md | 6 + packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 10 + packages/dev-utils/package.json | 2 +- packages/e2e-test/CHANGELOG.md | 7 + packages/e2e-test/package.json | 2 +- packages/frontend-app-api/CHANGELOG.md | 8 + packages/frontend-app-api/package.json | 2 +- packages/frontend-plugin-api/CHANGELOG.md | 7 + packages/frontend-plugin-api/package.json | 2 +- packages/frontend-test-utils/CHANGELOG.md | 8 + packages/frontend-test-utils/package.json | 2 +- packages/repo-tools/CHANGELOG.md | 10 + packages/repo-tools/package.json | 2 +- .../techdocs-cli-embedded-app/CHANGELOG.md | 13 + .../techdocs-cli-embedded-app/package.json | 2 +- packages/techdocs-cli/CHANGELOG.md | 8 + packages/techdocs-cli/package.json | 2 +- plugins/api-docs/CHANGELOG.md | 11 + plugins/api-docs/package.json | 2 +- plugins/app-backend/CHANGELOG.md | 11 + plugins/app-backend/package.json | 2 +- plugins/app-node/CHANGELOG.md | 8 + plugins/app-node/package.json | 2 +- plugins/app-visualizer/CHANGELOG.md | 8 + plugins/app-visualizer/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- plugins/auth-backend/CHANGELOG.md | 24 + plugins/auth-backend/package.json | 2 +- plugins/auth-node/CHANGELOG.md | 8 + plugins/auth-node/package.json | 2 +- plugins/auth-react/CHANGELOG.md | 7 + plugins/auth-react/package.json | 2 +- .../catalog-backend-module-aws/CHANGELOG.md | 10 + .../catalog-backend-module-aws/package.json | 2 +- .../catalog-backend-module-azure/CHANGELOG.md | 10 + .../catalog-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../catalog-backend-module-gcp/CHANGELOG.md | 10 + .../catalog-backend-module-gcp/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../catalog-backend-module-ldap/CHANGELOG.md | 8 + .../catalog-backend-module-ldap/package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- plugins/catalog-backend/CHANGELOG.md | 15 + plugins/catalog-backend/package.json | 2 +- plugins/catalog-graph/CHANGELOG.md | 10 + plugins/catalog-graph/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 11 + plugins/catalog-import/package.json | 2 +- plugins/catalog-node/CHANGELOG.md | 8 + plugins/catalog-node/package.json | 2 +- plugins/catalog-react/CHANGELOG.md | 9 + plugins/catalog-react/package.json | 2 +- .../catalog-unprocessed-entities/CHANGELOG.md | 7 + .../catalog-unprocessed-entities/package.json | 2 +- plugins/catalog/CHANGELOG.md | 13 + plugins/catalog/package.json | 2 +- plugins/config-schema/CHANGELOG.md | 7 + plugins/config-schema/package.json | 2 +- plugins/devtools-backend/CHANGELOG.md | 10 + plugins/devtools-backend/package.json | 2 +- plugins/devtools/CHANGELOG.md | 9 + plugins/devtools/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../events-backend-module-azure/CHANGELOG.md | 8 + .../events-backend-module-azure/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../events-backend-module-gerrit/CHANGELOG.md | 8 + .../events-backend-module-gerrit/package.json | 2 +- .../events-backend-module-github/CHANGELOG.md | 8 + .../events-backend-module-github/package.json | 2 +- .../events-backend-module-gitlab/CHANGELOG.md | 8 + .../events-backend-module-gitlab/package.json | 2 +- .../events-backend-test-utils/CHANGELOG.md | 7 + .../events-backend-test-utils/package.json | 2 +- plugins/events-backend/CHANGELOG.md | 9 + plugins/events-backend/package.json | 2 +- plugins/events-node/CHANGELOG.md | 7 + plugins/events-node/package.json | 2 +- .../example-todo-list-backend/CHANGELOG.md | 9 + .../example-todo-list-backend/package.json | 2 +- plugins/example-todo-list/CHANGELOG.md | 7 + plugins/example-todo-list/package.json | 2 +- plugins/home-react/CHANGELOG.md | 7 + plugins/home-react/package.json | 2 +- plugins/home/CHANGELOG.md | 11 + plugins/home/package.json | 2 +- plugins/kubernetes-backend/CHANGELOG.md | 12 + plugins/kubernetes-backend/package.json | 2 +- plugins/kubernetes-cluster/CHANGELOG.md | 9 + plugins/kubernetes-cluster/package.json | 2 +- plugins/kubernetes-node/CHANGELOG.md | 7 + plugins/kubernetes-node/package.json | 2 +- plugins/kubernetes-react/CHANGELOG.md | 8 + plugins/kubernetes-react/package.json | 2 +- plugins/kubernetes/CHANGELOG.md | 9 + plugins/kubernetes/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- plugins/notifications-backend/CHANGELOG.md | 14 + plugins/notifications-backend/package.json | 2 +- plugins/notifications-node/CHANGELOG.md | 10 + plugins/notifications-node/package.json | 2 +- plugins/notifications/CHANGELOG.md | 10 + plugins/notifications/package.json | 2 +- plugins/org-react/CHANGELOG.md | 8 + plugins/org-react/package.json | 2 +- plugins/org/CHANGELOG.md | 10 + plugins/org/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/permission-backend/CHANGELOG.md | 10 + plugins/permission-backend/package.json | 2 +- plugins/permission-node/CHANGELOG.md | 9 + plugins/permission-node/package.json | 2 +- plugins/proxy-backend/CHANGELOG.md | 8 + plugins/proxy-backend/package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 8 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/scaffolder-backend/CHANGELOG.md | 23 + plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder-common/CHANGELOG.md | 6 + plugins/scaffolder-common/package.json | 2 +- .../scaffolder-node-test-utils/CHANGELOG.md | 9 + .../scaffolder-node-test-utils/package.json | 2 +- plugins/scaffolder-node/CHANGELOG.md | 9 + plugins/scaffolder-node/package.json | 2 +- plugins/scaffolder-react/CHANGELOG.md | 10 + plugins/scaffolder-react/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 14 + plugins/scaffolder/package.json | 2 +- .../CHANGELOG.md | 11 + .../package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- plugins/search-backend-module-pg/CHANGELOG.md | 9 + plugins/search-backend-module-pg/package.json | 2 +- .../CHANGELOG.md | 10 + .../package.json | 2 +- .../CHANGELOG.md | 12 + .../package.json | 2 +- plugins/search-backend-node/CHANGELOG.md | 9 + plugins/search-backend-node/package.json | 2 +- plugins/search-backend/CHANGELOG.md | 12 + plugins/search-backend/package.json | 2 +- plugins/search-react/CHANGELOG.md | 8 + plugins/search-react/package.json | 2 +- plugins/search/CHANGELOG.md | 11 + plugins/search/package.json | 2 +- plugins/signals-backend/CHANGELOG.md | 11 + plugins/signals-backend/package.json | 2 +- plugins/signals-node/CHANGELOG.md | 10 + plugins/signals-node/package.json | 2 +- plugins/signals/CHANGELOG.md | 7 + plugins/signals/package.json | 2 +- .../techdocs-addons-test-utils/CHANGELOG.md | 12 + .../techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/CHANGELOG.md | 10 + plugins/techdocs-backend/package.json | 2 +- .../CHANGELOG.md | 9 + .../package.json | 2 +- plugins/techdocs-node/CHANGELOG.md | 8 + plugins/techdocs-node/package.json | 2 +- plugins/techdocs-react/CHANGELOG.md | 7 + plugins/techdocs-react/package.json | 2 +- plugins/techdocs/CHANGELOG.md | 14 + plugins/techdocs/package.json | 2 +- plugins/user-settings-backend/CHANGELOG.md | 9 + plugins/user-settings-backend/package.json | 2 +- plugins/user-settings/CHANGELOG.md | 10 + plugins/user-settings/package.json | 2 +- 294 files changed, 3143 insertions(+), 147 deletions(-) create mode 100644 .changeset/create-app-1714476054.md create mode 100644 docs/releases/v1.27.0-next.1-changelog.md create mode 100644 plugins/notifications-backend-module-email/CHANGELOG.md diff --git a/.changeset/create-app-1714476054.md b/.changeset/create-app-1714476054.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1714476054.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/pre.json b/.changeset/pre.json index 809cd51567..f776d90b91 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -282,36 +282,49 @@ "@backstage/plugin-vault-backend": "0.4.10", "@backstage/plugin-vault-node": "0.1.10", "@backstage/plugin-xcmetrics": "0.2.52", - "@backstage/plugin-catalog-backend-module-gitlab-org": "0.0.0" + "@backstage/plugin-catalog-backend-module-gitlab-org": "0.0.0", + "@backstage/plugin-notifications-backend-module-email": "0.0.0" }, "changesets": [ "afraid-needles-divide", + "blue-hotels-shake", "bright-pumpkins-rule", "chatty-cycles-unite", "chilly-adults-sing", "chilly-fireants-roll", + "chilly-shoes-doubt", "cold-rats-leave", "cool-elephants-march", + "create-app-1714476054", "cuddly-chairs-kick", + "curly-shirts-flow", "curvy-planes-flash", + "cyan-suns-shave", "dry-sloths-impress", "eighty-apricots-kneel", "eighty-bats-stare", "eleven-pandas-divide", "fix-stackoverflow", "fluffy-hotels-wait", + "four-cooks-serve", "fresh-crews-impress", + "funny-bees-taste", + "fuzzy-seahorses-tell", "gold-waves-bake", "gorgeous-cameras-cross", "green-adults-push", + "green-boxes-rescue", "hip-carrots-drive", + "hot-forks-train", "itchy-gorillas-hope", "itchy-keys-wonder", "late-planes-fix", "loud-frogs-eat", + "loud-timers-flow", "loud-vans-greet", "lovely-games-cry", "lucky-news-guess", + "mean-ravens-dance", "orange-numbers-think", "perfect-points-hope", "proud-doors-cheat", @@ -322,16 +335,22 @@ "renovate-0d0bd5c", "selfish-pigs-glow", "shy-students-clap", + "silent-wombats-hang", + "six-scissors-smile", "smart-avocados-invent", "smooth-garlics-behave", "sour-socks-approve", "stupid-onions-know", + "sweet-zoos-clap", + "tall-ads-shave", "tasty-apes-learn", "thick-llamas-itch", + "thick-terms-rush", "tricky-cougars-shout", "unlucky-days-play", "unlucky-rivers-collect", "warm-fans-promise", + "young-guests-reflect", "young-olives-drop" ] } diff --git a/docs/releases/v1.27.0-next.1-changelog.md b/docs/releases/v1.27.0-next.1-changelog.md new file mode 100644 index 0000000000..77c6919cda --- /dev/null +++ b/docs/releases/v1.27.0-next.1-changelog.md @@ -0,0 +1,1487 @@ +# Release v1.27.0-next.1 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.27.0-next.1](https://backstage.github.io/upgrade-helper/?to=1.27.0-next.1) + +## @backstage/backend-common@0.22.0-next.1 + +### Minor Changes + +- ed83f85: Internal refactor of the database code. + + **BREAKING**: The helper functions `createDatabaseClient` and `ensureDatabaseExists` have been removed from the public interface, since they have no usage within the repository and never were suitable for calling from the outside. Please consider using `coreServices.database` or `DatabaseManager` directly wherever possible instead. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.2-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/app-defaults@1.5.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + +## @backstage/backend-app-api@0.7.2-next.1 + +### Patch Changes + +- 09f8988: Remove explicit `alg` check for user tokens in `verifyToken` +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/cli-node@0.2.5 + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/backend-defaults@0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.2-next.1 + - @backstage/backend-common@0.22.0-next.1 + +## @backstage/backend-dynamic-feature-service@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.2-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-events-backend@0.3.5-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/cli-node@0.2.5 + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/plugin-app-node@0.1.18-next.1 + +## @backstage/backend-openapi-utils@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/backend-plugin-api@0.6.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + +## @backstage/backend-tasks@0.5.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/backend-test-utils@0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.2-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/core-compat-api@0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## @backstage/core-components@0.14.6-next.1 + +### Patch Changes + +- 5223c4c: Fixed an internal circular import that broke Jest mocks. +- 5b7b49b: The `SignInPage` guest provider will now fall back to legacy guest auth if the backend request fails, allowing guest auth without a running backend. + +## @backstage/create-app@0.5.15-next.1 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/dev-utils@1.0.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/app-defaults@1.5.5-next.1 + - @backstage/integration-react@1.1.26 + +## @backstage/frontend-app-api@0.6.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## @backstage/frontend-plugin-api@0.6.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + +## @backstage/frontend-test-utils@0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.6.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## @backstage/repo-tools@0.8.1-next.1 + +### Patch Changes + +- 9ae9bb2: Update the paths logic in the api reports command to support complex subpaths +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/cli-node@0.2.5 + - @backstage/config-loader@1.8.0 + +## @techdocs/cli@1.8.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-techdocs-node@1.12.4-next.1 + +## @backstage/plugin-api-docs@0.11.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog@1.19.1-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + +## @backstage/plugin-app-backend@0.3.66-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/plugin-app-node@0.1.18-next.1 + +## @backstage/plugin-app-node@0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-app-visualizer@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## @backstage/plugin-auth-backend@0.22.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10-next.1 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.9-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.10-next.1 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.15-next.1 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.13-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.15-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.11-next.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.13-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.1.15-next.1 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.11-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-atlassian-provider@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-gitlab-provider@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-google-provider@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-guest-provider@0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-microsoft-provider@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-provider@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-oidc-provider@0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-okta-provider@0.0.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-pinniped-provider@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-backend-module-vmware-cloud-provider@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-node@0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-auth-react@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + +## @backstage/plugin-catalog@1.19.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + +## @backstage/plugin-catalog-backend@1.22.0-next.1 + +### Patch Changes + +- 8479a0b: Fixed bug in stitching queue gauge that included entities that are scheduled in the future. +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.24-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/backend-openapi-utils@0.1.11-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-azure@0.1.38-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/backend-openapi-utils@0.1.11-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-gcp@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-backend-module-github@0.6.1-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.3.15-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.15-next.3 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.4.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.5.26-next.1 + +### Patch Changes + +- 49eab29: Fixed disabling of user photo fetching. Previously, the config value wasn't propagated properly, so user photos was still being fetched despite disabled by config. +- 6e370e6: Handle fetching huge amounts of users from Azure without crashing +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-puppetdb@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.1 + +### Patch Changes + +- 6f5a3a3: Correctly convert owner to string in case owner has not been provided +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-graph@0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + +## @backstage/plugin-catalog-import@0.10.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/core-compat-api@0.2.5-next.1 + +## @backstage/plugin-catalog-node@1.11.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-catalog-react@1.11.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + +## @backstage/plugin-config-schema@0.1.55-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + +## @backstage/plugin-devtools@0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + +## @backstage/plugin-devtools-backend@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-events-backend@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-events-backend-module-aws-sqs@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-events-backend-module-azure@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-events-backend-module-bitbucket-cloud@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-events-backend-module-gerrit@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-events-backend-module-github@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-events-backend-module-gitlab@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-events-backend-test-utils@0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + +## @backstage/plugin-events-node@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-home@0.7.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-home-react@0.1.13-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + +## @backstage/plugin-home-react@0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + +## @backstage/plugin-kubernetes@0.11.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-kubernetes-react@0.3.5-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + +## @backstage/plugin-kubernetes-backend@0.17.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-kubernetes-node@0.1.12-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-kubernetes-cluster@0.0.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-kubernetes-react@0.3.5-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + +## @backstage/plugin-kubernetes-node@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-kubernetes-react@0.3.5-next.1 + +### Patch Changes + +- 3102a99: add the namespace label to CronJobDrawer & IngressDrawer. +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + +## @backstage/plugin-notifications@0.2.1-next.1 + +### Patch Changes + +- f730c0b: The user can newly mark all unread messages as read at one click. +- bfcb2f1: Allow showing notifications as snackbars in the UI +- f6633ca: Add option to set the notification as read automatically when the notification link is opened +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + +## @backstage/plugin-notifications-backend@0.2.1-next.1 + +### Patch Changes + +- 295c05d: Support for filtering entities from notification recipients after resolving them from the recipients +- cba628a: Add possibility to generate random notifications on the fly in local development +- Updated dependencies + - @backstage/plugin-notifications-node@0.1.4-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-signals-node@0.1.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-notifications-backend-module-email@0.0.1-next.0 + +### Patch Changes + +- dbf2696: Allow sending notifications by email with the new notifications module +- Updated dependencies + - @backstage/plugin-notifications-node@0.1.4-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-notifications-node@0.1.4-next.1 + +### Patch Changes + +- 295c05d: Support for filtering entities from notification recipients after resolving them from the recipients +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-signals-node@0.1.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-org@0.6.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + +## @backstage/plugin-org-react@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + +## @backstage/plugin-permission-backend@0.5.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-permission-node@0.7.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-proxy-backend@0.4.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder@1.19.4-next.1 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/plugin-scaffolder-react@1.8.5-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/core-compat-api@0.2.5-next.1 + +## @backstage/plugin-scaffolder-backend@1.22.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.10-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.8-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-azure@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-common@1.5.2-next.1 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder + +## @backstage/plugin-scaffolder-node@0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-scaffolder-node-test-utils@0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-test-utils@0.3.8-next.1 + +## @backstage/plugin-scaffolder-react@1.8.5-next.1 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + +## @backstage/plugin-search@1.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + +## @backstage/plugin-search-backend@1.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/repo-tools@0.8.1-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/backend-openapi-utils@0.1.11-next.1 + +## @backstage/plugin-search-backend-module-catalog@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-search-backend-module-explore@0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-search-backend-module-pg@0.5.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-search-backend-module-stack-overflow-collator@0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-search-backend-module-techdocs@0.1.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/plugin-techdocs-node@1.12.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-search-backend-node@1.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-search-react@1.7.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## @backstage/plugin-signals@0.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + +## @backstage/plugin-signals-backend@0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-signals-node@0.1.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-signals-node@0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-techdocs@1.10.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-auth-react@0.1.2-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.19.1-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-techdocs@1.10.5-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + +## @backstage/plugin-techdocs-backend@1.10.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.1 + - @backstage/plugin-techdocs-node@1.12.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + +## @backstage/plugin-techdocs-node@1.12.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## @backstage/plugin-techdocs-react@1.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + +## @backstage/plugin-user-settings@0.8.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + +## @backstage/plugin-user-settings-backend@0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + +## example-app@0.2.97-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.5-next.1 + - @backstage/plugin-scaffolder@1.19.4-next.1 + - @backstage/plugin-notifications@0.2.1-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog@1.19.1-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/app-defaults@1.5.5-next.1 + - @backstage/cli@0.26.5-next.0 + - @backstage/frontend-app-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-api-docs@0.11.5-next.1 + - @backstage/plugin-auth-react@0.1.2-next.1 + - @backstage/plugin-catalog-graph@0.4.5-next.1 + - @backstage/plugin-catalog-import@0.10.11-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.1 + - @backstage/plugin-devtools@0.1.14-next.1 + - @backstage/plugin-home@0.7.4-next.1 + - @backstage/plugin-kubernetes@0.11.10-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.1 + - @backstage/plugin-org@0.6.25-next.1 + - @backstage/plugin-search@1.4.11-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-signals@0.0.6-next.1 + - @backstage/plugin-techdocs@1.10.5-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + - @backstage/plugin-user-settings@0.8.6-next.1 + +## example-app-next@0.0.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.5-next.1 + - @backstage/plugin-scaffolder@1.19.4-next.1 + - @backstage/plugin-notifications@0.2.1-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog@1.19.1-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/app-defaults@1.5.5-next.1 + - @backstage/cli@0.26.5-next.0 + - @backstage/frontend-app-api@0.6.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-api-docs@0.11.5-next.1 + - @backstage/plugin-app-visualizer@0.1.6-next.1 + - @backstage/plugin-auth-react@0.1.2-next.1 + - @backstage/plugin-catalog-graph@0.4.5-next.1 + - @backstage/plugin-catalog-import@0.10.11-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.1 + - @backstage/plugin-home@0.7.4-next.1 + - @backstage/plugin-kubernetes@0.11.10-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.1 + - @backstage/plugin-org@0.6.25-next.1 + - @backstage/plugin-search@1.4.11-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-signals@0.0.6-next.1 + - @backstage/plugin-techdocs@1.10.5-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + - @backstage/plugin-user-settings@0.8.6-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + +## app-next-example-plugin@0.0.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + +## example-backend@0.2.98-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.1 + - @backstage/plugin-scaffolder-backend@1.22.5-next.1 + - example-app@0.2.97-next.1 + - @backstage/plugin-search-backend@1.5.8-next.1 + - @backstage/plugin-app-backend@0.3.66-next.1 + - @backstage/plugin-kubernetes-backend@0.17.1-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-devtools-backend@0.3.4-next.1 + - @backstage/plugin-events-backend@0.3.5-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-permission-backend@0.5.42-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-proxy-backend@0.4.16-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.24-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.1-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.24-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.27-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/plugin-signals-backend@0.1.4-next.1 + - @backstage/plugin-signals-node@0.1.4-next.1 + - @backstage/plugin-techdocs-backend@1.10.5-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + +## example-backend-next@0.0.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.1 + - @backstage/plugin-notifications-backend@0.2.1-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.1 + - @backstage/plugin-scaffolder-backend@1.22.5-next.1 + - @backstage/plugin-search-backend@1.5.8-next.1 + - @backstage/backend-defaults@0.2.18-next.1 + - @backstage/plugin-app-backend@0.3.66-next.1 + - @backstage/plugin-kubernetes-backend@0.17.1-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.4-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.36-next.1 + - @backstage/plugin-devtools-backend@0.3.4-next.1 + - @backstage/plugin-permission-backend@0.5.42-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-proxy-backend@0.4.16-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.24-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.24-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/plugin-signals-backend@0.1.4-next.1 + - @backstage/plugin-techdocs-backend@1.10.5-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1-next.1 + +## e2e-test@0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.15-next.1 + +## techdocs-cli-embedded-app@0.2.96-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog@1.19.1-next.1 + - @backstage/app-defaults@1.5.5-next.1 + - @backstage/cli@0.26.5-next.0 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-techdocs@1.10.5-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + +## @internal/plugin-todo-list@1.0.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + +## @internal/plugin-todo-list-backend@1.0.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 diff --git a/package.json b/package.json index abae1b54ec..5d9fbef716 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.27.0-next.0", + "version": "1.27.0-next.1", "private": true, "repository": { "type": "git", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 9efa40b6cc..15f1c5bd67 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/app-defaults +## 1.5.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + ## 1.5.5-next.0 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index ad9986a29a..d5955d863e 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.5.5-next.0", + "version": "1.5.5-next.1", "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 33f564936a..fa7d0f3107 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.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.0.11-next.0 ### Patch Changes diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index acd85fc7cb..55dd01aea5 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.11-next.0", + "version": "0.0.11-next.1", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin" diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 1188aff451..ab2c13c42a 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,40 @@ # example-app-next +## 0.0.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.5-next.1 + - @backstage/plugin-scaffolder@1.19.4-next.1 + - @backstage/plugin-notifications@0.2.1-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog@1.19.1-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/app-defaults@1.5.5-next.1 + - @backstage/cli@0.26.5-next.0 + - @backstage/frontend-app-api@0.6.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-api-docs@0.11.5-next.1 + - @backstage/plugin-app-visualizer@0.1.6-next.1 + - @backstage/plugin-auth-react@0.1.2-next.1 + - @backstage/plugin-catalog-graph@0.4.5-next.1 + - @backstage/plugin-catalog-import@0.10.11-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.1 + - @backstage/plugin-home@0.7.4-next.1 + - @backstage/plugin-kubernetes@0.11.10-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.1 + - @backstage/plugin-org@0.6.25-next.1 + - @backstage/plugin-search@1.4.11-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-signals@0.0.6-next.1 + - @backstage/plugin-techdocs@1.10.5-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + - @backstage/plugin-user-settings@0.8.6-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + ## 0.0.11-next.0 ### Patch Changes diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 065aa43322..d41ff02488 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.11-next.0", + "version": "0.0.11-next.1", "private": true, "repository": { "type": "git", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 10c2873bc4..12e71ba6c0 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,38 @@ # example-app +## 0.2.97-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-react@1.8.5-next.1 + - @backstage/plugin-scaffolder@1.19.4-next.1 + - @backstage/plugin-notifications@0.2.1-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog@1.19.1-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/app-defaults@1.5.5-next.1 + - @backstage/cli@0.26.5-next.0 + - @backstage/frontend-app-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-api-docs@0.11.5-next.1 + - @backstage/plugin-auth-react@0.1.2-next.1 + - @backstage/plugin-catalog-graph@0.4.5-next.1 + - @backstage/plugin-catalog-import@0.10.11-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.4-next.1 + - @backstage/plugin-devtools@0.1.14-next.1 + - @backstage/plugin-home@0.7.4-next.1 + - @backstage/plugin-kubernetes@0.11.10-next.1 + - @backstage/plugin-kubernetes-cluster@0.0.11-next.1 + - @backstage/plugin-org@0.6.25-next.1 + - @backstage/plugin-search@1.4.11-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-signals@0.0.6-next.1 + - @backstage/plugin-techdocs@1.10.5-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.10-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + - @backstage/plugin-user-settings@0.8.6-next.1 + ## 0.2.97-next.0 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index b0e3a8fee3..20d0d0312f 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.97-next.0", + "version": "0.2.97-next.1", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 30515c50ff..733a19d81e 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/backend-app-api +## 0.7.2-next.1 + +### Patch Changes + +- 09f8988: Remove explicit `alg` check for user tokens in `verifyToken` +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/cli-node@0.2.5 + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.7.1-next.0 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 09ff295f8d..bb53e78ff1 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": "0.7.1-next.0", + "version": "0.7.2-next.1", "description": "Core API used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index fdeaf6d137..c8e459ba78 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/backend-common +## 0.22.0-next.1 + +### Minor Changes + +- ed83f85: Internal refactor of the database code. + + **BREAKING**: The helper functions `createDatabaseClient` and `ensureDatabaseExists` have been removed from the public interface, since they have no usage within the repository and never were suitable for calling from the outside. Please consider using `coreServices.database` or `DatabaseManager` directly wherever possible instead. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.2-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.21.8-next.0 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 0a08c58e54..42c4c49863 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-common", - "version": "0.21.8-next.0", + "version": "0.22.0-next.1", "description": "Common functionality library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index fd311f7d2b..a9871d0aa5 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-defaults +## 0.2.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.2-next.1 + - @backstage/backend-common@0.22.0-next.1 + ## 0.2.18-next.0 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 8b061b15f4..0a9fb7de55 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", "description": "Backend defaults used by Backstage backend apps", - "version": "0.2.18-next.0", + "version": "0.2.18-next.1", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index 95b6097025..f45e57bd9c 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/backend-dynamic-feature-service +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.2-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-events-backend@0.3.5-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/cli-node@0.2.5 + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/plugin-app-node@0.1.18-next.1 + ## 0.2.10-next.0 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index 07a559c748..35d053d40a 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-dynamic-feature-service", "description": "Backstage dynamic feature service", - "version": "0.2.10-next.0", + "version": "0.2.10-next.1", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-next/CHANGELOG.md b/packages/backend-next/CHANGELOG.md index 3c48343ee4..5cc98ddb8a 100644 --- a/packages/backend-next/CHANGELOG.md +++ b/packages/backend-next/CHANGELOG.md @@ -1,5 +1,40 @@ # example-backend-next +## 0.0.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.1 + - @backstage/plugin-notifications-backend@0.2.1-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.1 + - @backstage/plugin-scaffolder-backend@1.22.5-next.1 + - @backstage/plugin-search-backend@1.5.8-next.1 + - @backstage/backend-defaults@0.2.18-next.1 + - @backstage/plugin-app-backend@0.3.66-next.1 + - @backstage/plugin-kubernetes-backend@0.17.1-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-backend-module-guest-provider@0.1.4-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-catalog-backend-module-openapi@0.1.36-next.1 + - @backstage/plugin-devtools-backend@0.3.4-next.1 + - @backstage/plugin-permission-backend@0.5.42-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-proxy-backend@0.4.16-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.24-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.24-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/plugin-signals-backend@0.1.4-next.1 + - @backstage/plugin-techdocs-backend@1.10.5-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.1.15-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.2.1-next.1 + ## 0.0.26-next.0 ### Patch Changes diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index 65416da7a3..f5c61bd8ff 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-next", - "version": "0.0.26-next.0", + "version": "0.0.26-next.1", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/backend-openapi-utils/CHANGELOG.md b/packages/backend-openapi-utils/CHANGELOG.md index 2e0d578fdc..3978b5dfd0 100644 --- a/packages/backend-openapi-utils/CHANGELOG.md +++ b/packages/backend-openapi-utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/backend-openapi-utils +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.11-next.0 ### Patch Changes diff --git a/packages/backend-openapi-utils/package.json b/packages/backend-openapi-utils/package.json index c95e01bfd7..f77626001d 100644 --- a/packages/backend-openapi-utils/package.json +++ b/packages/backend-openapi-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-openapi-utils", "description": "OpenAPI typescript support.", - "version": "0.1.11-next.0", + "version": "0.1.11-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 4dccd8d1bc..e6a0d5c278 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-plugin-api +## 0.6.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + ## 0.6.18-next.0 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index e4bfaf3112..fff99c0370 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": "0.6.18-next.0", + "version": "0.6.18-next.1", "description": "Core API used by Backstage backend plugins", "backstage": { "role": "node-library" diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 5fa17ac1fd..f90d8ff7ef 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-tasks +## 0.5.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.5.23-next.0 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 77d4ff60c7..a7f64b9db1 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-tasks", "description": "Common distributed task management library for Backstage backends", - "version": "0.5.23-next.0", + "version": "0.5.23-next.1", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 03886c776d..6602f4cbe8 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/backend-test-utils +## 0.3.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.7.2-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.3.8-next.0 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 1d43e24adf..d88447efbb 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": "0.3.8-next.0", + "version": "0.3.8-next.1", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index ce08ade977..06aba6c5ca 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,42 @@ # example-backend +## 0.2.98-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-unprocessed@0.4.5-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.1 + - @backstage/plugin-scaffolder-backend@1.22.5-next.1 + - example-app@0.2.97-next.1 + - @backstage/plugin-search-backend@1.5.8-next.1 + - @backstage/plugin-app-backend@0.3.66-next.1 + - @backstage/plugin-kubernetes-backend@0.17.1-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-devtools-backend@0.3.4-next.1 + - @backstage/plugin-events-backend@0.3.5-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-permission-backend@0.5.42-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-proxy-backend@0.4.16-next.1 + - @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.2.19-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.35-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.24-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.4.1-next.1 + - @backstage/plugin-search-backend-module-explore@0.1.24-next.1 + - @backstage/plugin-search-backend-module-pg@0.5.27-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/plugin-signals-backend@0.1.4-next.1 + - @backstage/plugin-signals-node@0.1.4-next.1 + - @backstage/plugin-techdocs-backend@1.10.5-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + ## 0.2.98-next.0 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 378661ae12..ea37121492 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.98-next.0", + "version": "0.2.98-next.1", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 29ebd0cc2b..3e3f824816 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/core-compat-api +## 0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.2.5-next.0 ### Patch Changes diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index a7bab7b52f..fbea128a68 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.2.5-next.0", + "version": "0.2.5-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 4e8f90b441..2c6d4ea490 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/core-components +## 0.14.6-next.1 + +### Patch Changes + +- 5223c4c: Fixed an internal circular import that broke Jest mocks. +- 5b7b49b: The `SignInPage` guest provider will now fall back to legacy guest auth if the backend request fails, allowing guest auth without a running backend. + ## 0.14.5-next.0 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index df637fa3a8..87f51dff26 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.14.5-next.0", + "version": "0.14.6-next.1", "publishConfig": { "access": "public" }, diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 2706fc14b5..c6338c7243 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/create-app +## 0.5.15-next.1 + +### Patch Changes + +- Bumped create-app version. + ## 0.5.15-next.0 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 28e2fd1087..72d8e00045 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.5.15-next.0", + "version": "0.5.15-next.1", "publishConfig": { "access": "public" }, diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 4b29d5fc31..e4622aab60 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/dev-utils +## 1.0.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/app-defaults@1.5.5-next.1 + - @backstage/integration-react@1.1.26 + ## 1.0.32-next.0 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index e987314494..bac82890d0 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.0.32-next.0", + "version": "1.0.32-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 4aec992020..326002b350 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,12 @@ # e2e-test +## 0.2.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.5.15-next.1 + ## 0.2.16-next.0 ### Patch Changes diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index ae4b15aec0..a41c724d5a 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,7 +1,7 @@ { "name": "e2e-test", "description": "E2E test for verifying Backstage packages", - "version": "0.2.16-next.0", + "version": "0.2.16-next.1", "private": true, "backstage": { "role": "cli" diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index f7d85d19cf..0ac06f4393 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/frontend-app-api +## 0.6.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.6.5-next.0 ### Patch Changes diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index c131e81b41..65045bc6ab 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.6.5-next.0", + "version": "0.6.5-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index 222732d4db..1c681fa6bc 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/frontend-plugin-api +## 0.6.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + ## 0.6.5-next.0 ### Patch Changes diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 802ab13d41..94906cfc67 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.6.5-next.0", + "version": "0.6.5-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index b0f052b3ee..79d54b2f29 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/frontend-test-utils +## 0.1.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-app-api@0.6.5-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.1.7-next.0 ### Patch Changes diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 862d4c449f..af83141368 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.1.7-next.0", + "version": "0.1.7-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 7b2646ad2b..443a5f30b5 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/repo-tools +## 0.8.1-next.1 + +### Patch Changes + +- 9ae9bb2: Update the paths logic in the api reports command to support complex subpaths +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/cli-node@0.2.5 + - @backstage/config-loader@1.8.0 + ## 0.8.1-next.0 ### Patch Changes diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 5d53a9cff6..2e9ec5fab5 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/repo-tools", "description": "CLI for Backstage repo tooling ", - "version": "0.8.1-next.0", + "version": "0.8.1-next.1", "publishConfig": { "access": "public" }, diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 5764b6e71e..99853ed312 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,18 @@ # techdocs-cli-embedded-app +## 0.2.96-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog@1.19.1-next.1 + - @backstage/app-defaults@1.5.5-next.1 + - @backstage/cli@0.26.5-next.0 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-techdocs@1.10.5-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + ## 0.2.96-next.0 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 00083edb6d..ce19840c56 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.96-next.0", + "version": "0.2.96-next.1", "private": true, "backstage": { "role": "frontend" diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 1525b13414..3b2c544e7d 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @techdocs/cli +## 1.8.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-techdocs-node@1.12.4-next.1 + ## 1.8.10-next.0 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 4edb8f47a4..13eed9d249 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.8.10-next.0", + "version": "1.8.11-next.1", "publishConfig": { "access": "public" }, diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 370b221ea2..faac54a107 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-api-docs +## 0.11.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog@1.19.1-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + ## 0.11.5-next.0 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 5d0521f283..c8acdf256f 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.11.5-next.0", + "version": "0.11.5-next.1", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin" diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index b0629a2e92..2b51f92102 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-app-backend +## 0.3.66-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/plugin-app-node@0.1.18-next.1 + ## 0.3.66-next.0 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index d9fba47760..fc4a6a8ded 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.66-next.0", + "version": "0.3.66-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md index 58350a2337..dfc8e08cb9 100644 --- a/plugins/app-node/CHANGELOG.md +++ b/plugins/app-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-app-node +## 0.1.18-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.18-next.0 ### Patch Changes diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json index 9edf577db2..a38ed25171 100644 --- a/plugins/app-node/package.json +++ b/plugins/app-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-node", "description": "Node.js library for the app plugin", - "version": "0.1.18-next.0", + "version": "0.1.18-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index f9a70ea08b..c3e117a8a0 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-app-visualizer +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 0.1.6-next.0 ### Patch Changes diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index c711a9f8b8..0b2370054c 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.6-next.0", + "version": "0.1.6-next.1", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin" diff --git a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md index ed1a3efc23..915c9b0f19 100644 --- a/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-atlassian-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-atlassian-provider +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.10-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-atlassian-provider/package.json b/plugins/auth-backend-module-atlassian-provider/package.json index 386b7e6df1..0f0c3eeda9 100644 --- a/plugins/auth-backend-module-atlassian-provider/package.json +++ b/plugins/auth-backend-module-atlassian-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-atlassian-provider", "description": "The atlassian-provider backend module for the auth plugin.", - "version": "0.1.10-next.0", + "version": "0.1.10-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md index c2c299a055..0b2fa1faf0 100644 --- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-aws-alb-provider +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.10-next.0 ### 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 9e1139c0a3..ae25459ccc 100644 --- a/plugins/auth-backend-module-aws-alb-provider/package.json +++ b/plugins/auth-backend-module-aws-alb-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-aws-alb-provider", "description": "The aws-alb provider module for the Backstage auth backend.", - "version": "0.1.10-next.0", + "version": "0.1.10-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md index 416ad5b0c0..0ed21e2749 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-azure-easyauth-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-azure-easyauth-provider +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.1-next.0 ### 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 8398f16910..e936f48f43 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.1.1-next.0", + "version": "0.1.1-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 cdfbee09c5..4b414015cc 100644 --- a/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-bitbucket-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-bitbucket-provider +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.1-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-bitbucket-provider/package.json b/plugins/auth-backend-module-bitbucket-provider/package.json index 37016f4e5c..ba1184ab0f 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.1.1-next.0", + "version": "0.1.1-next.1", "description": "The bitbucket-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 d3d2fabdb4..1aabb47fb9 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-cloudflare-access-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-cloudflare-access-provider +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.1-next.0 ### 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 52e67198a6..77f77052b5 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.1.1-next.0", + "version": "0.1.1-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 d9d6a0f7f1..03576e316e 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gcp-iap-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-gcp-iap-provider +## 0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.13-next.0 ### 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 a67afd714e..1492ebaaf2 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/package.json +++ b/plugins/auth-backend-module-gcp-iap-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-gcp-iap-provider", "description": "A GCP IAP auth provider module for the Backstage auth backend", - "version": "0.2.13-next.0", + "version": "0.2.13-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-github-provider/CHANGELOG.md b/plugins/auth-backend-module-github-provider/CHANGELOG.md index 960322f0b4..c9da8b5970 100644 --- a/plugins/auth-backend-module-github-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-github-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-github-provider +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.15-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index a1b5abd032..65217f0eeb 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", "description": "The github-provider backend module for the auth plugin.", - "version": "0.1.15-next.0", + "version": "0.1.15-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md index 38148c2d3b..1f4f5ecbf9 100644 --- a/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-gitlab-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-gitlab-provider +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.15-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-gitlab-provider/package.json b/plugins/auth-backend-module-gitlab-provider/package.json index 5226dbd481..5d8ee28020 100644 --- a/plugins/auth-backend-module-gitlab-provider/package.json +++ b/plugins/auth-backend-module-gitlab-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-gitlab-provider", "description": "The gitlab-provider backend module for the auth plugin.", - "version": "0.1.15-next.0", + "version": "0.1.15-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-google-provider/CHANGELOG.md b/plugins/auth-backend-module-google-provider/CHANGELOG.md index 3f89f0a68b..9e7e4043a3 100644 --- a/plugins/auth-backend-module-google-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-google-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-google-provider +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.15-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-google-provider/package.json b/plugins/auth-backend-module-google-provider/package.json index 5b52825f00..a9b529504d 100644 --- a/plugins/auth-backend-module-google-provider/package.json +++ b/plugins/auth-backend-module-google-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-google-provider", "description": "A Google auth provider module for the Backstage auth backend", - "version": "0.1.15-next.0", + "version": "0.1.15-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-guest-provider/CHANGELOG.md b/plugins/auth-backend-module-guest-provider/CHANGELOG.md index befe66eb79..56c4e179b8 100644 --- a/plugins/auth-backend-module-guest-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-guest-provider/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend-module-guest-provider +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.4-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-guest-provider/package.json b/plugins/auth-backend-module-guest-provider/package.json index 827babe0f5..9676afb176 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.1.4-next.0", + "version": "0.1.4-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 49e733cdba..14e4cef987 100644 --- a/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-microsoft-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-microsoft-provider +## 0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.13-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-microsoft-provider/package.json b/plugins/auth-backend-module-microsoft-provider/package.json index 217cc92e67..a8569903cc 100644 --- a/plugins/auth-backend-module-microsoft-provider/package.json +++ b/plugins/auth-backend-module-microsoft-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-microsoft-provider", "description": "The microsoft-provider backend module for the auth plugin.", - "version": "0.1.13-next.0", + "version": "0.1.13-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md index f41ccace1c..747d8fa317 100644 --- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-oauth2-provider +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.15-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json index 16a0eef71c..7cbc5929ae 100644 --- a/plugins/auth-backend-module-oauth2-provider/package.json +++ b/plugins/auth-backend-module-oauth2-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-provider", "description": "The oauth2-provider backend module for the auth plugin.", - "version": "0.1.15-next.0", + "version": "0.1.15-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md index 0fd1fa1d03..acc41221f6 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oauth2-proxy-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-oauth2-proxy-provider +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.11-next.0 ### 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 fdf6ebf59e..cbcef196a5 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/package.json +++ b/plugins/auth-backend-module-oauth2-proxy-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oauth2-proxy-provider", "description": "The oauth2-proxy-provider backend module for the auth plugin.", - "version": "0.1.11-next.0", + "version": "0.1.11-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md index 981e1ed364..6713b3c7b6 100644 --- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-auth-backend-module-oidc-provider +## 0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-backend@0.22.5-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.9-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json index 2d82a36796..c8894fc070 100644 --- a/plugins/auth-backend-module-oidc-provider/package.json +++ b/plugins/auth-backend-module-oidc-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-oidc-provider", "description": "The oidc-provider backend module for the auth plugin.", - "version": "0.1.9-next.0", + "version": "0.1.9-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md index c8c50918dd..43e434a711 100644 --- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-okta-provider +## 0.0.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.0.11-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json index cdf096745e..3006544145 100644 --- a/plugins/auth-backend-module-okta-provider/package.json +++ b/plugins/auth-backend-module-okta-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-okta-provider", "description": "The okta-provider backend module for the auth plugin.", - "version": "0.0.11-next.0", + "version": "0.0.11-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md index 65477d4a1c..a115289d0b 100644 --- a/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-pinniped-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-pinniped-provider +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.12-next.0 ### Patch Changes diff --git a/plugins/auth-backend-module-pinniped-provider/package.json b/plugins/auth-backend-module-pinniped-provider/package.json index 67999e1088..163c3f8e05 100644 --- a/plugins/auth-backend-module-pinniped-provider/package.json +++ b/plugins/auth-backend-module-pinniped-provider/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend-module-pinniped-provider", "description": "The pinniped-provider backend module for the auth plugin.", - "version": "0.1.12-next.0", + "version": "0.1.12-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md index 0105253b11..9245b6ded5 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md +++ b/plugins/auth-backend-module-vmware-cloud-provider/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-backend-module-vmware-cloud-provider +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.10-next.0 ### 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 f09d750a1b..6054f63ea1 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.1.10-next.0", + "version": "0.1.10-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 af0be37f7b..aa33d39713 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-auth-backend +## 0.22.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-backend-module-aws-alb-provider@0.1.10-next.1 + - @backstage/plugin-auth-backend-module-oidc-provider@0.1.9-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-auth-backend-module-atlassian-provider@0.1.10-next.1 + - @backstage/plugin-auth-backend-module-bitbucket-provider@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.1.15-next.1 + - @backstage/plugin-auth-backend-module-gitlab-provider@0.1.15-next.1 + - @backstage/plugin-auth-backend-module-microsoft-provider@0.1.13-next.1 + - @backstage/plugin-auth-backend-module-oauth2-provider@0.1.15-next.1 + - @backstage/plugin-auth-backend-module-okta-provider@0.0.11-next.1 + - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.1.1-next.1 + - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.2.13-next.1 + - @backstage/plugin-auth-backend-module-google-provider@0.1.15-next.1 + - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.1.11-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.22.5-next.0 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 0c3948550c..372b8f8d4b 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.22.5-next.0", + "version": "0.22.5-next.1", "description": "A Backstage backend plugin that handles authentication", "backstage": { "role": "backend-plugin" diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index 61a00fa3cf..af4318227d 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-node +## 0.4.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.4.13-next.0 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index f1c2be92a5..ba05caaad4 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.4.13-next.0", + "version": "0.4.13-next.1", "backstage": { "role": "node-library" }, diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index 370d2d2f47..ebd567184f 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-auth-react +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + ## 0.1.1-next.0 ### Patch Changes diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index c1be6d5f5a..c223b4c40f 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.1-next.0", + "version": "0.1.2-next.1", "description": "Web library for the auth plugin", "backstage": { "role": "web-library" diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 6264bfa38e..be6e743eb9 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.3.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.3.13-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 2bda0583f1..fbc4122fe2 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.3.13-next.0", + "version": "0.3.13-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-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 38c29b1ade..5b7751dc03 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.38-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.38-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index d013c903b3..4df7824fa7 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.1.38-next.0", + "version": "0.1.38-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 fba33c49ad..449c016837 100644 --- a/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-backstage-openapi/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-backstage-openapi +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/backend-openapi-utils@0.1.11-next.1 + ## 0.2.1-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-backstage-openapi/package.json b/plugins/catalog-backend-module-backstage-openapi/package.json index 0acac7f75a..86c7bb02e7 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.2.1-next.0", + "version": "0.2.1-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index 5e3b4be51f..a40ceb8254 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.2.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.5-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index c0863627cf..f6b4620671 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.2.5-next.0", + "version": "0.2.5-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index ed4bbd6696..153494a358 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.32-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index a656cd93a3..e46d0107a5 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.1.32-next.0", + "version": "0.1.32-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-gcp/CHANGELOG.md b/plugins/catalog-backend-module-gcp/CHANGELOG.md index 8a66b028ca..18299d760a 100644 --- a/plugins/catalog-backend-module-gcp/CHANGELOG.md +++ b/plugins/catalog-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-gcp +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.19-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gcp/package.json b/plugins/catalog-backend-module-gcp/package.json index 13a5f542fb..c58c014b0e 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.1.19-next.0", + "version": "0.1.19-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 f4569fde80..8b5e5edd96 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.35-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 674263fb32..c436a07263 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.1.35-next.0", + "version": "0.1.35-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index c5cddaefd5..b1fd9b17d7 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-backend-module-github@0.6.1-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.13-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index a04f926e7b..2838396c88 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.1.13-next.0", + "version": "0.1.13-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/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index b9ca9815fd..5bb58f25c0 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-github +## 0.6.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.6.1-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 8329ed16b8..47a32566af 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.6.1-next.0", + "version": "0.6.1-next.1", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index fc2bd62881..fa0b9f2a24 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.0.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend-module-gitlab@0.3.15-next.3 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.0.1-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 942486d30f..c2d524d9d8 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.0.1-next.0", + "version": "0.0.1-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 5801ccd836..5b36f2570b 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.3.15-next.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.3.15-next.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index b60b87b544..d0d3c876ae 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -72,5 +72,5 @@ ] } }, - "version": "0.3.15-next.2" + "version": "0.3.15-next.3" } diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 06249b41ff..2f4505abba 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.4.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.4.23-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index 8e428e19a6..6c9f930d33 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.4.23-next.0", + "version": "0.4.23-next.1", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 5deb8cfe2b..10a774ffad 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.5.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + ## 0.5.34-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index d9bcde8a2d..82558bc6d8 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.5.34-next.0", + "version": "0.5.34-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-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 9632fb3e14..8b7dcb9c80 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.5.26-next.1 + +### Patch Changes + +- 49eab29: Fixed disabling of user photo fetching. Previously, the config value wasn't propagated properly, so user photos was still being fetched despite disabled by config. +- 6e370e6: Handle fetching huge amounts of users from Azure without crashing +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.5.26-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 5978536c13..7a89b0c507 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.5.26-next.0", + "version": "0.5.26-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-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index 57a217ef1a..f7303d298e 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend@1.22.0-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.36-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 0cb5409a7b..be2efd8c93 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.1.36-next.0", + "version": "0.1.36-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 5f120d1615..b2604c469e 100644 --- a/plugins/catalog-backend-module-puppetdb/CHANGELOG.md +++ b/plugins/catalog-backend-module-puppetdb/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-puppetdb +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.24-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-puppetdb/package.json b/plugins/catalog-backend-module-puppetdb/package.json index 79decb21cf..6c38b658e3 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.1.24-next.0", + "version": "0.1.24-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 c53f721504..b7b46ed403 100644 --- a/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md +++ b/plugins/catalog-backend-module-scaffolder-entity-model/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-scaffolder-entity-model +## 0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.16-next.0 ### 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 1436ac1b96..6b8bc12493 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.1.16-next.0", + "version": "0.1.16-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 f9f764f608..a53e36a696 100644 --- a/plugins/catalog-backend-module-unprocessed/CHANGELOG.md +++ b/plugins/catalog-backend-module-unprocessed/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-unprocessed +## 0.4.5-next.1 + +### Patch Changes + +- 6f5a3a3: Correctly convert owner to string in case owner has not been provided +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.4.5-next.0 ### Patch Changes diff --git a/plugins/catalog-backend-module-unprocessed/package.json b/plugins/catalog-backend-module-unprocessed/package.json index 6988f76d51..38d96e62e9 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.4.5-next.0", + "version": "0.4.5-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 62e517919f..426333c567 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend +## 1.22.0-next.1 + +### Patch Changes + +- 8479a0b: Fixed bug in stitching queue gauge that included entities that are scheduled in the future. +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-search-backend-module-catalog@0.1.24-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/backend-openapi-utils@0.1.11-next.1 + ## 1.22.0-next.0 ### Minor Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 1f09408802..dd5f15f6a6 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "1.22.0-next.0", + "version": "1.22.0-next.1", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin" diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 656af9172d..e1b79d8d13 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-graph +## 0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + ## 0.4.5-next.0 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 3872bccc6b..d0363f5376 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.5-next.0", + "version": "0.4.5-next.1", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index c5873263f3..bfdfabba5a 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-import +## 0.10.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/core-compat-api@0.2.5-next.1 + ## 0.10.11-next.0 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 3fe969ac7b..a766110a4d 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.10.11-next.0", + "version": "0.10.11-next.1", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index 563db2bf53..a2405a7eef 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-node +## 1.11.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 1.11.2-next.0 ### Patch Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 6787e28d9d..ebfea63cef 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-node", - "version": "1.11.2-next.0", + "version": "1.11.2-next.1", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", "backstage": { "role": "node-library" diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 84236540e9..190773044a 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-react +## 1.11.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + ## 1.11.4-next.0 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 43128252a6..041253f223 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "1.11.4-next.0", + "version": "1.11.4-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index 67b2fc3d83..06d4344889 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + ## 0.2.4-next.0 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index 713844426c..eb35eae31e 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.4-next.0", + "version": "0.2.4-next.1", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 40f2c250f9..300083da1c 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog +## 1.19.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + ## 1.19.1-next.0 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 346676a17f..207717e1c9 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.19.1-next.0", + "version": "1.19.1-next.1", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin" diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 614000fb5b..792691f33d 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-config-schema +## 0.1.55-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + ## 0.1.55-next.0 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 8ff10e1197..19f2401602 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.55-next.0", + "version": "0.1.55-next.1", "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 99532e3d19..1d4247cb9f 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-devtools-backend +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/config-loader@1.8.0 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.3.4-next.0 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index 3b7dbe23f8..e9c6f48b49 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.3.4-next.0", + "version": "0.3.4-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index 8d504c3004..b618417ce0 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-devtools +## 0.1.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + ## 0.1.14-next.0 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 147991ce09..64b3626546 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.14-next.0", + "version": "0.1.14-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md index 3807e3771d..498f89b6fa 100644 --- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md +++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-events-backend-module-aws-sqs +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.3.4-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json index d5655cead1..ab33e2c451 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.3.4-next.0", + "version": "0.3.4-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md index f0c82485a4..1fbb360beb 100644 --- a/plugins/events-backend-module-azure/CHANGELOG.md +++ b/plugins/events-backend-module-azure/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-azure +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.4-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json index 613620d36c..f7ade8b3a2 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.4-next.0", + "version": "0.2.4-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md index b77cd31c94..b649c38b5b 100644 --- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-bitbucket-cloud +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.4-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json index 317fbaecbd..baed6171fd 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.4-next.0", + "version": "0.2.4-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md index d676451dca..57c6390296 100644 --- a/plugins/events-backend-module-gerrit/CHANGELOG.md +++ b/plugins/events-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-gerrit +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.4-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json index bd7de4a33a..b3f8161604 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.4-next.0", + "version": "0.2.4-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index 111ac1ba80..ae5ee59263 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-github +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.4-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index 440ca61b7d..9fb876f714 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.2.4-next.0", + "version": "0.2.4-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md index 9a9f0a0956..73c4c7e281 100644 --- a/plugins/events-backend-module-gitlab/CHANGELOG.md +++ b/plugins/events-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-events-backend-module-gitlab +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.4-next.0 ### Patch Changes diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json index 583f929fdd..e10b26736f 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.2.4-next.0", + "version": "0.2.4-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md index 76e0e9b90a..f5ba8b9609 100644 --- a/plugins/events-backend-test-utils/CHANGELOG.md +++ b/plugins/events-backend-test-utils/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-events-backend-test-utils +## 0.1.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-events-node@0.3.4-next.1 + ## 0.1.28-next.0 ### Patch Changes diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json index 8c39378840..f766130efc 100644 --- a/plugins/events-backend-test-utils/package.json +++ b/plugins/events-backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-test-utils", - "version": "0.1.28-next.0", + "version": "0.1.28-next.1", "description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node", "backstage": { "role": "node-library" diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md index d839799775..1bb33b6db1 100644 --- a/plugins/events-backend/CHANGELOG.md +++ b/plugins/events-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-events-backend +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json index ce694628d0..dc36a33d07 100644 --- a/plugins/events-backend/package.json +++ b/plugins/events-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend", - "version": "0.3.5-next.0", + "version": "0.3.5-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md index c21ffbcb1f..185da613eb 100644 --- a/plugins/events-node/CHANGELOG.md +++ b/plugins/events-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-events-node +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.3.4-next.0 ### Patch Changes diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json index deadde1f7f..7312950a99 100644 --- a/plugins/events-node/package.json +++ b/plugins/events-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-node", - "version": "0.3.4-next.0", + "version": "0.3.4-next.1", "description": "The plugin-events-node module for @backstage/plugin-events-backend", "backstage": { "role": "node-library" diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index db22f83859..2d6dee39e2 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @internal/plugin-todo-list-backend +## 1.0.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 1.0.27-next.0 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 8a8d32f9b0..cd45af6d37 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.27-next.0", + "version": "1.0.27-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 10f1a6ddcb..0962426ac3 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list +## 1.0.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + ## 1.0.27-next.0 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 377277bfbe..849b96433a 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.27-next.0", + "version": "1.0.27-next.1", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 60d7e72fda..b1f8f6793e 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-home-react +## 0.1.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + ## 0.1.13-next.0 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 8e81326138..bb04ce8211 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-react", - "version": "0.1.13-next.0", + "version": "0.1.13-next.1", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library" diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index bfc7d8cce2..565d82f395 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-home +## 0.7.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-home-react@0.1.13-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + ## 0.7.4-next.0 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index 82a5401fe3..0058c35e00 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.7.4-next.0", + "version": "0.7.4-next.1", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index ade81960a4..a1000aea2c 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-backend +## 0.17.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-kubernetes-node@0.1.12-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.17.1-next.0 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index c1240f6367..a8634d8855 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.17.1-next.0", + "version": "0.17.1-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index 46dd8aa084..8ebfd45d77 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-kubernetes-react@0.3.5-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + ## 0.0.11-next.0 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 5381729509..1402c79951 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.11-next.0", + "version": "0.0.11-next.1", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin" diff --git a/plugins/kubernetes-node/CHANGELOG.md b/plugins/kubernetes-node/CHANGELOG.md index 66fc5910aa..89c7a58526 100644 --- a/plugins/kubernetes-node/CHANGELOG.md +++ b/plugins/kubernetes-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-kubernetes-node +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.12-next.0 ### Patch Changes diff --git a/plugins/kubernetes-node/package.json b/plugins/kubernetes-node/package.json index a2a1ce7a8a..79e304671a 100644 --- a/plugins/kubernetes-node/package.json +++ b/plugins/kubernetes-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-node", - "version": "0.1.12-next.0", + "version": "0.1.12-next.1", "description": "Node.js library for the kubernetes plugin", "backstage": { "role": "node-library" diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index d7159fab75..e05d103ad6 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-kubernetes-react +## 0.3.5-next.1 + +### Patch Changes + +- 3102a99: add the namespace label to CronJobDrawer & IngressDrawer. +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + ## 0.3.5-next.0 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index d939dd4eac..7ff42ab26c 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-react", "description": "Web library for the kubernetes-react plugin", - "version": "0.3.5-next.0", + "version": "0.3.5-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 8426ffb55d..de0f70736a 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-kubernetes +## 0.11.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-kubernetes-react@0.3.5-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + ## 0.11.10-next.0 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 418f9957aa..e75d503558 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.11.10-next.0", + "version": "0.11.10-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md new file mode 100644 index 0000000000..065f8cb533 --- /dev/null +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -0,0 +1,11 @@ +# @backstage/plugin-notifications-backend-module-email + +## 0.0.1-next.0 + +### Patch Changes + +- dbf2696: Allow sending notifications by email with the new notifications module +- Updated dependencies + - @backstage/plugin-notifications-node@0.1.4-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index dfff036a13..94b5e10578 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.0.0", + "version": "0.0.1-next.0", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md index b3fbe6ae69..389fa25f61 100644 --- a/plugins/notifications-backend/CHANGELOG.md +++ b/plugins/notifications-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-notifications-backend +## 0.2.1-next.1 + +### Patch Changes + +- 295c05d: Support for filtering entities from notification recipients after resolving them from the recipients +- cba628a: Add possibility to generate random notifications on the fly in local development +- Updated dependencies + - @backstage/plugin-notifications-node@0.1.4-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-signals-node@0.1.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.1-next.0 ### Patch Changes diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json index 1a16996e90..9f7f427895 100644 --- a/plugins/notifications-backend/package.json +++ b/plugins/notifications-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend", - "version": "0.2.1-next.0", + "version": "0.2.1-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md index 867ea0a79f..2b6321a3f8 100644 --- a/plugins/notifications-node/CHANGELOG.md +++ b/plugins/notifications-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-notifications-node +## 0.1.4-next.1 + +### Patch Changes + +- 295c05d: Support for filtering entities from notification recipients after resolving them from the recipients +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-signals-node@0.1.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.4-next.0 ### Patch Changes diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json index 9d72b695a3..1abfcc07ba 100644 --- a/plugins/notifications-node/package.json +++ b/plugins/notifications-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-node", - "version": "0.1.4-next.0", + "version": "0.1.4-next.1", "description": "Node.js library for the notifications plugin", "backstage": { "role": "node-library" diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index eb0e44064e..78c8c96f1d 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-notifications +## 0.2.1-next.1 + +### Patch Changes + +- f730c0b: The user can newly mark all unread messages as read at one click. +- bfcb2f1: Allow showing notifications as snackbars in the UI +- f6633ca: Add option to set the notification as read automatically when the notification link is opened +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + ## 0.2.1-next.0 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index 9da48caefc..bdc96a921b 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.2.1-next.0", + "version": "0.2.1-next.1", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 1d25ba359c..2ad74d1db0 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-org-react +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + ## 0.1.24-next.0 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index 985e05b3c5..0567691a3b 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.24-next.0", + "version": "0.1.24-next.1", "backstage": { "role": "web-library" }, diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 17cb3d14a9..3d2bedca68 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-org +## 0.6.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + ## 0.6.25-next.0 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index c07ef33428..b00e6f2bb2 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.25-next.0", + "version": "0.6.25-next.1", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin" diff --git a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md index 7c402fa207..90874346ad 100644 --- a/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md +++ b/plugins/permission-backend-module-policy-allow-all/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-backend-module-allow-all-policy +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.15-next.0 ### Patch Changes diff --git a/plugins/permission-backend-module-policy-allow-all/package.json b/plugins/permission-backend-module-policy-allow-all/package.json index 1a8d44f652..ef5dfade7f 100644 --- a/plugins/permission-backend-module-policy-allow-all/package.json +++ b/plugins/permission-backend-module-policy-allow-all/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend-module-allow-all-policy", - "version": "0.1.15-next.0", + "version": "0.1.15-next.1", "description": "Allow all policy backend module for the permission plugin.", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index d4b3072fe9..cc5b497a2c 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-permission-backend +## 0.5.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.5.42-next.0 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 6c7c00eb79..3671e2b5a4 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.42-next.0", + "version": "0.5.42-next.1", "backstage": { "role": "backend-plugin" }, diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 9d93fbe268..fd84633615 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-node +## 0.7.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.7.29-next.0 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index e367d0abd3..c510014089 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.7.29-next.0", + "version": "0.7.29-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 0e3d8475b0..b133154c85 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-proxy-backend +## 0.4.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.4.16-next.0 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index b955a40211..9430f7120d 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.4.16-next.0", + "version": "0.4.16-next.1", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index a205da59c8..37987ca19f 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.10-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index 46cb67fe74..efd61bae83 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.1.10-next.0", + "version": "0.1.10-next.1", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index 67cbfa1af5..099d4d6fd0 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.8-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index 9d92a7d4e6..51df2f87ed 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.1.8-next.0", + "version": "0.1.8-next.1", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index 29b4d61ddc..83a345f463 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.8-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 5213d00079..2c195c8d88 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.1.8-next.0", + "version": "0.1.8-next.1", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index 0203b51ac6..31b5cfad46 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.8-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index cd6d38643c..ad07c7208d 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.2.8-next.0", + "version": "0.2.8-next.1", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index b001283394..1bbc8a234d 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.2.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.19-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index 175f12a605..ddf56c91d1 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.2.19-next.0", + "version": "0.2.19-next.1", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 825a90eae1..01535ca917 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.42-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 0b46079953..b4206baff2 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.2.42-next.0", + "version": "0.2.42-next.1", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index 9a2d2fd32a..959d01271b 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.10-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 78ebe2a2ed..d7f9bfce66 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.1.10-next.0", + "version": "0.1.10-next.1", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 09f5faafcd..306fbd6812 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.8-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index 6b8740f791..77bc3cd26e 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.1.8-next.0", + "version": "0.1.8-next.1", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index 8fa18de2bb..bcef59a58c 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.2.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.8-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index 925efa8c37..8491b5877a 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.2.8-next.0", + "version": "0.2.8-next.1", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index f155b66762..58f4cfdfb0 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.3.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.3.4-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 95059f2513..941dcb5aef 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.3.4-next.0", + "version": "0.3.4-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 541e175c11..4553938eab 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.35-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.4.35-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 26fd91b1b6..a523ed9a8d 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.4.35-next.0", + "version": "0.4.35-next.1", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index 8119d5a446..d3685153d5 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.26-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 9321e7ef1e..bd7555a8c2 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.1.26-next.0", + "version": "0.1.26-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 4772114265..96b7222d0f 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/plugin-scaffolder-node-test-utils@0.1.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.3.1-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index c091dfbab9..c0f0c85f72 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.3.1-next.0", + "version": "0.3.1-next.1", "backstage": { "role": "backend-plugin-module" }, diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index e9bb1c2b05..0ed790e3be 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-scaffolder-backend +## 1.22.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.1.16-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-scaffolder-backend-module-github@0.2.8-next.1 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.3.4-next.1 + - @backstage/plugin-scaffolder-backend-module-azure@0.1.10-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.2.8-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.1.8-next.1 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.1.8-next.1 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.1.10-next.1 + - @backstage/plugin-scaffolder-backend-module-gitea@0.1.8-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 1.22.5-next.0 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index d107d578cb..338dc290e1 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.22.5-next.0", + "version": "1.22.5-next.1", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin" diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 4b6da80009..4a9e1b870b 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-scaffolder-common +## 1.5.2-next.1 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder + ## 1.5.2-next.0 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index d11e576d52..614bd16a25 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-common", - "version": "1.5.2-next.0", + "version": "1.5.2-next.1", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", "backstage": { "role": "common-library" diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index 3fe4c45621..5914c8fdee 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-scaffolder-node@0.4.4-next.1 + - @backstage/backend-test-utils@0.3.8-next.1 + ## 0.1.4-next.0 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 427bb5664a..99adae5ab7 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.1.4-next.0", + "version": "0.1.4-next.1", "backstage": { "role": "node-library" }, diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index 84cb98bf19..f1739c59e4 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-node +## 0.4.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.4.4-next.0 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index d7d6105689..f0776e6491 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.4.4-next.0", + "version": "0.4.4-next.1", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library" diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index 2236048243..9c3a25638d 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-react +## 1.8.5-next.1 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + ## 1.8.5-next.0 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 352478d99d..b7b7b20014 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.8.5-next.0", + "version": "1.8.5-next.1", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library" diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 69b570e947..9a7ecd2e50 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder +## 1.19.4-next.1 + +### Patch Changes + +- 9156654: Capturing more event clicks for scaffolder +- Updated dependencies + - @backstage/plugin-scaffolder-common@1.5.2-next.1 + - @backstage/plugin-scaffolder-react@1.8.5-next.1 + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/core-compat-api@0.2.5-next.1 + ## 1.19.4-next.0 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 96227ed494..14c1d5b2de 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.19.4-next.0", + "version": "1.19.4-next.1", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin" diff --git a/plugins/search-backend-module-catalog/CHANGELOG.md b/plugins/search-backend-module-catalog/CHANGELOG.md index 51b9f5674a..fb9977e0ea 100644 --- a/plugins/search-backend-module-catalog/CHANGELOG.md +++ b/plugins/search-backend-module-catalog/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-search-backend-module-catalog +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.23-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-catalog/package.json b/plugins/search-backend-module-catalog/package.json index 1d9a4a37c7..ad03071eaf 100644 --- a/plugins/search-backend-module-catalog/package.json +++ b/plugins/search-backend-module-catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-catalog", - "version": "0.1.23-next.0", + "version": "0.1.24-next.1", "description": "A module for the search backend that exports catalog modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index ca4d6ad599..1af0204403 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 1.4.1-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index f13616d60b..85489b4043 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "1.4.1-next.0", + "version": "1.4.1-next.1", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-explore/CHANGELOG.md b/plugins/search-backend-module-explore/CHANGELOG.md index 94519f4751..ce6499ab4e 100644 --- a/plugins/search-backend-module-explore/CHANGELOG.md +++ b/plugins/search-backend-module-explore/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-explore +## 0.1.24-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.23-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-explore/package.json b/plugins/search-backend-module-explore/package.json index 612e42df10..d181d70cbf 100644 --- a/plugins/search-backend-module-explore/package.json +++ b/plugins/search-backend-module-explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-explore", - "version": "0.1.23-next.0", + "version": "0.1.24-next.1", "description": "A module for the search backend that exports explore modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 74e9bc9a1a..37386a7fd1 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-backend-module-pg +## 0.5.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.5.27-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 3666e85273..3aec50e503 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-pg", - "version": "0.5.27-next.0", + "version": "0.5.27-next.1", "description": "A module for the search backend that implements search using PostgreSQL", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md index 80447b1b78..c8faaf7609 100644 --- a/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md +++ b/plugins/search-backend-module-stack-overflow-collator/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend-module-stack-overflow-collator +## 0.1.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.11-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-stack-overflow-collator/package.json b/plugins/search-backend-module-stack-overflow-collator/package.json index ed9ff057aa..3c76ed164b 100644 --- a/plugins/search-backend-module-stack-overflow-collator/package.json +++ b/plugins/search-backend-module-stack-overflow-collator/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-stack-overflow-collator", - "version": "0.1.11-next.0", + "version": "0.1.11-next.1", "description": "A module for the search backend that exports stack overflow modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index aeb6999a3c..e79c98ab5a 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.1.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/plugin-techdocs-node@1.12.4-next.1 + - @backstage/plugin-catalog-node@1.11.2-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.23-next.0 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index 1dea62aa74..09aa136f0e 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.1.23-next.0", + "version": "0.1.23-next.1", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module" diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 9e79ded222..854996ba53 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-search-backend-node +## 1.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-tasks@0.5.23-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 1.2.22-next.0 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index ac59d7eab2..c734681980 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-node", - "version": "1.2.22-next.0", + "version": "1.2.22-next.1", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", "backstage": { "role": "node-library" diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index f064499e07..5dabd9f1a5 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search-backend +## 1.5.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/repo-tools@0.8.1-next.1 + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-permission-node@0.7.29-next.1 + - @backstage/plugin-search-backend-node@1.2.22-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + - @backstage/backend-openapi-utils@0.1.11-next.1 + ## 1.5.8-next.0 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 34ea122db9..c06b99ec92 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "1.5.8-next.0", + "version": "1.5.8-next.1", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin" diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index b7ef7b85fc..c4e75fa41a 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-react +## 1.7.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + ## 1.7.11-next.0 ### Patch Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 00cc8032f2..274deae1d7 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.7.11-next.0", + "version": "1.7.11-next.1", "backstage": { "role": "web-library" }, diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index bd07286578..cde6f55c14 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-search +## 1.4.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + ## 1.4.11-next.0 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 5aeeecbfd3..06f5ff47e7 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.11-next.0", + "version": "1.4.11-next.1", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin" diff --git a/plugins/signals-backend/CHANGELOG.md b/plugins/signals-backend/CHANGELOG.md index a787465282..fcd34ead2e 100644 --- a/plugins/signals-backend/CHANGELOG.md +++ b/plugins/signals-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-signals-backend +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/plugin-signals-node@0.1.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.4-next.0 ### Patch Changes diff --git a/plugins/signals-backend/package.json b/plugins/signals-backend/package.json index ac1e74332b..5d961c71ef 100644 --- a/plugins/signals-backend/package.json +++ b/plugins/signals-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals-backend", - "version": "0.1.4-next.0", + "version": "0.1.4-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/signals-node/CHANGELOG.md b/plugins/signals-node/CHANGELOG.md index 3599e86aaa..ea703d4291 100644 --- a/plugins/signals-node/CHANGELOG.md +++ b/plugins/signals-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-signals-node +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/plugin-events-node@0.3.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.1.4-next.0 ### Patch Changes diff --git a/plugins/signals-node/package.json b/plugins/signals-node/package.json index ba454635ac..970b89aca2 100644 --- a/plugins/signals-node/package.json +++ b/plugins/signals-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-signals-node", "description": "Node.js library for the signals plugin", - "version": "0.1.4-next.0", + "version": "0.1.4-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/signals/CHANGELOG.md b/plugins/signals/CHANGELOG.md index 3f0e646944..fdfddfdf2a 100644 --- a/plugins/signals/CHANGELOG.md +++ b/plugins/signals/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-signals +## 0.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + ## 0.0.6-next.0 ### Patch Changes diff --git a/plugins/signals/package.json b/plugins/signals/package.json index 9f248def27..7d5fb3bcf9 100644 --- a/plugins/signals/package.json +++ b/plugins/signals/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-signals", - "version": "0.0.6-next.0", + "version": "0.0.6-next.1", "backstage": { "role": "frontend-plugin" }, diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 4ce9d18415..5151796171 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.19.1-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-techdocs@1.10.5-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + ## 1.0.32-next.0 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index ac7d3cb752..6a6edcd5fa 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.32-next.0", + "version": "1.0.32-next.1", "backstage": { "role": "web-library" }, diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 86056032a6..53ee8b3681 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-techdocs-backend +## 1.10.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.1.23-next.1 + - @backstage/plugin-techdocs-node@1.12.4-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 1.10.5-next.0 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index bd3cee3ad6..013029bb0b 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "1.10.5-next.0", + "version": "1.10.5-next.1", "description": "The Backstage backend plugin that renders technical documentation for your components", "backstage": { "role": "backend-plugin" diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index d577b465b5..b914a8e3b1 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + ## 1.1.10-next.0 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 793018b1e1..5c8a0248ee 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", - "version": "1.1.10-next.0", + "version": "1.1.10-next.1", "description": "Plugin module for contributed TechDocs Addons", "backstage": { "role": "frontend-plugin-module" diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index 73fae3afe0..23f5d333d3 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-techdocs-node +## 1.12.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 1.12.4-next.0 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 0c590816e7..1ff7b305f6 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-node", - "version": "1.12.4-next.0", + "version": "1.12.4-next.1", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", "backstage": { "role": "node-library" diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 95f7bd8eaa..9a13bd2149 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-techdocs-react +## 1.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + ## 1.2.4-next.0 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index 4431c8f107..ebba33cdca 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-react", - "version": "1.2.4-next.0", + "version": "1.2.4-next.1", "description": "Shared frontend utilities for TechDocs and Addons", "backstage": { "role": "web-library" diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index a1d4d9c7ee..ee3071b030 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-techdocs +## 1.10.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/integration-react@1.1.26 + - @backstage/plugin-auth-react@0.1.2-next.1 + - @backstage/plugin-search-react@1.7.11-next.1 + - @backstage/plugin-techdocs-react@1.2.4-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + ## 1.10.5-next.0 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 46e24c8a44..7e06c8f128 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "1.10.5-next.0", + "version": "1.10.5-next.1", "description": "The Backstage plugin that renders technical documentation for your components", "backstage": { "role": "frontend-plugin" diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 7aa3b634d1..57e3663d5f 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-user-settings-backend +## 0.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.22.0-next.1 + - @backstage/plugin-auth-node@0.4.13-next.1 + - @backstage/backend-plugin-api@0.6.18-next.1 + ## 0.2.17-next.0 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index b8e9949bcf..f8b46d8d3f 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings-backend", - "version": "0.2.17-next.0", + "version": "0.2.17-next.1", "description": "The Backstage backend plugin to manage user settings", "backstage": { "role": "backend-plugin" diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 95fbfa85eb..2611a7d18a 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-user-settings +## 0.8.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.14.6-next.1 + - @backstage/plugin-catalog-react@1.11.4-next.1 + - @backstage/frontend-plugin-api@0.6.5-next.1 + - @backstage/core-compat-api@0.2.5-next.1 + ## 0.8.6-next.0 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 40c1b0772f..3461d220d6 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.8.6-next.0", + "version": "0.8.6-next.1", "description": "A Backstage plugin that provides a settings page", "backstage": { "role": "frontend-plugin" From d4a3b10f02bae9888166cd386d1d875c364cdee2 Mon Sep 17 00:00:00 2001 From: secustor <sebastian@poxhofer.at> Date: Tue, 30 Apr 2024 14:00:43 +0200 Subject: [PATCH 173/234] feat: expose DD RUM sessionSampleRate and sessionReplaySampleRate Signed-off-by: secustor <sebastian@poxhofer.at> --- app-config.yaml | 3 + docs/integrations/datadog-rum/installation.md | 2 + packages/app-next/public/index.html | 5 +- packages/app/public/index.html | 5 +- packages/cli/package.json | 62 +++++++++++-------- 5 files changed, 49 insertions(+), 28 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 9e5b5c468c..cb70170541 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -9,6 +9,9 @@ app: # applicationId: qwerty # site: # datadoghq.eu default = datadoghq.com # env: # optional + # sessionSampleRate: 100 + # sessionReplaySampleRate: 0 + support: url: https://github.com/backstage/backstage/issues # Used by common ErrorPage items: # Used by common SupportButton component diff --git a/docs/integrations/datadog-rum/installation.md b/docs/integrations/datadog-rum/installation.md index 32eb2b3de8..4dc6c24134 100644 --- a/docs/integrations/datadog-rum/installation.md +++ b/docs/integrations/datadog-rum/installation.md @@ -22,6 +22,8 @@ app: applicationId: qwerty # site: datadoghq.eu # env: 'staging' + # sessionSampleRate: 100 + # sessionReplaySampleRate: 0 ``` If your [`app-config.yaml`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/app-config.yaml#L5) file does not have this configuration, you may have to adjust your [`packages/app/public/index.html`](https://github.com/backstage/backstage/blob/e0506af8fc54074a160fb91c83d6cae8172d3bb3/packages/app/public/index.html#L69) to include the Datadog RUM `init()` section manually. diff --git a/packages/app-next/public/index.html b/packages/app-next/public/index.html index 1bbfda590d..63ab0bec0c 100644 --- a/packages/app-next/public/index.html +++ b/packages/app-next/public/index.html @@ -72,7 +72,10 @@ site: '<%= config.getOptionalString("app.datadogRum.site") || "datadoghq.com" %>', service: 'backstage', env: '<%= config.getString("app.datadogRum.env") %>', - sampleRate: 100, + sampleRate: + '<%= config.getOptionalNumber("app.datadogRum.sessionSampleRate") || 100 %>', + sessionReplaySampleRate: + '<%= config.getOptionalNumber("app.datadogRum.sessionReplaySampleRate") || 0 %>', trackInteractions: true, }); }); diff --git a/packages/app/public/index.html b/packages/app/public/index.html index 631666da28..9a8267e98e 100644 --- a/packages/app/public/index.html +++ b/packages/app/public/index.html @@ -72,7 +72,10 @@ site: '<%= config.getOptionalString("app.datadogRum.site") || "datadoghq.com" %>', service: 'backstage', env: '<%= config.getString("app.datadogRum.env") %>', - sampleRate: 100, + sampleRate: + '<%= config.getOptionalNumber("app.datadogRum.sessionSampleRate") || 100 %>', + sessionReplaySampleRate: + '<%= config.getOptionalNumber("app.datadogRum.sessionReplaySampleRate") || 0 %>', trackInteractions: true, }); }); diff --git a/packages/cli/package.json b/packages/cli/package.json index ecdfa590d6..c8baae4d4d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,34 +1,46 @@ { "name": "@backstage/cli", - "description": "CLI for developing Backstage plugins and apps", "version": "0.26.5-next.0", - "publishConfig": { - "access": "public" - }, + "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" }, + "publishConfig": { + "access": "public" + }, + "keywords": [ + "backstage" + ], "homepage": "https://backstage.io", "repository": { "type": "git", "url": "https://github.com/backstage/backstage", "directory": "packages/cli" }, - "keywords": [ - "backstage" - ], "license": "Apache-2.0", "main": "dist/index.cjs.js", - "scripts": { - "build": "backstage-cli package build", - "lint": "backstage-cli package lint", - "test": "backstage-cli package test", - "clean": "backstage-cli package clean", - "start": "nodemon --" - }, "bin": { "backstage-cli": "bin/backstage-cli" }, + "files": [ + "asset-types", + "templates", + "config", + "bin", + "dist/**/*.js" + ], + "scripts": { + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "start": "nodemon --", + "test": "backstage-cli package test" + }, + "nodemonConfig": { + "exec": "bin/backstage-cli", + "ext": "ts", + "watch": "./src" + }, "dependencies": { "@backstage/catalog-model": "workspace:^", "@backstage/cli-common": "workspace:^", @@ -197,18 +209,6 @@ "optional": true } }, - "files": [ - "asset-types", - "templates", - "config", - "bin", - "dist/**/*.js" - ], - "nodemonConfig": { - "watch": "./src", - "exec": "bin/backstage-cli", - "ext": "ts" - }, "configSchema": { "$schema": "https://backstage.io/schema/config-v1", "title": "@backstage/cli", @@ -248,6 +248,16 @@ "type": "string", "visibility": "frontend", "description": "site for Datadog RUM events" + }, + "sessionSampleRate": { + "type": "number", + "visibility": "frontend", + "description": "sample rate of Datadog RUM events" + }, + "sessionReplaySampleRate": { + "type": "number", + "visibility": "frontend", + "description": "sample rate of session replays based upon already sampled Datadog RUM events" } }, "required": [ From a874dd1524a3b665c64d9b693a2935d3e89fc83d Mon Sep 17 00:00:00 2001 From: Andre Wanlin <awanlin@spotify.com> Date: Fri, 12 Apr 2024 14:01:02 -0500 Subject: [PATCH 174/234] Added SingIn Resolvers Config Details Signed-off-by: Andre Wanlin <awanlin@spotify.com> --- docs/auth/atlassian/provider.md | 7 +++++ docs/auth/github/provider.md | 7 +++++ docs/auth/gitlab/provider.md | 7 +++++ docs/auth/google/gcp-iap-auth.md | 28 +++++++++++++++++++ docs/auth/google/provider.md | 7 +++++ docs/auth/microsoft/provider.md | 7 +++++ docs/auth/oauth2-proxy/provider.md | 10 ++++++- docs/auth/okta/provider.md | 7 +++++ docs/auth/vmware-cloud/provider.md | 7 +++++ .../building-backends/08-migrating.md | 4 +-- 10 files changed, 88 insertions(+), 3 deletions(-) diff --git a/docs/auth/atlassian/provider.md b/docs/auth/atlassian/provider.md index 2680370d93..cdd18bdc33 100644 --- a/docs/auth/atlassian/provider.md +++ b/docs/auth/atlassian/provider.md @@ -47,8 +47,15 @@ auth: clientId: ${AUTH_ATLASSIAN_CLIENT_ID} clientSecret: ${AUTH_ATLASSIAN_CLIENT_SECRET} scope: ${AUTH_ATLASSIAN_SCOPES} + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + - resolver: usernameMatchingUserEntityName ``` +> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + The Atlassian provider is a structure with three configuration keys: - `clientId`: The Key you generated in the developer console. diff --git a/docs/auth/github/provider.md b/docs/auth/github/provider.md index 353b8ebe08..2ff8e0836d 100644 --- a/docs/auth/github/provider.md +++ b/docs/auth/github/provider.md @@ -41,8 +41,15 @@ auth: clientSecret: ${AUTH_GITHUB_CLIENT_SECRET} ## uncomment if using GitHub Enterprise # enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL} + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + - resolver: usernameMatchingUserEntityName ``` +> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + The GitHub provider is a structure with three configuration keys: - `clientId`: The client ID that you generated on GitHub, e.g. diff --git a/docs/auth/gitlab/provider.md b/docs/auth/gitlab/provider.md index ed7fa6aec3..fc4dac8164 100644 --- a/docs/auth/gitlab/provider.md +++ b/docs/auth/gitlab/provider.md @@ -43,8 +43,15 @@ auth: # audience: https://gitlab.company.com ## uncomment if using a custom redirect URI # callbackUrl: https://${BASE_URL}/api/auth/gitlab/handler/frame + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + - resolver: usernameMatchingUserEntityName ``` +> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + The GitLab provider is a structure with three configuration keys: - `clientId`: The Application ID that you generated on GitLab, e.g. diff --git a/docs/auth/google/gcp-iap-auth.md b/docs/auth/google/gcp-iap-auth.md index 410008b22e..911ee4e263 100644 --- a/docs/auth/google/gcp-iap-auth.md +++ b/docs/auth/google/gcp-iap-auth.md @@ -27,8 +27,15 @@ auth: gcp-iap: audience: '/projects/<project number>/global/backendServices/<backend service id>' jwtHeader: x-custom-header # Optional: Only if you are using a custom header for the IAP JWT + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + - resolver: emailMatchingUserEntityAnnotation ``` +> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + The full `audience` value can be obtained by visiting your [Identity-Aware Proxy Google Cloud console](https://console.cloud.google.com/security/iap), selecting your project, finding your Backend Service to proxy, clicking the 3 vertical dots then "Get JWT Audience Code", and copying from the resulting popup, which will look similar to the following: ![Identity-Aware Proxy JWT Audience Code popup](../../assets/auth/gcp-iap-jwt-audience-code-popup.png) @@ -94,6 +101,27 @@ Now the backend is ready to serve auth requests on the `/api/auth/gcp-iap/refresh` endpoint. All that's left is to update the frontend sign-in mechanism to poll that endpoint through the IAP, on the user's behalf. +### New Backend System + +There is a module for this provider that works with the new backend system. + +First you'll want to run this command to add the module: + +```sh + yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-gcp-iap-provider +``` + +Then you will need to add this to your backend: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +backend.add(import('@backstage/plugin-auth-backend')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend-module-gcp-iap-provider')); +/* highlight-add-end */ +``` + ## Frontend Changes It is recommended to use the `ProxiedSignInPage` for this provider, which is diff --git a/docs/auth/google/provider.md b/docs/auth/google/provider.md index d3cd8f2dd2..9b611c2fc9 100644 --- a/docs/auth/google/provider.md +++ b/docs/auth/google/provider.md @@ -42,8 +42,15 @@ auth: development: clientId: ${AUTH_GOOGLE_CLIENT_ID} clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET} + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + - resolver: emailMatchingUserEntityAnnotation ``` +> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + The Google provider is a structure with two configuration keys: - `clientId`: The client ID that you generated, e.g. diff --git a/docs/auth/microsoft/provider.md b/docs/auth/microsoft/provider.md index bb8a7db6e4..d3dd32f7c8 100644 --- a/docs/auth/microsoft/provider.md +++ b/docs/auth/microsoft/provider.md @@ -57,8 +57,15 @@ auth: domainHint: ${AZURE_TENANT_ID} additionalScopes: - Mail.Send + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + - resolver: emailMatchingUserEntityAnnotation ``` +> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + The Microsoft provider is a structure with three mandatory configuration keys: - `clientId`: Application (client) ID, found on App Registration > Overview diff --git a/docs/auth/oauth2-proxy/provider.md b/docs/auth/oauth2-proxy/provider.md index ef11827039..507fc3d2bf 100644 --- a/docs/auth/oauth2-proxy/provider.md +++ b/docs/auth/oauth2-proxy/provider.md @@ -22,9 +22,17 @@ The provider configuration can be added to your `app-config.yaml` under the root auth: environment: development providers: - oauth2Proxy: {} + oauth2Proxy: + development: + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + - resolver: forwardedUserMatchingUserEntityName ``` +> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + Right now no configuration options are supported, but the empty object is needed to enable the provider in the auth backend. diff --git a/docs/auth/okta/provider.md b/docs/auth/okta/provider.md index 2242e4a0f0..ff113d94ed 100644 --- a/docs/auth/okta/provider.md +++ b/docs/auth/okta/provider.md @@ -47,8 +47,15 @@ auth: idp: ${AUTH_OKTA_IDP} # Optional # https://developer.okta.com/docs/reference/api/oidc/#scope-dependent-claims-not-always-returned additionalScopes: ${AUTH_OKTA_ADDITIONAL_SCOPES} # Optional + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + - resolver: emailMatchingUserEntityAnnotation ``` +> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + The values referenced are found on the Application page on your Okta site. - `clientId`: The client ID that you generated on Okta, e.g. diff --git a/docs/auth/vmware-cloud/provider.md b/docs/auth/vmware-cloud/provider.md index 20115f9e75..db62b9e985 100644 --- a/docs/auth/vmware-cloud/provider.md +++ b/docs/auth/vmware-cloud/provider.md @@ -156,8 +156,15 @@ auth: development: clientId: ${APP_ID} organizationId: ${ORG_ID} + signIn: + resolvers: + - resolver: emailMatchingUserEntityProfileEmail + - resolver: emailLocalPartMatchingUserEntityName + - resolver: vmwareCloudSignInResolvers ``` +> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + where `APP_ID` refers to the ID retrieved when creating the OAuth App, and `ORG_ID` is the [long ID of the Organization](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-CF9E9318-B811-48CF-8499-9419997DC1F8.html#view-the-organization-id-1) diff --git a/docs/backend-system/building-backends/08-migrating.md b/docs/backend-system/building-backends/08-migrating.md index 1d7e1dcd03..0fbb368449 100644 --- a/docs/backend-system/building-backends/08-migrating.md +++ b/docs/backend-system/building-backends/08-migrating.md @@ -816,9 +816,9 @@ auth: tenantId: ${AZURE_TENANT_ID} signIn: resolvers: - - resolver: emailMatchingUserEntityAnnotation - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName + - resolver: emailMatchingUserEntityAnnotation ``` > Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. @@ -848,7 +848,7 @@ Additional resolvers: - [usernameMatchingUserEntityName](https://github.com/backstage/backstage/blob/5447cffd23cf00772988fb799ced0ec5e54efb2e/plugins/auth-backend-module-atlassian-provider/src/resolvers.ts#L33C16-L33C46) -##### GCP IAM +##### GCP IAP (Google Identity-Aware Proxy) Setup: From 8383e2ca7d4aceb761ec0cf23efb8ca5643d0415 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <awanlin@spotify.com> Date: Tue, 16 Apr 2024 11:03:28 -0500 Subject: [PATCH 175/234] Added dedicated resolvers section Signed-off-by: Andre Wanlin <awanlin@spotify.com> --- docs/auth/atlassian/provider.md | 14 +++++++++-- docs/auth/github/provider.md | 14 +++++++++-- docs/auth/gitlab/provider.md | 14 +++++++++-- docs/auth/google/gcp-iap-auth.md | 14 +++++++++-- docs/auth/google/provider.md | 14 +++++++++-- docs/auth/microsoft/provider.md | 14 +++++++++-- docs/auth/oauth2-proxy/provider.md | 40 ++++++------------------------ docs/auth/okta/provider.md | 16 +++++++++--- docs/auth/vmware-cloud/provider.md | 20 +++++++++------ 9 files changed, 105 insertions(+), 55 deletions(-) diff --git a/docs/auth/atlassian/provider.md b/docs/auth/atlassian/provider.md index cdd18bdc33..2082c97b85 100644 --- a/docs/auth/atlassian/provider.md +++ b/docs/auth/atlassian/provider.md @@ -54,8 +54,6 @@ auth: - resolver: usernameMatchingUserEntityName ``` -> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. - The Atlassian provider is a structure with three configuration keys: - `clientId`: The Key you generated in the developer console. @@ -64,6 +62,18 @@ The Atlassian provider is a structure with three configuration keys: **NOTE:** the scopes `offline_access`, `read:jira-work`, and `read:jira-user` are provided by default. +### Resolvers + +This provider includes several resolvers out of the box that you can use: + +- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. +- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. +- `usernameMatchingUserEntityName`: Matches the username from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. + +> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. + ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `atlassianAuthApi` reference and diff --git a/docs/auth/github/provider.md b/docs/auth/github/provider.md index 2ff8e0836d..b24e47f0d9 100644 --- a/docs/auth/github/provider.md +++ b/docs/auth/github/provider.md @@ -48,8 +48,6 @@ auth: - resolver: usernameMatchingUserEntityName ``` -> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. - The GitHub provider is a structure with three configuration keys: - `clientId`: The client ID that you generated on GitHub, e.g. @@ -62,6 +60,18 @@ The GitHub provider is a structure with three configuration keys: `https://your-intermediate-service.com/handler`. Only needed if Backstage is not the immediate receiver (e.g. one OAuth app for many backstage instances). +### Resolvers + +This provider includes several resolvers out of the box that you can use: + +- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. +- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. +- `usernameMatchingUserEntityName`: Matches the username from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. + +> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. + ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `githubAuthApi` reference and diff --git a/docs/auth/gitlab/provider.md b/docs/auth/gitlab/provider.md index fc4dac8164..5ba17289d6 100644 --- a/docs/auth/gitlab/provider.md +++ b/docs/auth/gitlab/provider.md @@ -50,8 +50,6 @@ auth: - resolver: usernameMatchingUserEntityName ``` -> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. - The GitLab provider is a structure with three configuration keys: - `clientId`: The Application ID that you generated on GitLab, e.g. @@ -63,6 +61,18 @@ The GitLab provider is a structure with three configuration keys: `https://$backstage.acme.corp/api/auth/gitlab/handler/frame` Note: Due to a peculiarity with GitLab OAuth, ensure there is no trailing `/` after 'frame' in the URL. +### Resolvers + +This provider includes several resolvers out of the box that you can use: + +- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. +- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. +- `usernameMatchingUserEntityName`: Matches the username from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. + +> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. + ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `gitlabAuthApi` reference and diff --git a/docs/auth/google/gcp-iap-auth.md b/docs/auth/google/gcp-iap-auth.md index 911ee4e263..a660627b7e 100644 --- a/docs/auth/google/gcp-iap-auth.md +++ b/docs/auth/google/gcp-iap-auth.md @@ -34,8 +34,6 @@ auth: - resolver: emailMatchingUserEntityAnnotation ``` -> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. - The full `audience` value can be obtained by visiting your [Identity-Aware Proxy Google Cloud console](https://console.cloud.google.com/security/iap), selecting your project, finding your Backend Service to proxy, clicking the 3 vertical dots then "Get JWT Audience Code", and copying from the resulting popup, which will look similar to the following: ![Identity-Aware Proxy JWT Audience Code popup](../../assets/auth/gcp-iap-jwt-audience-code-popup.png) @@ -43,6 +41,18 @@ The full `audience` value can be obtained by visiting your [Identity-Aware Proxy This config section must be in place for the provider to load at all. Now let's add the provider itself. +### Resolvers + +This provider includes several resolvers out of the box that you can use: + +- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. +- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. +- `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `google.com/email` annotation matches. If no match is found it will throw a `NotFoundError`. + +> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. + ## Backend Changes This provider is not enabled by default in the auth backend code, because diff --git a/docs/auth/google/provider.md b/docs/auth/google/provider.md index 9b611c2fc9..c5429486ef 100644 --- a/docs/auth/google/provider.md +++ b/docs/auth/google/provider.md @@ -49,14 +49,24 @@ auth: - resolver: emailMatchingUserEntityAnnotation ``` -> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. - The Google provider is a structure with two configuration keys: - `clientId`: The client ID that you generated, e.g. `10023341500512-beui241gjwwkrdkr2eh7dprewj2pp1q.apps.googleusercontent.com` - `clientSecret`: The client secret tied to the generated client ID. +### Resolvers + +This provider includes several resolvers out of the box that you can use: + +- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. +- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. +- `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `google.com/email` annotation matches. If no match is found it will throw a `NotFoundError`. + +> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. + ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `googleAuthApi` reference and diff --git a/docs/auth/microsoft/provider.md b/docs/auth/microsoft/provider.md index d3dd32f7c8..5e4dac32b5 100644 --- a/docs/auth/microsoft/provider.md +++ b/docs/auth/microsoft/provider.md @@ -64,8 +64,6 @@ auth: - resolver: emailMatchingUserEntityAnnotation ``` -> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. - The Microsoft provider is a structure with three mandatory configuration keys: - `clientId`: Application (client) ID, found on App Registration > Overview @@ -77,6 +75,18 @@ The Microsoft provider is a structure with three mandatory configuration keys: For more details, see [Home Realm Discovery](https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/home-realm-discovery-policy) - `additionalScopes` (optional): List of scopes for the App Registration. The default and mandatory value is ['user.read']. +### Resolvers + +This provider includes several resolvers out of the box that you can use: + +- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. +- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. +- `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `microsoft.com/email` annotation matches. If no match is found it will throw a `NotFoundError`. + +> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. + ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `microsoftAuthApiRef` reference and diff --git a/docs/auth/oauth2-proxy/provider.md b/docs/auth/oauth2-proxy/provider.md index 507fc3d2bf..96dc217486 100644 --- a/docs/auth/oauth2-proxy/provider.md +++ b/docs/auth/oauth2-proxy/provider.md @@ -31,43 +31,17 @@ auth: - resolver: forwardedUserMatchingUserEntityName ``` -> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. +### Resolvers -Right now no configuration options are supported, but the empty object is needed -to enable the provider in the auth backend. +This provider includes several resolvers out of the box that you can use: -To use the `oauth2Proxy` provider you must also configure it with a sign-in resolver. -For more information about the sign-in process in general, see the -[Sign-in Identities and Resolvers](../identity-resolver.md) documentation. +- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. +- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. +- `forwardedUserMatchingUserEntityName`: Matches the value in the `x-forwarded-user` header from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. -For the `oauth2Proxy` provider, the sign-in result is quite different than other providers. -Because it's a proxy provider that can be configured to forward information through -arbitrary headers, the auth result simply just gives you access to the HTTP headers -of the incoming request. Using these you can either extract the information directly, -or grab ID or access tokens to look up additional information and/or validate the request. +> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. -A simple sign-in resolver might for example look like this: - -```ts -providerFactories: { - ...defaultAuthProviderFactories, - oauth2Proxy: providers.oauth2Proxy.create({ - signIn: { - async resolver({ result }, ctx) { - const name = result.getHeader('x-forwarded-user'); - if (!name) { - throw new Error('Request did not contain a user') - } - return ctx.signInWithCatalogUser({ - entityRef: { name }, - }); - }, - }, - }), -}, -``` - -[An example on how to sign a user in without a matching user](https://github.com/backstage/backstage/blob/master/packages/backend/src/plugins/auth.ts) +If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. ## Adding the provider to the Backstage frontend diff --git a/docs/auth/okta/provider.md b/docs/auth/okta/provider.md index ff113d94ed..163612c4a7 100644 --- a/docs/auth/okta/provider.md +++ b/docs/auth/okta/provider.md @@ -29,7 +29,7 @@ To add Okta authentication, you must create an Application from Okta: The configuration examples provided above are suitable for local development. For a production deployment, substitute `http://localhost:7007` with the url that your Backstage instance is available at. -# Configuration +## Configuration The provider configuration can then be added to your `app-config.yaml` under the root `auth` configuration: @@ -54,8 +54,6 @@ auth: - resolver: emailMatchingUserEntityAnnotation ``` -> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. - The values referenced are found on the Application page on your Okta site. - `clientId`: The client ID that you generated on Okta, e.g. @@ -68,6 +66,18 @@ The values referenced are found on the Application page on your Okta site. `additionalScopes` is an optional value, a string of space separated scopes, that will be combined with the default `scope` value of `openid profile email offline_access` to adjust the `scope` sent to Okta during OAuth. This will have an impact on [the dependent claims returned](https://developer.okta.com/docs/reference/api/oidc/#scope-dependent-claims-not-always-returned). For example, setting the `additionalScopes` value to `groups` will result in the claim returning a list of the groups that the user is a member of that also match the ID token group filter of the client app. +### Resolvers + +This provider includes several resolvers out of the box that you can use: + +- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. +- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. +- `emailMatchingUserEntityAnnotation`: Matches the email address from the auth provider with the User entity where the value of the `okta.com/email` annotation matches. If no match is found it will throw a `NotFoundError`. + +> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. + ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `oktaAuthApi` reference and diff --git a/docs/auth/vmware-cloud/provider.md b/docs/auth/vmware-cloud/provider.md index db62b9e985..62a653baf0 100644 --- a/docs/auth/vmware-cloud/provider.md +++ b/docs/auth/vmware-cloud/provider.md @@ -107,10 +107,6 @@ export default async function createPlugin( In the above, `commonSignInResolvers.emailLocalPartMatchingUserEntityName()` can be replaced with a more suitable resolver for the app in question. -## Configure Sign-in Resolution - -See [Sign-in Identities and Resolvers](../identity-resolver.md) for details. - ## Add to Sign-in Page See the [Sign-In Configuration](../index.md#sign-in-configuration) docs for @@ -163,9 +159,7 @@ auth: - resolver: vmwareCloudSignInResolvers ``` -> Note: the resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. - -where `APP_ID` refers to the ID retrieved when creating the OAuth App, and +Where `APP_ID` refers to the ID retrieved when creating the OAuth App, and `ORG_ID` is the [long ID of the Organization](https://docs.vmware.com/en/VMware-Cloud-services/services/Using-VMware-Cloud-Services/GUID-CF9E9318-B811-48CF-8499-9419997DC1F8.html#view-the-organization-id-1) in VMware Cloud for which you wish to enable sign-in. @@ -176,3 +170,15 @@ library used by this provider requires the use of Express session middleware to do this. Therefore the value `your session secret` under `auth.session.secret` should be replaced with a long, complex and unique string which will act as a key for signing session cookies set by Backstage. + +### Resolvers + +This provider includes several resolvers out of the box that you can use: + +- `emailMatchingUserEntityProfileEmail`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will throw a `NotFoundError`. +- `emailLocalPartMatchingUserEntityName`: Matches the [local part](https://en.wikipedia.org/wiki/Email_address#Local-part) of the email address from the auth provider with the User entity that has a matching `name`. If no match is found it will throw a `NotFoundError`. +- `vmwareCloudSignInResolvers`: Matches the email address from the auth provider with the User entity that has a matching `spec.profile.email`. If no match is found it will sign in the user without associating with a catalog user. + +> Note: The resolvers will be tried in order, but will only be skipped if they throw a `NotFoundError`. + +If these resolvers do not fit your needs you can build a custom resolver, this is covered in the [Building Custom Resolvers](../identity-resolver.md#building-custom-resolvers) section of the Sign-in Identities and Resolvers documentation. From 79d1c082438da505e1a6a62bc89cae12320189bd Mon Sep 17 00:00:00 2001 From: Andre Wanlin <awanlin@spotify.com> Date: Tue, 30 Apr 2024 08:03:22 -0500 Subject: [PATCH 176/234] Invert sections Signed-off-by: Andre Wanlin <awanlin@spotify.com> --- docs/auth/google/gcp-iap-auth.md | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/auth/google/gcp-iap-auth.md b/docs/auth/google/gcp-iap-auth.md index a660627b7e..4be18e5e50 100644 --- a/docs/auth/google/gcp-iap-auth.md +++ b/docs/auth/google/gcp-iap-auth.md @@ -55,9 +55,30 @@ If these resolvers do not fit your needs you can build a custom resolver, this i ## Backend Changes -This provider is not enabled by default in the auth backend code, because -besides the config section above, it also needs to be given one or more -callbacks in actual code as well as described below. +There is a module for this provider that you will need to add to your backend. + +First you'll want to run this command to add the module: + +```sh + yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-gcp-iap-provider +``` + +Then you will need to add this to your backend: + +```ts title="packages/backend/src/index.ts" +const backend = createBackend(); + +backend.add(import('@backstage/plugin-auth-backend')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-auth-backend-module-gcp-iap-provider')); +/* highlight-add-end */ +``` + +### Legacy Backend Changes + +If you are still using the legacy backend you will need to make the changes outlined here. + +This provider is not enabled by default in the auth backend code, because besides the config section above, it also needs to be given one or more callbacks in actual code as well as described below. Add a `providerFactories` entry to the router in `packages/backend/src/plugins/auth.ts`. @@ -111,27 +132,6 @@ Now the backend is ready to serve auth requests on the `/api/auth/gcp-iap/refresh` endpoint. All that's left is to update the frontend sign-in mechanism to poll that endpoint through the IAP, on the user's behalf. -### New Backend System - -There is a module for this provider that works with the new backend system. - -First you'll want to run this command to add the module: - -```sh - yarn --cwd packages/backend add @backstage/plugin-auth-backend-module-gcp-iap-provider -``` - -Then you will need to add this to your backend: - -```ts title="packages/backend/src/index.ts" -const backend = createBackend(); - -backend.add(import('@backstage/plugin-auth-backend')); -/* highlight-add-start */ -backend.add(import('@backstage/plugin-auth-backend-module-gcp-iap-provider')); -/* highlight-add-end */ -``` - ## Frontend Changes It is recommended to use the `ProxiedSignInPage` for this provider, which is From 95de07271c7d9753f10adcc9b9c4ced1cf1c300f Mon Sep 17 00:00:00 2001 From: Andre Wanlin <awanlin@spotify.com> Date: Fri, 12 Apr 2024 14:41:59 -0500 Subject: [PATCH 177/234] Updated the Azure and Microsoft Graph Integration Docs Signed-off-by: Andre Wanlin <awanlin@spotify.com> --- docs/integrations/azure/discovery.md | 20 ++++++++++ docs/integrations/azure/org.md | 55 ++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index 7fbba5228d..4529a31cfe 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -127,6 +127,26 @@ builder.addEntityProvider( /* highlight-add-end */ ``` +### New Backend System + +This entity provider will work with the new backend system. Here is how to install it: + +Run the following command to add the module's package: + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure +``` + +Then updated your backend by adding the following line: + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-azure/alpha')); +/* highlight-add-end */ +``` + ## Alternative Processor As an alternative to the entity provider `AzureDevOpsEntityProvider`, you can still use the `AzureDevopsDiscoveryProcessor`. diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index 5e39aaeb7d..69151cd82c 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -64,6 +64,26 @@ export default async function createPlugin( } ``` +### New Backend System + +This entity provider will work with the new backend system. Here is how to install it: + +Run the following command to add the module's package: + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-msgraph/alpha +``` + +Then updated your backend by adding the following line: + +```ts title="packages/backend/src/index.ts" +backend.add(import('@backstage/plugin-catalog-backend/alpha')); +/* highlight-add-start */ +backend.add(import('@backstage/plugin-catalog-backend-module-msgraph/alpha')); +/* highlight-add-end */ +``` + ## Authenticating with Microsoft Graph ### Local Development @@ -244,6 +264,41 @@ export async function myOrganizationTransformer( } ``` +### Using Transformers in the New Backend System + +Transformers can be configured by extending `microsoftGraphOrgEntityProviderTransformExtensionPoint`. Here is an example: + +```ts title="packages/backend/src/index.ts" +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { microsoftGraphOrgEntityProviderTransformExtensionPoint } from '@backstage/plugin-catalog-backend-module-msgraph/alpha'; + +backend.add( + createBackendModule({ + pluginId: 'catalog', + moduleId: 'microsoft-graph-extensions', + register(env) { + env.registerInit({ + deps: { + /* highlight-add-start */ + microsoftGraphTransformers: + microsoftGraphOrgEntityProviderTransformExtensionPoint, + /* highlight-add-end */ + }, + async init({ microsoftGraphTransformers }) { + /* highlight-add-start */ + microsoftGraphTransformers.setUserTransformer(myUserTransformer); + microsoftGraphTransformers.setGroupTransformer(myGroupTransformer); + microsoftGraphTransformers.setOrganizationTransformer( + myOrganizationTransformer, + ); + /* highlight-add-end */ + }, + }); + }, + }), +); +``` + ## Troubleshooting ### No data From b78ffad266a8fbcb9e9f92996b8a95f2cb3b73a6 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <awanlin@spotify.com> Date: Tue, 30 Apr 2024 09:27:02 -0500 Subject: [PATCH 178/234] Refactored to follow --old convention Signed-off-by: Andre Wanlin <awanlin@spotify.com> --- docs/integrations/azure/discovery--old.md | 191 +++++++++++++++ docs/integrations/azure/discovery.md | 100 +------- docs/integrations/azure/org--old.md | 278 ++++++++++++++++++++++ docs/integrations/azure/org.md | 144 +++++------ 4 files changed, 535 insertions(+), 178 deletions(-) create mode 100644 docs/integrations/azure/discovery--old.md create mode 100644 docs/integrations/azure/org--old.md diff --git a/docs/integrations/azure/discovery--old.md b/docs/integrations/azure/discovery--old.md new file mode 100644 index 0000000000..e9ce137eff --- /dev/null +++ b/docs/integrations/azure/discovery--old.md @@ -0,0 +1,191 @@ +--- +id: discovery--old +title: Azure DevOps Discovery +sidebar_label: Discovery +# prettier-ignore +description: Automatically discovering catalog entities from repositories in an Azure DevOps organization +--- + +:::info +This documentation is written for the old backend which has been replaced by +[the new backend system](../backend-system/index.md), being the default since +Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new +backend system, you may want to read [its own article](./discovery.md) +instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + +The Azure DevOps integration has a special entity provider for discovering +catalog entities within an Azure DevOps. The provider will crawl your Azure +DevOps organization and register entities matching the configured path. This can +be useful as an alternative to static locations or manually adding things to the +catalog. + +This guide explains how to install and configure the Azure DevOps Entity Provider (recommended) or the Azure DevOps Processor. + +## Dependencies + +### Code Search Feature + +Azure discovery is driven by the Code Search feature in Azure DevOps, this may not be enabled by default. For Azure +DevOps Services you can confirm this by looking at the installed extensions in your Organization Settings. For Azure +DevOps Server you'll find this information in your Collection Settings. + +If the Code Search extension is not listed then you can install it from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=ms.vss-code-search&targetId=f9352dac-ba6e-434e-9241-a848a510ce3f&utm_source=vstsproduct&utm_medium=SearchExtStatus). + +### Azure Integration + +Setup [Azure integration](locations.md) with `host` and `token`. Host must be `dev.azure.com` for Cloud users, otherwise set this to your on-premise hostname. + +## Installation + +At your configuration, you add one or more provider configs: + +```yaml title="app-config.yaml" +catalog: + providers: + azureDevOps: + yourProviderId: # identifies your dataset / provider independent of config changes + organization: myorg + project: myproject + repository: service-* # this will match all repos starting with service-* + path: /catalog-info.yaml + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } + yourSecondProviderId: # identifies your dataset / provider independent of config changes + organization: myorg + project: '*' # this will match all projects + repository: '*' # this will match all repos + path: /catalog-info.yaml + anotherProviderId: # another identifier + organization: myorg + project: myproject + repository: '*' # this will match all repos + path: /src/*/catalog-info.yaml # this will search for files deep inside the /src folder + yetAnotherProviderId: # guess, what? Another one :) + host: selfhostedazure.yourcompany.com + organization: myorg + project: myproject + branch: development +``` + +The parameters available are: + +- **`host:`** _(optional)_ Leave empty for Cloud hosted, otherwise set to your self-hosted instance host. +- **`organization:`** Your Organization slug (or Collection for on-premise users). Required. +- **`project:`** _(required)_ Your project slug. Wildcards are supported as shown on the examples above. Using '\*' will search all projects. For a project name containing spaces, use both single and double quotes as in `project: '"My Project Name"'`. +- **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched. +- **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml. +- **`branch:`** _(optional)_ The branch name to use. +- **`schedule`**: + - **`frequency`**: + How often you want the task to run. The system does its best to avoid overlapping invocations. + - **`timeout`**: + The maximum amount of time that a single task invocation can take. + - **`initialDelay`** _(optional)_: + The amount of time that should pass before the first invocation happens. + - **`scope`** _(optional)_: + `'global'` or `'local'`. Sets the scope of concurrency control. + +_Note:_ + +- The path parameter follows the same rules as the search on Azure DevOps web interface. For more details visit the [official search documentation](https://docs.microsoft.com/en-us/azure/devops/project/search/get-started-search?view=azure-devops). +- To use branch parameters, it is necessary that the desired branch be added to the "Searchable branches" list within Azure DevOps Repositories. To do this, follow the instructions below: + +1. Access your Azure DevOps and open the repository in which you want to add the branch. +2. Click on "Settings" in the lower-left corner of the screen. +3. Select the "Options" option in the left navigation bar. +4. In the "Searchable branches" section, click on the "Add" button to add a new branch. +5. In the window that appears, enter the name of the branch you want to add and click "Add". +6. The added branch will now appear in the "Searchable branches" list. + +It may take some time before the branch is indexed and searchable. + +As this provider is not one of the default providers, you will first need to install +the Azure catalog plugin: + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure +``` + +Once you've done that, you'll also need to add the segment below to `packages/backend/src/plugins/catalog.ts`: + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-next-line */ +import { AzureDevOpsEntityProvider } from '@backstage/plugin-catalog-backend-module-azure'; + +const builder = await CatalogBuilder.create(env); +/** ... other processors and/or providers ... */ +/* highlight-add-start */ +builder.addEntityProvider( + AzureDevOpsEntityProvider.fromConfig(env.config, { + logger: env.logger, + // optional: alternatively, use scheduler with schedule defined in app-config.yaml + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, + }), + // optional: alternatively, use schedule + scheduler: env.scheduler, + }), +); +/* highlight-add-end */ +``` + +## Alternative Processor + +As an alternative to the entity provider `AzureDevOpsEntityProvider`, you can still use the `AzureDevopsDiscoveryProcessor`. + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-next-line */ +import { AzureDevOpsDiscoveryProcessor } from '@backstage/plugin-catalog-backend-module-azure'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise<Router> { + const builder = await CatalogBuilder.create(env); + /* highlight-add-next-line */ + builder.addProcessor( + AzureDevOpsDiscoveryProcessor.fromConfig(env.config, { + logger: env.logger, + }), + ); + + // .. +} +``` + +```yaml +catalog: + locations: + # Scan all repositories for a catalog-info.yaml in the root of the default branch + - type: azure-discovery + target: https://dev.azure.com/myorg/myproject + # Or use a custom pattern for a subset of all repositories with default repository + - type: azure-discovery + target: https://dev.azure.com/myorg/myproject/_git/service-* + # Or use a custom file format and location + - type: azure-discovery + target: https://dev.azure.com/myorg/myproject/_git/*?path=/src/*/catalog-info.yaml + # And optionally provide a specific branch name using the version parameter + - type: azure-discovery + target: https://dev.azure.com/myorg/myproject/_git/*?path=/catalog-info.yaml&version=GBtopic/catalog-info +``` + +Note the `azure-discovery` type, as this is not a regular `url` processor. + +When using a custom pattern, the target is composed of these parts: + +- The base instance URL, `https://dev.azure.com` in this case +- The organization name which is required, `myorg` in this case +- The project name which is optional, `myproject` in this case. This defaults to \*, which scans all the projects where the token has access to. +- The repository blob to scan, which accepts \* wildcard tokens and must be + added after `_git/`. This can simply be `*` to scan all repositories in the + project. +- The path within each repository to find the catalog YAML file. This will + usually be `/catalog-info.yaml`, `/src/*/catalog-info.yaml` or a similar + variation for catalog files stored in the root directory of each repository. +- The repository branch to scan which is optional, `topic/catalog-info` in this case. If omitted, the repo's default branch will be scanned. The `GB` prefix is required, as this is how Azure DevOps identifies the version as a branch. diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index 4529a31cfe..f63c16a876 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -6,6 +6,14 @@ sidebar_label: Discovery description: Automatically discovering catalog entities from repositories in an Azure DevOps organization --- +:::info +This documentation is written for [the new backend +system](../backend-system/index.md) which is the default since Backstage +[version 1.24](../releases/v1.24.0.md). If you are still on the old backend +system, you may want to read [its own article](./discovery--old.md) +instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + The Azure DevOps integration has a special entity provider for discovering catalog entities within an Azure DevOps. The provider will crawl your Azure DevOps organization and register entities matching the configured path. This can @@ -30,7 +38,7 @@ Setup [Azure integration](locations.md) with `host` and `token`. Host must be `d ## Installation -At your configuration, you add one or more provider configs: +In your configuration, you add one or more provider configs: ```yaml title="app-config.yaml" catalog: @@ -103,41 +111,6 @@ the Azure catalog plugin: yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure ``` -Once you've done that, you'll also need to add the segment below to `packages/backend/src/plugins/catalog.ts`: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { AzureDevOpsEntityProvider } from '@backstage/plugin-catalog-backend-module-azure'; - -const builder = await CatalogBuilder.create(env); -/** ... other processors and/or providers ... */ -/* highlight-add-start */ -builder.addEntityProvider( - AzureDevOpsEntityProvider.fromConfig(env.config, { - logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule - scheduler: env.scheduler, - }), -); -/* highlight-add-end */ -``` - -### New Backend System - -This entity provider will work with the new backend system. Here is how to install it: - -Run the following command to add the module's package: - -```bash -# From your Backstage root directory -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure -``` - Then updated your backend by adding the following line: ```ts title="packages/backend/src/index.ts" @@ -146,58 +119,3 @@ backend.add(import('@backstage/plugin-catalog-backend/alpha')); backend.add(import('@backstage/plugin-catalog-backend-module-azure/alpha')); /* highlight-add-end */ ``` - -## Alternative Processor - -As an alternative to the entity provider `AzureDevOpsEntityProvider`, you can still use the `AzureDevopsDiscoveryProcessor`. - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { AzureDevOpsDiscoveryProcessor } from '@backstage/plugin-catalog-backend-module-azure'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise<Router> { - const builder = await CatalogBuilder.create(env); - /* highlight-add-next-line */ - builder.addProcessor( - AzureDevOpsDiscoveryProcessor.fromConfig(env.config, { - logger: env.logger, - }), - ); - - // .. -} -``` - -```yaml -catalog: - locations: - # Scan all repositories for a catalog-info.yaml in the root of the default branch - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject - # Or use a custom pattern for a subset of all repositories with default repository - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject/_git/service-* - # Or use a custom file format and location - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject/_git/*?path=/src/*/catalog-info.yaml - # And optionally provide a specific branch name using the version parameter - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject/_git/*?path=/catalog-info.yaml&version=GBtopic/catalog-info -``` - -Note the `azure-discovery` type, as this is not a regular `url` processor. - -When using a custom pattern, the target is composed of these parts: - -- The base instance URL, `https://dev.azure.com` in this case -- The organization name which is required, `myorg` in this case -- The project name which is optional, `myproject` in this case. This defaults to \*, which scans all the projects where the token has access to. -- The repository blob to scan, which accepts \* wildcard tokens and must be - added after `_git/`. This can simply be `*` to scan all repositories in the - project. -- The path within each repository to find the catalog YAML file. This will - usually be `/catalog-info.yaml`, `/src/*/catalog-info.yaml` or a similar - variation for catalog files stored in the root directory of each repository. -- The repository branch to scan which is optional, `topic/catalog-info` in this case. If omitted, the repo's default branch will be scanned. The `GB` prefix is required, as this is how Azure DevOps identifies the version as a branch. diff --git a/docs/integrations/azure/org--old.md b/docs/integrations/azure/org--old.md new file mode 100644 index 0000000000..306f8eecb8 --- /dev/null +++ b/docs/integrations/azure/org--old.md @@ -0,0 +1,278 @@ +--- +id: org--old +title: Microsoft Entra Tenant Data +sidebar_label: Org Data +# prettier-ignore +description: Importing users and groups from Microsoft Entra ID into Backstage +--- + +:::info +This documentation is written for the old backend which has been replaced by +[the new backend system](../backend-system/index.md), being the default since +Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new +backend system, you may want to read [its own article](./org.md) +instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + +The Backstage catalog can be set up to ingest organizational data - users and +teams - directly from a tenant in Microsoft Entra ID via the +Microsoft Graph API. + +## Installation + +The package is not installed by default, therefore you have to add `@backstage/plugin-catalog-backend-module-msgraph` to your backend package. + +```bash +# From your Backstage root directory +yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-msgraph +``` + +Next add the basic configuration to `app-config.yaml` + +```yaml title="app-config.yaml" +catalog: + providers: + microsoftGraphOrg: + default: + tenantId: ${AZURE_TENANT_ID} + user: + filter: accountEnabled eq true and userType eq 'member' + group: + filter: > + securityEnabled eq false + and mailEnabled eq true + and groupTypes/any(c:c+eq+'Unified') + schedule: + frequency: PT1H + timeout: PT50M +``` + +Finally, register the plugin in `catalog.ts`. +For large organizations, this plugin can take a long time, so be careful setting low frequency / timeouts and importing a large amount of users / groups for the first try. + +```ts title="packages/backend/src/plugins/catalog.ts" +/* highlight-add-next-line */ +import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise<Router> { + const builder = await CatalogBuilder.create(env); + + /* highlight-add-start */ + builder.addEntityProvider( + MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { + logger: env.logger, + scheduler: env.scheduler, + }), + ); + /* highlight-add-end */ + + // .. +} +``` + +## Authenticating with Microsoft Graph + +### Local Development + +For a local dev environment, it's recommended you have the Azure CLI or Azure PowerShell installed, and are logged in to those. +Alternatively you can use VSCode with the Azure extension if you install `@azure/identity-vscode`. +When these are set up, the plugin will authenticate with the Microsoft Graph API without you needing to configure any credentials, or granting any special permissions. +If you can't do this, you'll have to create an App Registration. + +### App Registration + +If none of the other authentication methods work, you can create an app registration in the azure portal. +By default the graph plugin requires the following Application permissions (not Delegated) for Microsoft Graph: + +- `GroupMember.Read.All` +- `User.Read.All` + +If your organization required Admin Consent for these permissions, that will need to be granted. + +When authenticating with a ClientId/ClientSecret, you can either set the `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` and `AZURE_CLIENT_SECRET` environment variables, or specify the values in configuration + +```yaml +microsoftGraphOrg: + default: + ##... + clientId: 9ef1aac6-b454-4e69-9cf5-7199df049281 + clientSecret: REDACTED +``` + +To authenticate with a certificate rather than a client secret, you can set the `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` and `AZURE_CLIENT_CERTIFICATE_PATH` environments + +### Managed Identity + +If deploying to resources that supports Managed Identity, and has identities configured (e.g. Azure App Services, Azure Container Apps), Managed Identity should be picked up without any additional configuration. +If your app has multiple managed identities, you may need to set the `AZURE_CLIENT_ID` environment variable to tell Azure Identity which identity to use. + +To grant the managed identity the same permissions as mentioned in _App Registration_ above, [please follow this guide](https://docs.microsoft.com/en-us/azure/app-service/tutorial-connect-app-access-microsoft-graph-as-app-javascript?tabs=azure-powershell) + +## Filtering imported Users and Groups + +By default, the plugin will import all users and groups from your directory. +This can be customized through [filters](https://learn.microsoft.com/en-us/graph/filter-query-parameter) and [search](https://learn.microsoft.com/en-us/graph/search-query-parameter) queries. Keep in mind that if you omit filters and search queries for the user or group properties, the plugin will automatically import all available users or groups. + +### Groups + +A smaller set of groups can be obtained by configuring a search query or a filter. +If both `filter` and `search` are provided, then groups must match both to be ingested. + +```yaml +microsoftGraphOrg: + providerId: + group: + filter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:c+eq+'Unified') + search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")' +``` + +In addition to these groups, one additional group will be created for your organization. +All imported groups will be a child of this group. + +### Users + +There are two modes for importing users - You can import all user objects matching a `filter`. + +```yaml +microsoftGraphOrg: + providerId: + user: + filter: accountEnabled eq true and userType eq 'member' +``` + +Alternatively you can import users that are members of specific groups. +For each group matching the `search` and `filter` query, each group member will be imported. +Only direct group members will be imported, not transient users. + +```yaml +microsoftGraphOrg: + providerId: + userGroupMember: + filter: "displayName eq 'Backstage Users'" + search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")' +``` + +### 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`: + +```yaml +microsoftGraphOrg: + providerId: + user: + filter: ... + loadPhotos: false +``` + +## Customizing Transformation + +Ingested entities can be customized by providing custom transformers. +These can be used to completely replace the built in logic, or used to tweak it by using the default transformers (`defaultGroupTransformer`, `defaultUserTransformer` and `defaultOrganizationTransformer` +Entities can also be excluded from backstage by returning `undefined`. + +These Transformers are be registered when configuring `MicrosoftGraphOrgEntityProvider` + +```ts +builder.addEntityProvider( + MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { + // ... + /* highlight-add-start */ + groupTransformer: myGroupTransformer, + userTransformer: myUserTransformer, + organizationTransformer: myOrganizationTransformer, + /* highlight-add-end */ + }), +); +``` + +When using custom transformers, you may want to customize the data returned. +Several configuration options can be provided to tweak the Microsoft Graph query to get the data you need + +```yaml +microsoftGraphOrg: + providerId: + user: + expand: manager + group: + expand: member + select: ['id', 'displayName', 'description'] +``` + +The following provides an example of each kind of transformer + +```ts +import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; +import { + defaultGroupTransformer, + defaultUserTransformer, + defaultOrganizationTransformer, +} from '@backstage/plugin-catalog-backend-module-msgraph'; +import { GroupEntity, UserEntity } from '@backstage/catalog-model'; + +// This group transformer completely replaces the built in logic with custom logic. +export async function myGroupTransformer( + group: MicrosoftGraph.Group, + groupPhoto?: string, +): Promise<GroupEntity | undefined> { + return { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: group.id!, + annotations: {}, + }, + spec: { + type: 'Microsoft Entra ID', + children: [], + }, + }; +} + +// This user transformer makes use of the built in logic, but also sets the description field +export async function myUserTransformer( + graphUser: MicrosoftGraph.User, + userPhoto?: string, +): Promise<UserEntity | undefined> { + const backstageUser = await defaultUserTransformer(graphUser, userPhoto); + + if (backstageUser) { + backstageUser.metadata.description = 'Loaded from Microsoft Entra ID'; + } + + return backstageUser; +} + +// Example organization transformer that removes the organization group completely +export async function myOrganizationTransformer( + graphOrganization: MicrosoftGraph.Organization, +): Promise<GroupEntity | undefined> { + return undefined; +} +``` + +## Troubleshooting + +### No data + +First check your logs for the message `Reading msgraph users and groups`. +If you don't see this, check you've registered the provider, and that the schedule is valid + +If you see a log entry `Read 0 msgraph users and 0 msgraph groups`, check your search and filter arguments. + +If you see the start message (`Reading msgraph users and groups`) but no end message (`Read X msgraph users and Y msgraph groups`), then it is likely the job is taking a long time due to a large volume of data. +The default behavior is to import all users and groups, which is often more data than needed. +Try importing a smaller set of data (e.g. `filter: displayName eq 'John Smith'`). + +### Authentication / Token Errors + +See [Troubleshooting Azure Identity Authentication Issues](https://aka.ms/azsdk/js/identity/troubleshoot) + +### Error while reading users from Microsoft Graph: Authorization_RequestDenied - Insufficient privileges to complete the operation + +- Make sure you've granted all the required permissions to your application registration or managed identity +- Make sure the permissions are `Application` permissions rather than `Delegated` +- If your organization has configured "Admin consent" to be required, make sure this has been granted for your application permissions +- If your group queries are returning Microsoft Teams groups, you may need to grant addition permissions (e.g. `Team.ReadBasic.All`, `TeamMember.Read.All`) +- If you've added additional `select` or `expand` fields, those may need additional permissions granted diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index 69151cd82c..46623a7537 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -6,6 +6,14 @@ sidebar_label: Org Data description: Importing users and groups from Microsoft Entra ID into Backstage --- +:::info +This documentation is written for [the new backend +system](../backend-system/index.md) which is the default since Backstage +[version 1.24](../releases/v1.24.0.md). If you are still on the old backend +system, you may want to read [its own article](./identity-resolver--old.md) +instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +::: + The Backstage catalog can be set up to ingest organizational data - users and teams - directly from a tenant in Microsoft Entra ID via the Microsoft Graph API. @@ -39,43 +47,11 @@ catalog: timeout: PT50M ``` -Finally, register the plugin in `catalog.ts`. +:::note For large organizations, this plugin can take a long time, so be careful setting low frequency / timeouts and importing a large amount of users / groups for the first try. +::: -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise<Router> { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - builder.addEntityProvider( - MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -### New Backend System - -This entity provider will work with the new backend system. Here is how to install it: - -Run the following command to add the module's package: - -```bash -# From your Backstage root directory -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-msgraph/alpha -``` - -Then updated your backend by adding the following line: +Finally, updated your backend by adding the following line: ```ts title="packages/backend/src/index.ts" backend.add(import('@backstage/plugin-catalog-backend/alpha')); @@ -184,21 +160,6 @@ Ingested entities can be customized by providing custom transformers. These can be used to completely replace the built in logic, or used to tweak it by using the default transformers (`defaultGroupTransformer`, `defaultUserTransformer` and `defaultOrganizationTransformer` Entities can also be excluded from backstage by returning `undefined`. -These Transformers are be registered when configuring `MicrosoftGraphOrgEntityProvider` - -```ts -builder.addEntityProvider( - MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { - // ... - /* highlight-add-start */ - groupTransformer: myGroupTransformer, - userTransformer: myUserTransformer, - organizationTransformer: myOrganizationTransformer, - /* highlight-add-end */ - }), -); -``` - When using custom transformers, you may want to customize the data returned. Several configuration options can be provided to tweak the Microsoft Graph query to get the data you need @@ -212,9 +173,53 @@ microsoftGraphOrg: select: ['id', 'displayName', 'description'] ``` -The following provides an example of each kind of transformer +### Using Custom Transformers -```ts +Transformers can be configured by extending `microsoftGraphOrgEntityProviderTransformExtensionPoint`. Here is an example: + +```ts title="packages/backend/src/index.ts" +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { microsoftGraphOrgEntityProviderTransformExtensionPoint } from '@backstage/plugin-catalog-backend-module-msgraph/alpha'; +import { + myUserTransformer, + myGroupTransformer, + myOrganizationTransformer, +} from './transformers'; + +backend.add( + createBackendModule({ + pluginId: 'catalog', + moduleId: 'microsoft-graph-extensions', + register(env) { + env.registerInit({ + deps: { + /* highlight-add-start */ + microsoftGraphTransformers: + microsoftGraphOrgEntityProviderTransformExtensionPoint, + /* highlight-add-end */ + }, + async init({ microsoftGraphTransformers }) { + /* highlight-add-start */ + microsoftGraphTransformers.setUserTransformer(myUserTransformer); + microsoftGraphTransformers.setGroupTransformer(myGroupTransformer); + microsoftGraphTransformers.setOrganizationTransformer( + myOrganizationTransformer, + ); + /* highlight-add-end */ + }, + }); + }, + }), +); +``` + +The `myUserTransformer`, `myGroupTransformer`, and `myOrganizationTransformer` transformer functions are from the examples in the section below. + +### Transformer Examples + +The following provides an example of each kind of transformer. We recommend creating a `transformers.ts` file in your `packages/backend/src` folder for these. + +```ts title="packages/backend/src/transformers.ts" import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; import { defaultGroupTransformer, @@ -264,41 +269,6 @@ export async function myOrganizationTransformer( } ``` -### Using Transformers in the New Backend System - -Transformers can be configured by extending `microsoftGraphOrgEntityProviderTransformExtensionPoint`. Here is an example: - -```ts title="packages/backend/src/index.ts" -import { createBackendModule } from '@backstage/backend-plugin-api'; -import { microsoftGraphOrgEntityProviderTransformExtensionPoint } from '@backstage/plugin-catalog-backend-module-msgraph/alpha'; - -backend.add( - createBackendModule({ - pluginId: 'catalog', - moduleId: 'microsoft-graph-extensions', - register(env) { - env.registerInit({ - deps: { - /* highlight-add-start */ - microsoftGraphTransformers: - microsoftGraphOrgEntityProviderTransformExtensionPoint, - /* highlight-add-end */ - }, - async init({ microsoftGraphTransformers }) { - /* highlight-add-start */ - microsoftGraphTransformers.setUserTransformer(myUserTransformer); - microsoftGraphTransformers.setGroupTransformer(myGroupTransformer); - microsoftGraphTransformers.setOrganizationTransformer( - myOrganizationTransformer, - ); - /* highlight-add-end */ - }, - }); - }, - }), -); -``` - ## Troubleshooting ### No data From 7a4f604177f6de42a2df81f5981704b5b7f0a8bd Mon Sep 17 00:00:00 2001 From: Andre Wanlin <awanlin@spotify.com> Date: Tue, 30 Apr 2024 11:13:42 -0500 Subject: [PATCH 179/234] Added comment about only picking one resolver Signed-off-by: Andre Wanlin <awanlin@spotify.com> --- docs/auth/atlassian/provider.md | 1 + docs/auth/github/provider.md | 1 + docs/auth/gitlab/provider.md | 1 + docs/auth/google/gcp-iap-auth.md | 1 + docs/auth/google/provider.md | 1 + docs/auth/microsoft/provider.md | 1 + docs/auth/oauth2-proxy/provider.md | 1 + docs/auth/okta/provider.md | 1 + docs/auth/vmware-cloud/provider.md | 1 + 9 files changed, 9 insertions(+) diff --git a/docs/auth/atlassian/provider.md b/docs/auth/atlassian/provider.md index 2082c97b85..d6f19ee791 100644 --- a/docs/auth/atlassian/provider.md +++ b/docs/auth/atlassian/provider.md @@ -49,6 +49,7 @@ auth: scope: ${AUTH_ATLASSIAN_SCOPES} signIn: resolvers: + # typically you would pick one of these - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName - resolver: usernameMatchingUserEntityName diff --git a/docs/auth/github/provider.md b/docs/auth/github/provider.md index b24e47f0d9..2c3aaeaf7d 100644 --- a/docs/auth/github/provider.md +++ b/docs/auth/github/provider.md @@ -43,6 +43,7 @@ auth: # enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL} signIn: resolvers: + # typically you would pick one of these - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName - resolver: usernameMatchingUserEntityName diff --git a/docs/auth/gitlab/provider.md b/docs/auth/gitlab/provider.md index 5ba17289d6..ff6a341667 100644 --- a/docs/auth/gitlab/provider.md +++ b/docs/auth/gitlab/provider.md @@ -45,6 +45,7 @@ auth: # callbackUrl: https://${BASE_URL}/api/auth/gitlab/handler/frame signIn: resolvers: + # typically you would pick one of these - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName - resolver: usernameMatchingUserEntityName diff --git a/docs/auth/google/gcp-iap-auth.md b/docs/auth/google/gcp-iap-auth.md index 4be18e5e50..709ef2b521 100644 --- a/docs/auth/google/gcp-iap-auth.md +++ b/docs/auth/google/gcp-iap-auth.md @@ -29,6 +29,7 @@ auth: jwtHeader: x-custom-header # Optional: Only if you are using a custom header for the IAP JWT signIn: resolvers: + # typically you would pick one of these - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName - resolver: emailMatchingUserEntityAnnotation diff --git a/docs/auth/google/provider.md b/docs/auth/google/provider.md index c5429486ef..f2d2e8524e 100644 --- a/docs/auth/google/provider.md +++ b/docs/auth/google/provider.md @@ -44,6 +44,7 @@ auth: clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET} signIn: resolvers: + # typically you would pick one of these - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName - resolver: emailMatchingUserEntityAnnotation diff --git a/docs/auth/microsoft/provider.md b/docs/auth/microsoft/provider.md index 5e4dac32b5..5c4707c151 100644 --- a/docs/auth/microsoft/provider.md +++ b/docs/auth/microsoft/provider.md @@ -59,6 +59,7 @@ auth: - Mail.Send signIn: resolvers: + # typically you would pick one of these - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName - resolver: emailMatchingUserEntityAnnotation diff --git a/docs/auth/oauth2-proxy/provider.md b/docs/auth/oauth2-proxy/provider.md index 96dc217486..aded90a1e7 100644 --- a/docs/auth/oauth2-proxy/provider.md +++ b/docs/auth/oauth2-proxy/provider.md @@ -26,6 +26,7 @@ auth: development: signIn: resolvers: + # typically you would pick one of these - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName - resolver: forwardedUserMatchingUserEntityName diff --git a/docs/auth/okta/provider.md b/docs/auth/okta/provider.md index 163612c4a7..0c15746cfd 100644 --- a/docs/auth/okta/provider.md +++ b/docs/auth/okta/provider.md @@ -49,6 +49,7 @@ auth: additionalScopes: ${AUTH_OKTA_ADDITIONAL_SCOPES} # Optional signIn: resolvers: + # typically you would pick one of these - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName - resolver: emailMatchingUserEntityAnnotation diff --git a/docs/auth/vmware-cloud/provider.md b/docs/auth/vmware-cloud/provider.md index 62a653baf0..8536eaea7e 100644 --- a/docs/auth/vmware-cloud/provider.md +++ b/docs/auth/vmware-cloud/provider.md @@ -154,6 +154,7 @@ auth: organizationId: ${ORG_ID} signIn: resolvers: + # typically you would pick one of these - resolver: emailMatchingUserEntityProfileEmail - resolver: emailLocalPartMatchingUserEntityName - resolver: vmwareCloudSignInResolvers From bdd6f8a6a649fa7f2351324111b5d2b69fc9c913 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Tue, 30 Apr 2024 18:25:11 +0200 Subject: [PATCH 180/234] Update deploy_packages.yml Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- .github/workflows/deploy_packages.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 775d8d00f8..f9ef4c67eb 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -105,11 +105,11 @@ jobs: - name: test (and upload coverage) run: | yarn backstage-cli repo test --maxWorkers=3 --workerIdleMemoryLimit=1300M --coverage - bash <(curl -s https://codecov.io/bash) + # bash <(curl -s https://codecov.io/bash) # Upload code coverage for some specific flags. Also see .codecov.yml - bash <(curl -s https://codecov.io/bash) -f packages/core-app-api/coverage/* -F core-app-api - bash <(curl -s https://codecov.io/bash) -f packages/core-components/coverage/* -F core-components - bash <(curl -s https://codecov.io/bash) -f packages/core-plugin-api/coverage/* -F core-plugin-api + # bash <(curl -s https://codecov.io/bash) -f packages/core-app-api/coverage/* -F core-app-api + # bash <(curl -s https://codecov.io/bash) -f packages/core-components/coverage/* -F core-components + # bash <(curl -s https://codecov.io/bash) -f packages/core-plugin-api/coverage/* -F core-plugin-api env: BACKSTAGE_TEST_DISABLE_DOCKER: 1 BACKSTAGE_TEST_DATABASE_POSTGRES16_CONNECTION_STRING: postgresql://postgres:postgres@localhost:${{ job.services.postgres16.ports[5432] }} From 48b38ae4338ef2c0e98d8340711d838495b1e1f0 Mon Sep 17 00:00:00 2001 From: Cassidy Marble <marblenix@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:40:55 -0400 Subject: [PATCH 181/234] Update discovery.md Fixing a typo. Signed-off-by: Cassidy Marble <marblenix@users.noreply.github.com> --- docs/integrations/bitbucketCloud/discovery.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index 81770afbf8..160827cb0a 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -27,7 +27,7 @@ yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbuck ### Installation with New Backend System ```ts -// optional if you want HTTP endpojnts to receive external events +// optional if you want HTTP endpoints to receive external events // backend.add(import('@backstage/plugin-events-backend/alpha')); // optional if you want to use AWS SQS instead of HTTP endpoints to receive external events // backend.add(import('@backstage/plugin-events-backend-module-aws-sqs/alpha')); From 8450ccd204580063a6152bb197c79e06fd0284ee Mon Sep 17 00:00:00 2001 From: Andre Wanlin <awanlin@spotify.com> Date: Tue, 30 Apr 2024 12:08:15 -0500 Subject: [PATCH 182/234] Fixed links Signed-off-by: Andre Wanlin <awanlin@spotify.com> --- docs/integrations/azure/discovery--old.md | 6 +----- docs/integrations/azure/discovery.md | 6 +----- docs/integrations/azure/org--old.md | 6 +----- docs/integrations/azure/org.md | 6 +----- 4 files changed, 4 insertions(+), 20 deletions(-) diff --git a/docs/integrations/azure/discovery--old.md b/docs/integrations/azure/discovery--old.md index e9ce137eff..be490d70fe 100644 --- a/docs/integrations/azure/discovery--old.md +++ b/docs/integrations/azure/discovery--old.md @@ -7,11 +7,7 @@ description: Automatically discovering catalog entities from repositories in an --- :::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../backend-system/index.md), being the default since -Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./discovery.md) -instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Azure DevOps integration has a special entity provider for discovering diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index f63c16a876..00c2cca104 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -7,11 +7,7 @@ description: Automatically discovering catalog entities from repositories in an --- :::info -This documentation is written for [the new backend -system](../backend-system/index.md) which is the default since Backstage -[version 1.24](../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./discovery--old.md) -instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Azure DevOps integration has a special entity provider for discovering diff --git a/docs/integrations/azure/org--old.md b/docs/integrations/azure/org--old.md index 306f8eecb8..89d1dfd61d 100644 --- a/docs/integrations/azure/org--old.md +++ b/docs/integrations/azure/org--old.md @@ -7,11 +7,7 @@ description: Importing users and groups from Microsoft Entra ID into Backstage --- :::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../backend-system/index.md), being the default since -Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./org.md) -instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for the old backend which has been replaced by [the new backend system](..././backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Backstage catalog can be set up to ingest organizational data - users and diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index 46623a7537..89abd4813b 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -7,11 +7,7 @@ description: Importing users and groups from Microsoft Entra ID into Backstage --- :::info -This documentation is written for [the new backend -system](../backend-system/index.md) which is the default since Backstage -[version 1.24](../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./identity-resolver--old.md) -instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Backstage catalog can be set up to ingest organizational data - users and From 353880c4501cbbffee657c89deb50e5d115b111c Mon Sep 17 00:00:00 2001 From: Andre Wanlin <awanlin@spotify.com> Date: Tue, 30 Apr 2024 12:52:41 -0500 Subject: [PATCH 183/234] Fixed missing links Signed-off-by: Andre Wanlin <awanlin@spotify.com> --- docs/integrations/azure/discovery.md | 2 +- docs/integrations/azure/org--old.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index 00c2cca104..7e7b465118 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -7,7 +7,7 @@ description: Automatically discovering catalog entities from repositories in an --- :::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Azure DevOps integration has a special entity provider for discovering diff --git a/docs/integrations/azure/org--old.md b/docs/integrations/azure/org--old.md index 89d1dfd61d..9f4b70db26 100644 --- a/docs/integrations/azure/org--old.md +++ b/docs/integrations/azure/org--old.md @@ -7,7 +7,7 @@ description: Importing users and groups from Microsoft Entra ID into Backstage --- :::info -This documentation is written for the old backend which has been replaced by [the new backend system](..././backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for the old backend which has been replaced by [the new backend system](.../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Backstage catalog can be set up to ingest organizational data - users and From b0eb7588ddee55aa63172eab6a1a4f7f32d61c4b Mon Sep 17 00:00:00 2001 From: Andre Wanlin <awanlin@spotify.com> Date: Tue, 30 Apr 2024 13:04:09 -0500 Subject: [PATCH 184/234] Fixed link typo Signed-off-by: Andre Wanlin <awanlin@spotify.com> --- docs/integrations/azure/org--old.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/azure/org--old.md b/docs/integrations/azure/org--old.md index 9f4b70db26..13d53b24b3 100644 --- a/docs/integrations/azure/org--old.md +++ b/docs/integrations/azure/org--old.md @@ -7,7 +7,7 @@ description: Importing users and groups from Microsoft Entra ID into Backstage --- :::info -This documentation is written for the old backend which has been replaced by [the new backend system](.../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Backstage catalog can be set up to ingest organizational data - users and From 84cdb92346785ceffda429fa64e35d7a6bce94cf Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 30 Apr 2024 13:20:34 -0600 Subject: [PATCH 185/234] integration support for harness - fixed comments p2 Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- packages/integration/package.json | 1 - packages/integration/src/harness/core.test.ts | 16 ++++++++-------- packages/integration/src/harness/core.ts | 4 ---- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/integration/package.json b/packages/integration/package.json index b57b94f982..bbd0eecbe7 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -50,7 +50,6 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@backstage/config-loader": "workspace:^", - "@backstage/test-utils": "workspace:^", "@types/luxon": "^3.0.0", "msw": "^1.0.0" }, diff --git a/packages/integration/src/harness/core.test.ts b/packages/integration/src/harness/core.test.ts index abfbb5d4c2..d3d402806a 100644 --- a/packages/integration/src/harness/core.test.ts +++ b/packages/integration/src/harness/core.test.ts @@ -15,7 +15,7 @@ */ import { setupServer } from 'msw/node'; -import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { setupRequestMockHandlers } from '../helpers'; import { HarnessIntegrationConfig } from './config'; import { getHarnessEditContentsUrl, @@ -59,26 +59,26 @@ describe('Harness code core', () => { }); }); - describe('getGerritRequestOptions', () => { + describe('getHarnessRequestOptions', () => { it('adds token header when only a token is specified', () => { const authRequest: HarnessIntegrationConfig = { - host: 'gerrit.com', + host: 'app.harness.io', token: 'P', }; const anonymousRequest: HarnessIntegrationConfig = { - host: 'gerrit.com', + host: 'app.harness.io', }; expect( (getHarnessRequestOptions(authRequest).headers as any).Authorization, ).toEqual('Bearer P'); - expect( - getHarnessRequestOptions(anonymousRequest).headers, - ).toBeUndefined(); + expect(getHarnessRequestOptions(anonymousRequest).headers).toStrictEqual( + {}, + ); }); it('adds basic auth when apikey and token are specified', () => { const authRequest: HarnessIntegrationConfig = { - host: 'gerrit.com', + host: 'app.harness.io', token: 'P', apiKey: 'a', }; diff --git a/packages/integration/src/harness/core.ts b/packages/integration/src/harness/core.ts index 8eba850ec2..3a61905e27 100644 --- a/packages/integration/src/harness/core.ts +++ b/packages/integration/src/harness/core.ts @@ -120,10 +120,6 @@ export function getHarnessRequestOptions(config: HarnessIntegrationConfig): { const headers: Record<string, string> = {}; const { token, apiKey } = config; - if (!token) { - return headers; - } - if (apiKey) { headers['x-api-key'] = apiKey; } else if (token) { From 677b10650b806d743e0f6ad1b2718fb11d8cf3c5 Mon Sep 17 00:00:00 2001 From: Calvin Lee <cjlee@ualberta.ca> Date: Tue, 30 Apr 2024 13:23:30 -0600 Subject: [PATCH 186/234] integration support for harness - fixed comments p2 Signed-off-by: Calvin Lee <cjlee@ualberta.ca> --- yarn.lock | 1 - 1 file changed, 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 60b03d4399..3cf5a79175 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4368,7 +4368,6 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/config-loader": "workspace:^" "@backstage/errors": "workspace:^" - "@backstage/test-utils": "workspace:^" "@octokit/auth-app": ^4.0.0 "@octokit/rest": ^19.0.3 "@types/luxon": ^3.0.0 From c123331ba4b2c3de1163188c04f0a3ec4bc1ee99 Mon Sep 17 00:00:00 2001 From: Andre Wanlin <awanlin@spotify.com> Date: Wed, 1 May 2024 07:46:44 -0500 Subject: [PATCH 187/234] Updated commit status importance Signed-off-by: Andre Wanlin <awanlin@spotify.com> --- docs/integrations/github/github-apps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integrations/github/github-apps.md b/docs/integrations/github/github-apps.md index 96363bc719..604713a490 100644 --- a/docs/integrations/github/github-apps.md +++ b/docs/integrations/github/github-apps.md @@ -125,6 +125,7 @@ integration: - Reading software components: - `Contents`: `Read-only` + - `Commit statuses`: `Read-only` - Reading organization data: - `Members`: `Read-only` - Publishing software templates: @@ -135,7 +136,6 @@ integration: - `Pull requests`: `Read & write` - `Issues`: `Read & write` - `Workflows`: `Read & write` (if templates include GitHub workflows) - - `Commit statuses`: `Read-only` - `Variables`: `Read & write` (if templates include GitHub Action Repository Variables) - `Secrets`: `Read & write` (if templates include GitHub Action Repository Secrets) - `Environments`: `Read & write` (if templates include GitHub Environments) From 4ccc2f8e0f67c70bb1fc1c560abb97a17c079c11 Mon Sep 17 00:00:00 2001 From: npiyush97 <npiyush35@gmail.com> Date: Wed, 1 May 2024 20:13:07 +0530 Subject: [PATCH 188/234] fix regex to remove comments Signed-off-by: npiyush97 <npiyush35@gmail.com> --- microsite/docusaurus.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/docusaurus.config.ts b/microsite/docusaurus.config.ts index e2ae4ab338..5f6dfd3bc6 100644 --- a/microsite/docusaurus.config.ts +++ b/microsite/docusaurus.config.ts @@ -77,7 +77,7 @@ const config: Config = { let previous; do { previous = input; - input = input.replace(/<!--|--!?>/g, ''); + input = input.replace(/<!--.*?-->/gs, ''); } while (input !== previous); return input; } From 12a5feff7a9fd4c6e3c844f25da850aff8de77d6 Mon Sep 17 00:00:00 2001 From: Chap Ambrose <cambrose@salesforce.com> Date: Tue, 30 Apr 2024 16:52:20 -0500 Subject: [PATCH 189/234] seperate ensureSchemaExists config Signed-off-by: Chap Ambrose <cambrose@salesforce.com> --- .../src/database/connectors/postgres.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index 1ddc096e05..1aa4d2afa9 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -321,7 +321,7 @@ export class PgConnector implements Connector { let schemaOverrides; if (this.getPluginDivisionModeConfig() === 'schema') { schemaOverrides = this.getSchemaOverrides(pluginId); - if (this.getEnsureExistsConfig(pluginId)) { + if (this.getEnsureSchemaExistsConfig(pluginId)) { try { await pgConnector.ensureSchemaExists!(pluginConfig, pluginId); } catch (error) { @@ -437,6 +437,15 @@ export class PgConnector implements Connector { ); } + private getEnsureSchemaExistsConfig(pluginId: string): boolean { + const baseConfig = + this.config.getOptionalBoolean('ensureSchemaExists') ?? true; + return ( + this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ?? + baseConfig + ); + } + private getPluginDivisionModeConfig(): string { return this.config.getOptionalString('pluginDivisionMode') ?? 'database'; } From 86ae51bb4ad512d9c569e192fcf5ccaa0cf8b698 Mon Sep 17 00:00:00 2001 From: Chap Ambrose <cambrose@salesforce.com> Date: Wed, 1 May 2024 08:38:17 -0500 Subject: [PATCH 190/234] fixed getEnsureSchemaExistsConfig Signed-off-by: Chap Ambrose <cambrose@salesforce.com> --- packages/backend-common/src/database/connectors/postgres.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index 1aa4d2afa9..126c8cb89c 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -441,8 +441,9 @@ export class PgConnector implements Connector { const baseConfig = this.config.getOptionalBoolean('ensureSchemaExists') ?? true; return ( - this.config.getOptionalBoolean(`${pluginPath(pluginId)}.ensureExists`) ?? - baseConfig + this.config.getOptionalBoolean( + `${pluginPath(pluginId)}.getEnsureSchemaExistsConfig`, + ) ?? baseConfig ); } From ccc8851bd01a9f05bc4ce8e71f11a417c90cfdb4 Mon Sep 17 00:00:00 2001 From: Chap Ambrose <cambrose@salesforce.com> Date: Wed, 1 May 2024 09:05:30 -0500 Subject: [PATCH 191/234] add changeset Signed-off-by: Chap Ambrose <cambrose@salesforce.com> --- .changeset/heavy-trainers-fly.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/heavy-trainers-fly.md diff --git a/.changeset/heavy-trainers-fly.md b/.changeset/heavy-trainers-fly.md new file mode 100644 index 0000000000..db09f63496 --- /dev/null +++ b/.changeset/heavy-trainers-fly.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +add ensureSchemaExists backend database config From 0b8b8e80c8a6bfe74420d40c0f91fa6dcdcfeefe Mon Sep 17 00:00:00 2001 From: Chap Ambrose <cambrose@salesforce.com> Date: Wed, 1 May 2024 10:07:08 -0500 Subject: [PATCH 192/234] set ensureSchemaExists to false to match current behavior Signed-off-by: Chap Ambrose <cambrose@salesforce.com> --- .changeset/heavy-trainers-fly.md | 2 +- .../backend-common/src/database/connectors/postgres.ts | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.changeset/heavy-trainers-fly.md b/.changeset/heavy-trainers-fly.md index db09f63496..48560b7ddd 100644 --- a/.changeset/heavy-trainers-fly.md +++ b/.changeset/heavy-trainers-fly.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -add ensureSchemaExists backend database config +Added config prop `ensureSchemaExists` to support postgres instances where user can create schemas but not databases. diff --git a/packages/backend-common/src/database/connectors/postgres.ts b/packages/backend-common/src/database/connectors/postgres.ts index 126c8cb89c..b81bfd5b50 100644 --- a/packages/backend-common/src/database/connectors/postgres.ts +++ b/packages/backend-common/src/database/connectors/postgres.ts @@ -321,7 +321,10 @@ export class PgConnector implements Connector { let schemaOverrides; if (this.getPluginDivisionModeConfig() === 'schema') { schemaOverrides = this.getSchemaOverrides(pluginId); - if (this.getEnsureSchemaExistsConfig(pluginId)) { + if ( + this.getEnsureSchemaExistsConfig(pluginId) || + this.getEnsureExistsConfig(pluginId) + ) { try { await pgConnector.ensureSchemaExists!(pluginConfig, pluginId); } catch (error) { @@ -439,7 +442,7 @@ export class PgConnector implements Connector { private getEnsureSchemaExistsConfig(pluginId: string): boolean { const baseConfig = - this.config.getOptionalBoolean('ensureSchemaExists') ?? true; + this.config.getOptionalBoolean('ensureSchemaExists') ?? false; return ( this.config.getOptionalBoolean( `${pluginPath(pluginId)}.getEnsureSchemaExistsConfig`, From f1ee6899c91d0561ae750d5b0a82826c6458b2bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 1 May 2024 15:23:27 +0000 Subject: [PATCH 193/234] chore(deps): bump ejs from 3.1.9 to 3.1.10 Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10. - [Release notes](https://github.com/mde/ejs/releases) - [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10) --- updated-dependencies: - dependency-name: ejs dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3cf5a79175..f50de49dfc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22872,13 +22872,13 @@ __metadata: linkType: hard "ejs@npm:^3.1.6": - version: 3.1.9 - resolution: "ejs@npm:3.1.9" + version: 3.1.10 + resolution: "ejs@npm:3.1.10" dependencies: jake: ^10.8.5 bin: ejs: bin/cli.js - checksum: af6f10eb815885ff8a8cfacc42c6b6cf87daf97a4884f87a30e0c3271fedd85d76a3a297d9c33a70e735b97ee632887f85e32854b9cdd3a2d97edf931519a35f + checksum: ce90637e9c7538663ae023b8a7a380b2ef7cc4096de70be85abf5a3b9641912dde65353211d05e24d56b1f242d71185c6d00e02cb8860701d571786d92c71f05 languageName: node linkType: hard From d541ff686f828c9b7a18e447e2dc81359f5918fe Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Thu, 2 May 2024 08:11:35 +0300 Subject: [PATCH 194/234] fix: email processor esm issue with p-throttle + config read Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .changeset/cyan-eagles-hammer.md | 6 ++++++ .github/renovate.json5 | 4 ++++ .../notifications-backend-module-email/package.json | 2 +- .../processor/NotificationsEmailProcessor.test.ts | 12 ++++++------ .../src/processor/NotificationsEmailProcessor.ts | 2 +- yarn.lock | 10 +++++----- 6 files changed, 23 insertions(+), 13 deletions(-) create mode 100644 .changeset/cyan-eagles-hammer.md diff --git a/.changeset/cyan-eagles-hammer.md b/.changeset/cyan-eagles-hammer.md new file mode 100644 index 0000000000..dfe6f0bc62 --- /dev/null +++ b/.changeset/cyan-eagles-hammer.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +'@backstage/plugin-notifications-backend': patch +--- + +Fixed email processor `esm` issue and config reading diff --git a/.github/renovate.json5 b/.github/renovate.json5 index a9cb54912a..1c8d611c49 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -62,6 +62,10 @@ matchPackageNames: ['p-limit'], allowedVersions: '<4.0.0', }, + { + matchPackageNames: ['p-throttle'], + allowedVersions: '<4.0.0', + }, { matchPackageNames: ['p-queue'], allowedVersions: '<7.0.0', diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 94b5e10578..bb99d9cb97 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -45,7 +45,7 @@ "@backstage/types": "workspace:^", "lodash": "^4.17.21", "nodemailer": "^6.9.13", - "p-throttle": "^6.1.0" + "p-throttle": "^4.1.1" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index 3af1e498ca..11eef9b999 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -52,7 +52,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'smtp', hostname: 'localhost', port: 465, @@ -98,7 +98,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'ses', region: 'us-west-2', }, @@ -138,7 +138,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'sendmail', path: '/usr/local/bin/sendmail', }, @@ -189,7 +189,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'sendmail', path: '/usr/local/bin/sendmail', }, @@ -246,7 +246,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'sendmail', path: '/usr/local/bin/sendmail', }, @@ -306,7 +306,7 @@ describe('NotificationsEmailProcessor', () => { notifications: { processors: { email: { - transport: { + transportConfig: { transport: 'sendmail', path: '/usr/local/bin/sendmail', }, diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index 5387d09c3d..ce944d9592 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -62,7 +62,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { const emailProcessorConfig = config.getConfig( 'notifications.processors.email', ); - this.transportConfig = emailProcessorConfig.getConfig('transport'); + this.transportConfig = emailProcessorConfig.getConfig('transportConfig'); this.broadcastConfig = emailProcessorConfig.getOptionalConfig('broadcastConfig'); this.sender = emailProcessorConfig.getString('sender'); diff --git a/yarn.lock b/yarn.lock index 3cf5a79175..6b4def9d8e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6193,7 +6193,7 @@ __metadata: "@types/nodemailer": ^6.4.14 lodash: ^4.17.21 nodemailer: ^6.9.13 - p-throttle: ^6.1.0 + p-throttle: ^4.1.1 languageName: unknown linkType: soft @@ -33449,10 +33449,10 @@ __metadata: languageName: node linkType: hard -"p-throttle@npm:^6.1.0": - version: 6.1.0 - resolution: "p-throttle@npm:6.1.0" - checksum: c1947cca8844564c3d86f8c09067add5e7398b87898cb1f1aea5c48d2c211590d039a316d28a4b0d95364ffa0213c01dff6bf71175c468eab0e381f77715dbdb +"p-throttle@npm:^4.1.1": + version: 4.1.1 + resolution: "p-throttle@npm:4.1.1" + checksum: fe8709f3c3b1da7c033479375c2c302e80c1a5d86449013afa7cd46d1dc210bc824a7e4a9d088e66d31987d00878c2b5491bb2fe76246d4d2fc9a1636f5f8298 languageName: node linkType: hard From 6a8e728cf2686dbde09c0b58eb66e53ccbf76c4f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Thu, 2 May 2024 13:38:53 +0200 Subject: [PATCH 195/234] docs/architecture-overview: update to include new backend system Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- .../package-architecture.drawio.svg | 663 ++++++++++-------- 1 file changed, 369 insertions(+), 294 deletions(-) diff --git a/docs/assets/architecture-overview/package-architecture.drawio.svg b/docs/assets/architecture-overview/package-architecture.drawio.svg index df8c0f8820..89fab2647d 100644 --- a/docs/assets/architecture-overview/package-architecture.drawio.svg +++ b/docs/assets/architecture-overview/package-architecture.drawio.svg @@ -1,69 +1,66 @@ -<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1261px" height="642px" viewBox="-0.5 -0.5 1261 642" content="<mxfile><diagram id="M4OCM2KiCGRnt6vHj1W_" name="Page-1">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</diagram></mxfile>" style="background-color: rgb(255, 255, 255);"> +<svg host="65bd71144e" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1261px" height="811px" viewBox="-0.5 -0.5 1261 811" content="<mxfile><diagram id="M4OCM2KiCGRnt6vHj1W_" name="Page-1">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</diagram></mxfile>" style="background-color: rgb(255, 255, 255);"> <defs/> <g> - <rect x="0" y="0" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="all"/> - <rect x="180" y="500" width="100" height="130" fill="none" stroke="#000000" pointer-events="all"/> - <path d="M 160 245 L 253.88 245" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 258.88 245 L 253.88 246.67 L 253.88 243.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> - <path d="M 120 230 Q 120 230 120 136.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 120 131.12 L 121.67 136.12 L 118.33 136.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> - <path d="M 120 260 Q 120 305 143.88 305" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 148.88 305 L 143.88 306.67 L 143.88 303.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> - <path d="M 140 230 Q 140 137.5 243.88 137.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 248.88 137.5 L 243.88 139.17 L 243.88 135.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> + <rect x="180" y="670" width="100" height="130" fill="none" stroke="rgb(0, 0, 0)" pointer-events="all"/> + <path d="M 160 245 L 253.88 245" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 258.88 245 L 253.88 246.67 L 253.88 243.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 120 230 Q 120 230 120 136.12" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 120 131.12 L 121.67 136.12 L 118.33 136.12 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 120 260 Q 120 305 143.88 305" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 148.88 305 L 143.88 306.67 L 143.88 303.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 140 230 Q 140 137.5 243.88 137.5" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 248.88 137.5 L 243.88 139.17 L 243.88 135.83 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> <rect x="80" y="230" width="80" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 70px; height: 1px; padding-top: 245px; margin-left: 90px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> app </div> </div> </div> </foreignObject> - <text x="90" y="249" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="90" y="249" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> app </text> </switch> </g> - <path d="M 120 390 Q 120 345 143.88 345" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 148.88 345 L 143.88 346.67 L 143.88 343.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> - <path d="M 160 405 L 253.88 405" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 258.88 405 L 253.88 406.67 L 253.88 403.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> - <path d="M 140 420 Q 140 450 347.5 450 Q 555 450 555 473.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 555 478.88 L 553.33 473.88 L 556.67 473.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 120 390 Q 120 345 143.88 345" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 148.88 345 L 143.88 346.67 L 143.88 343.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 160 405 L 253.88 405" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 258.88 405 L 253.88 406.67 L 253.88 403.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> <rect x="80" y="390" width="80" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 70px; height: 1px; padding-top: 405px; margin-left: 90px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"> backend </div> </div> </div> </foreignObject> - <text x="90" y="409" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="90" y="409" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> backend </text> </switch> </g> - <path d="M 387.5 230 Q 387.5 205 378.75 205 Q 370 205 370 186.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 370 181.12 L 371.67 186.12 L 368.33 186.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> - <path d="M 430 245 Q 475 245 475 266.25 Q 475 287.5 513.88 287.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 518.88 287.5 L 513.88 289.17 L 513.88 285.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> - <path d="M 430 237.5 Q 585 237.5 585 233.75 Q 585 230 690 230 Q 795 230 795 243.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 795 248.88 L 793.33 243.88 L 796.67 243.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 387.5 230 Q 387.5 205 378.75 205 Q 370 205 370 186.12" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 370 181.12 L 371.67 186.12 L 368.33 186.12 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 430 245 Q 475 245 475 266.25 Q 475 287.5 513.88 287.5" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 518.88 287.5 L 513.88 289.17 L 513.88 285.83 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 430 237.5 Q 585 237.5 585 233.75 Q 585 230 690 230 Q 795 230 795 243.88" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 795 248.88 L 793.33 243.88 L 796.67 243.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> <rect x="260" y="230" width="170" height="30" fill="#dae8fc" stroke="#6c8ebf" stroke-width="3" pointer-events="all"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 160px; height: 1px; padding-top: 245px; margin-left: 270px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-style: italic; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-style: italic; white-space: normal; overflow-wrap: normal;"> plugin-<plugin-id> </div> </div> @@ -74,19 +71,19 @@ </text> </switch> </g> - <path d="M 387.5 420 Q 387.5 555 493.88 555" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 498.88 555 L 493.88 556.67 L 493.88 553.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> - <path d="M 430 412.5 Q 585 412.5 585 416.25 Q 585 420 690 420 Q 795 420 795 406.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 795 401.12 L 796.67 406.12 L 793.33 406.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> - <path d="M 430 405 Q 475 405 475 383.75 Q 475 362.5 513.88 362.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/> - <path d="M 518.88 362.5 L 513.88 364.17 L 513.88 360.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 387.5 420 Q 387.5 445 383.75 445 Q 380 445 380 463.88" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 380 468.88 L 378.33 463.88 L 381.67 463.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 430 412.5 Q 585 412.5 585 416.25 Q 585 420 690 420 Q 795 420 795 406.12" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 795 401.12 L 796.67 406.12 L 793.33 406.12 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 430 405 Q 475 405 475 383.75 Q 475 362.5 513.88 362.5" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/> + <path d="M 518.88 362.5 L 513.88 364.17 L 513.88 360.83 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> <rect x="260" y="390" width="170" height="30" fill="#fff2cc" stroke="#d6b656" stroke-width="3" pointer-events="all"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 160px; height: 1px; padding-top: 405px; margin-left: 270px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-style: italic; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; font-style: italic; white-space: normal; overflow-wrap: normal;"> plugin-<plugin-id>-backend </div> </div> @@ -97,69 +94,9 @@ </text> </switch> </g> - <path d="M 500 480 L 500 480 L 720 480 L 720 480" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/> - <path d="M 500 480 L 500 630 L 720 630 L 720 480" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 500 480 L 720 480" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> - <text x="609.5" y="497.5"> - Backend Libraries - </text> - </g> - <rect x="520" y="510" width="180" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> - <g transform="translate(-0.5 -0.5)"> - <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> - <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 525px; margin-left: 530px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> - @backstage/backend-common - </div> - </div> - </div> - </foreignObject> - <text x="530" y="529" fill="#000000" font-family="Helvetica" font-size="12px"> - @backstage/backend-common - </text> - </switch> - </g> - <rect x="520" y="550" width="180" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> - <g transform="translate(-0.5 -0.5)"> - <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> - <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 565px; margin-left: 530px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> - @backstage/backend-test-utils - </div> - </div> - </div> - </foreignObject> - <text x="530" y="569" fill="#000000" font-family="Helvetica" font-size="12px"> - @backstage/backend-test-utils - </text> - </switch> - </g> - <rect x="520" y="590" width="180" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> - <g transform="translate(-0.5 -0.5)"> - <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> - <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 605px; margin-left: 530px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> - @backstage/backend-tasks - </div> - </div> - </div> - </foreignObject> - <text x="530" y="609" fill="#000000" font-family="Helvetica" font-size="12px"> - @backstage/backend-tasks - </text> - </switch> - </g> - <path d="M 1030 250 L 1030 250 L 1250 250 L 1250 250" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 1030 250 L 1030 520 L 1250 520 L 1250 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 1030 250 L 1250 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> + <path d="M 1030 250 L 1030 250 L 1250 250 L 1250 250" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/> + <path d="M 1030 250 L 1030 520 L 1250 520 L 1250 250" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <g fill="rgb(0, 0, 0)" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> <text x="1139.5" y="267.5"> Common Libraries </text> @@ -167,16 +104,16 @@ <rect x="1050" y="440" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 455px; margin-left: 1060px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/catalog-client </div> </div> </div> </foreignObject> - <text x="1060" y="459" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="1060" y="459" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/catalog-client </text> </switch> @@ -184,16 +121,16 @@ <rect x="1050" y="280" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 295px; margin-left: 1060px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/types </div> </div> </div> </foreignObject> - <text x="1060" y="299" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="1060" y="299" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/types </text> </switch> @@ -201,16 +138,16 @@ <rect x="1050" y="320" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 335px; margin-left: 1060px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/config </div> </div> </div> </foreignObject> - <text x="1060" y="339" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="1060" y="339" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/config </text> </switch> @@ -218,16 +155,16 @@ <rect x="1050" y="360" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 375px; margin-left: 1060px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/errors </div> </div> </div> </foreignObject> - <text x="1060" y="379" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="1060" y="379" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/errors </text> </switch> @@ -235,16 +172,16 @@ <rect x="1050" y="400" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 415px; margin-left: 1060px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/catalog-model </div> </div> </div> </foreignObject> - <text x="1060" y="419" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="1060" y="419" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/catalog-model </text> </switch> @@ -252,24 +189,23 @@ <rect x="1050" y="480" width="180" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 495px; margin-left: 1060px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/integration </div> </div> </div> </foreignObject> - <text x="1060" y="499" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="1060" y="499" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/integration </text> </switch> </g> - <path d="M 10 20 L 10 20 L 230 20 L 230 20" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 10 20 L 10 130 L 230 130 L 230 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 10 20 L 230 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> + <path d="M 10 20 L 10 20 L 230 20 L 230 20" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 10 20 L 10 130 L 230 130 L 230 20" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <g fill="rgb(0, 0, 0)" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> <text x="119.5" y="37.5"> Frontend App Core </text> @@ -277,16 +213,16 @@ <rect x="30" y="50" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 65px; margin-left: 40px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/core-app-api </div> </div> </div> </foreignObject> - <text x="40" y="69" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="40" y="69" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/core-app-api </text> </switch> @@ -294,36 +230,36 @@ <rect x="30" y="90" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 105px; margin-left: 40px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/app-defaults </div> </div> </div> </foreignObject> - <text x="40" y="109" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="40" y="109" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/app-defaults </text> </switch> </g> - <path d="M 988 305 Q 1010 305 1010 345 Q 1010 385 1023.88 385" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 1028.88 385 L 1023.88 386.67 L 1023.88 383.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 988 305 Q 1010 305 1010 250 Q 1010 195 1023.88 195" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 1028.88 195 L 1023.88 196.67 L 1023.88 193.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 990 10 L 985 10 Q 980 10 980 20 L 980 295 Q 980 305 975 305 L 972.5 305 Q 970 305 975 305 L 977.5 305 Q 980 305 980 315 L 980 590 Q 980 600 985 600 L 990 600" fill="none" stroke="#000000" stroke-miterlimit="10" transform="translate(980,0)scale(-1,1)translate(-980,0)" pointer-events="none"/> - <path d="M 430 345 Q 475 345 475 353.75 Q 475 362.5 513.88 362.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 518.88 362.5 L 513.88 364.17 L 513.88 360.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 430 352.5 Q 465 352.5 465 435 Q 465 517.5 493.88 517.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 498.88 517.5 L 493.88 519.17 L 493.88 515.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 988 305 Q 1010 305 1010 345 Q 1010 385 1023.88 385" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 1028.88 385 L 1023.88 386.67 L 1023.88 383.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 988 305 Q 1010 305 1010 250 Q 1010 195 1023.88 195" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 1028.88 195 L 1023.88 196.67 L 1023.88 193.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 990 10 L 985 10 Q 980 10 980 20 L 980 295 Q 980 305 975 305 L 972.5 305 Q 970 305 975 305 L 977.5 305 Q 980 305 980 315 L 980 590 Q 980 600 985 600 L 990 600" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" transform="translate(980,0)scale(-1,1)translate(-980,0)" pointer-events="none"/> + <path d="M 430 345 Q 475 345 475 353.75 Q 475 362.5 513.88 362.5" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 518.88 362.5 L 513.88 364.17 L 513.88 360.83 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 430 352.5 Q 468.4 352.5 468.4 463.2" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 468.4 468.2 L 466.73 463.2 L 470.07 463.2 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> <rect x="150" y="330" width="280" height="30" fill="#fff2cc" stroke="#d6b656" stroke-width="3" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 270px; height: 1px; padding-top: 345px; margin-left: 160px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; overflow-wrap: normal;"> plugin-<plugin-id>-backend-module-<module-id> </div> </div> @@ -334,17 +270,17 @@ </text> </switch> </g> - <path d="M 430 305 Q 475 305 475 296.25 Q 475 287.5 513.88 287.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 518.88 287.5 L 513.88 289.17 L 513.88 285.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 430 297.5 Q 464.08 297.5 464.08 186.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 464.08 181.12 L 465.75 186.12 L 462.41 186.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 430 305 Q 475 305 475 296.25 Q 475 287.5 513.88 287.5" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 518.88 287.5 L 513.88 289.17 L 513.88 285.83 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 430 297.5 Q 464.1 297.5 464.08 186.12" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 464.08 181.12 L 465.75 186.12 L 462.41 186.12 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> <rect x="150" y="290" width="280" height="30" fill="#dae8fc" stroke="#6c8ebf" stroke-width="3" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 270px; height: 1px; padding-top: 305px; margin-left: 160px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; overflow-wrap: normal;"> plugin-<plugin-id>-module-<module-id> </div> </div> @@ -355,10 +291,9 @@ </text> </switch> </g> - <path d="M 1030 160 L 1030 160 L 1250 160 L 1250 160" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 1030 160 L 1030 230 L 1250 230 L 1250 160" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 1030 160 L 1250 160" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> + <path d="M 1030 160 L 1030 160 L 1250 160 L 1250 160" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 1030 160 L 1030 230 L 1250 230 L 1250 160" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <g fill="rgb(0, 0, 0)" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> <text x="1139.5" y="177.5"> Common Tooling </text> @@ -366,28 +301,27 @@ <rect x="1050" y="190" width="180" height="30" fill="#e1d5e7" stroke="#9673a6" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 205px; margin-left: 1060px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/cli </div> </div> </div> </foreignObject> - <text x="1060" y="209" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="1060" y="209" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/cli </text> </switch> </g> - <path d="M 850 400 Q 850 555 726.12 555" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 721.12 555 L 726.12 553.33 L 726.12 556.67 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 850 250 Q 850 95 736.12 95" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 731.12 95 L 736.12 93.33 L 736.12 96.67 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 740 250 L 740 250 L 960 250 L 960 250" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 740 250 L 740 400 L 960 400 L 960 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 740 250 L 960 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> + <path d="M 850 400 Q 850 555 776.12 555" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 771.12 555 L 776.12 553.33 L 776.12 556.67 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 850 250 Q 850 95 736.12 95" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 731.12 95 L 736.12 93.33 L 736.12 96.67 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 740 250 L 740 250 L 960 250 L 960 250" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 740 250 L 740 400 L 960 400 L 960 250" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <g fill="rgb(0, 0, 0)" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> <text x="849.5" y="267.5"> External Plugin Libraries </text> @@ -395,16 +329,16 @@ <rect x="750" y="280" width="200" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 190px; height: 1px; padding-top: 295px; margin-left: 760px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> plugin-<other-plugin-id>-react </div> </div> </div> </foreignObject> - <text x="760" y="299" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="760" y="299" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> plugin-<other-plugin-id>-react </text> </switch> @@ -412,16 +346,16 @@ <rect x="750" y="320" width="200" height="30" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 190px; height: 1px; padding-top: 335px; margin-left: 760px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> plugin-<other-plugin-id>-common </div> </div> </div> </foreignObject> - <text x="760" y="339" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="760" y="339" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> plugin-<other-plugin-id>-common </text> </switch> @@ -429,30 +363,29 @@ <rect x="750" y="360" width="200" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 190px; height: 1px; padding-top: 375px; margin-left: 760px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> plugin-<other-plugin-id>-node </div> </div> </div> </foreignObject> - <text x="760" y="379" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="760" y="379" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> plugin-<other-plugin-id>-node </text> </switch> </g> - <path d="M 700 325 Q 700 325 733.88 325" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 738.88 325 L 733.88 326.67 L 733.88 323.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 610 400 Q 610 400 610 473.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 610 478.88 L 608.33 473.88 L 611.67 473.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 610 250 Q 610 250 610 186.12" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 610 181.12 L 611.67 186.12 L 608.33 186.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 520 250 L 520 250 L 700 250 L 700 250" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 520 250 L 520 400 L 700 400 L 700 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 520 250 L 700 250" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> + <path d="M 700 325 Q 700 325 733.88 325" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 738.88 325 L 733.88 326.67 L 733.88 323.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 610 400 Q 610 435 609.87 462.35" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 609.85 467.35 L 608.2 462.34 L 611.54 462.36 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 610 250 Q 610 250 610 186.12" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 610 181.12 L 611.67 186.12 L 608.33 186.12 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 520 250 L 520 250 L 700 250 L 700 250" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 520 250 L 520 400 L 700 400 L 700 250" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <g fill="rgb(0, 0, 0)" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> <text x="609.5" y="267.5"> Plugin Libraries </text> @@ -460,10 +393,10 @@ <rect x="530" y="280" width="160" height="30" fill="#dae8fc" stroke="#6c8ebf" stroke-width="3" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 150px; height: 1px; padding-top: 295px; margin-left: 540px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; overflow-wrap: normal;"> plugin-<plugin-id>-react </div> </div> @@ -477,10 +410,10 @@ <rect x="530" y="320" width="160" height="30" fill="#d5e8d4" stroke="#82b366" stroke-width="3" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 150px; height: 1px; padding-top: 335px; margin-left: 540px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; overflow-wrap: normal;"> plugin-<plugin-id>-common </div> </div> @@ -494,10 +427,10 @@ <rect x="530" y="360" width="160" height="30" fill="#fff2cc" stroke="#d6b656" stroke-width="3" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 150px; height: 1px; padding-top: 375px; margin-left: 540px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: #000000; " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; font-style: italic; white-space: normal; overflow-wrap: normal;"> plugin-<plugin-id>-node </div> </div> @@ -508,13 +441,11 @@ </text> </switch> </g> - <path d="M 250 10 L 250 10 L 730 10 L 730 10" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/> - <path d="M 250 10 L 250 180 L 730 180 L 730 10" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/> - <path d="M 250 10 L 730 10" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/> - <path d="M 260 20 L 260 20 L 480 20 L 480 20" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 260 20 L 260 170 L 480 170 L 480 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 260 20 L 480 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> + <path d="M 250 10 L 250 10 L 730 10 L 730 10" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/> + <path d="M 250 10 L 250 180 L 730 180 L 730 10" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/> + <path d="M 260 20 L 260 20 L 480 20 L 480 20" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 260 20 L 260 170 L 480 170 L 480 20" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <g fill="rgb(0, 0, 0)" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> <text x="369.5" y="37.5"> Frontend Plugin Core </text> @@ -522,16 +453,16 @@ <rect x="280" y="50" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 65px; margin-left: 290px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/core-plugin-api </div> </div> </div> </foreignObject> - <text x="290" y="69" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="290" y="69" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/core-plugin-api </text> </switch> @@ -539,16 +470,16 @@ <rect x="280" y="90" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 105px; margin-left: 290px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/test-utils </div> </div> </div> </foreignObject> - <text x="290" y="109" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="290" y="109" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/test-utils </text> </switch> @@ -556,24 +487,23 @@ <rect x="280" y="130" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 145px; margin-left: 290px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/dev-utils </div> </div> </div> </foreignObject> - <text x="290" y="149" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="290" y="149" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/dev-utils </text> </switch> </g> - <path d="M 500 20 L 500 20 L 720 20 L 720 20" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 500 20 L 500 170 L 720 170 L 720 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 500 20 L 720 20" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <g fill="#000000" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> + <path d="M 500 20 L 500 20 L 720 20 L 720 20" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 500 20 L 500 170 L 720 170 L 720 20" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <g fill="rgb(0, 0, 0)" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> <text x="609.5" y="37.5"> Frontend Libraries </text> @@ -581,16 +511,16 @@ <rect x="520" y="130" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 145px; margin-left: 530px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/integration-react </div> </div> </div> </foreignObject> - <text x="530" y="149" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="530" y="149" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/integration-react </text> </switch> @@ -598,16 +528,16 @@ <rect x="520" y="50" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 65px; margin-left: 530px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/core-components </div> </div> </div> </foreignObject> - <text x="530" y="69" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="530" y="69" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/core-components </text> </switch> @@ -615,131 +545,276 @@ <rect x="520" y="90" width="180" height="30" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 105px; margin-left: 530px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> @backstage/theme </div> </div> </div> </foreignObject> - <text x="530" y="109" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="530" y="109" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> @backstage/theme </text> </switch> </g> - <rect x="10" y="500" width="160" height="130" fill="none" stroke="#000000" pointer-events="none"/> - <rect x="20" y="510" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> - <rect x="20" y="570" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> - <rect x="20" y="540" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> + <rect x="10" y="670" width="160" height="130" fill="none" stroke="rgb(0, 0, 0)" pointer-events="none"/> + <rect x="20" y="680" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> + <rect x="20" y="740" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> + <rect x="20" y="710" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> - <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 520px; margin-left: 50px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 690px; margin-left: 50px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> Frontend Package </div> </div> </div> </foreignObject> - <text x="50" y="524" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="50" y="694" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> Frontend Package </text> </switch> </g> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> - <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 550px; margin-left: 50px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 720px; margin-left: 50px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> Isomorphic Package </div> </div> </div> </foreignObject> - <text x="50" y="554" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="50" y="724" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> Isomorphic Package </text> </switch> </g> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> - <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 580px; margin-left: 50px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 750px; margin-left: 50px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> Backend Package </div> </div> </div> </foreignObject> - <text x="50" y="584" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="50" y="754" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> Backend Package </text> </switch> </g> - <rect x="20" y="600" width="20" height="20" fill="#e1d5e7" stroke="#9673a6" pointer-events="none"/> + <rect x="20" y="770" width="20" height="20" fill="#e1d5e7" stroke="#9673a6" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> - <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 610px; margin-left: 50px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 110px; height: 1px; padding-top: 780px; margin-left: 50px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> CLI Package </div> </div> </div> </foreignObject> - <text x="50" y="614" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="50" y="784" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> CLI Package </text> </switch> </g> - <rect x="190" y="540" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> - <rect x="190" y="600" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> - <rect x="190" y="570" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> - <rect x="250" y="540" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> - <path d="M 210 550 Q 210 550 243.88 550" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 248.88 550 L 243.88 551.67 L 243.88 548.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <rect x="250" y="600" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> - <path d="M 210 610 Q 210 610 243.88 610" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 248.88 610 L 243.88 611.67 L 243.88 608.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <rect x="250" y="570" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> - <path d="M 210 550 Q 230 550 230 562.5 Q 230 575 243.88 575" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 248.88 575 L 243.88 576.67 L 243.88 573.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 210 605 Q 230 605 230 595 Q 230 585 243.88 585" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 248.88 585 L 243.88 586.67 L 243.88 583.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 210 580 Q 210 580 243.88 580" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> - <path d="M 248.88 580 L 243.88 581.67 L 243.88 578.33 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"/> + <rect x="190" y="710" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> + <rect x="190" y="770" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> + <rect x="190" y="740" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> + <rect x="250" y="710" width="20" height="20" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"/> + <path d="M 210 720 Q 210 720 243.88 720" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 248.88 720 L 243.88 721.67 L 243.88 718.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <rect x="250" y="770" width="20" height="20" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> + <path d="M 210 780 Q 210 780 243.88 780" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 248.88 780 L 243.88 781.67 L 243.88 778.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <rect x="250" y="740" width="20" height="20" fill="#d5e8d4" stroke="#82b366" pointer-events="none"/> + <path d="M 210 720 Q 230 720 230 732.5 Q 230 745 243.88 745" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 248.88 745 L 243.88 746.67 L 243.88 743.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 210 775 Q 230 775 230 765 Q 230 755 243.88 755" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 248.88 755 L 243.88 756.67 L 243.88 753.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 210 750 Q 210 750 243.88 750" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 248.88 750 L 243.88 751.67 L 243.88 748.33 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> <g transform="translate(-0.5 -0.5)"> <switch> - <foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"> - <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 90px; height: 1px; padding-top: 520px; margin-left: 190px;"> - <div style="box-sizing: border-box; font-size: 0; text-align: left; "> - <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 90px; height: 1px; padding-top: 690px; margin-left: 190px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> Compatibility </div> </div> </div> </foreignObject> - <text x="190" y="524" fill="#000000" font-family="Helvetica" font-size="12px"> + <text x="190" y="694" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> Compatibility </text> </switch> </g> - <path d="M 251 589 L 251 570 L 270 589 Z" fill="#e1d5e7" stroke="#9673a6" stroke-miterlimit="10" transform="rotate(-180,260.5,579.5)" pointer-events="none"/> - <rect x="0" y="630" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="none"/> - <rect x="1250" y="630" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="none"/> - <rect x="1250" y="0" width="10" height="10" fill="#ffffff" stroke="none" pointer-events="none"/> + <path d="M 251 759 L 251 740 L 270 759 Z" fill="#e1d5e7" stroke="#9673a6" stroke-miterlimit="10" transform="rotate(-180,260.5,749.5)" pointer-events="none"/> + <rect x="0" y="800" width="10" height="10" fill="rgb(255, 255, 255)" stroke="none" pointer-events="none"/> + <rect x="1250" y="0" width="10" height="10" fill="rgb(255, 255, 255)" stroke="none" pointer-events="none"/> + <path d="M 10 520 L 10 520 L 230 520 L 230 520" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 10 520 L 10 630 L 230 630 L 230 520" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <g fill="rgb(0, 0, 0)" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> + <text x="119.5" y="537.5"> + Backend App Core + </text> + </g> + <rect x="30" y="550" width="180" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> + <g transform="translate(-0.5 -0.5)"> + <switch> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 565px; margin-left: 40px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> + @backstage/backend-app-api + </div> + </div> + </div> + </foreignObject> + <text x="40" y="569" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> + @backstage/backend-app-api + </text> + </switch> + </g> + <rect x="30" y="590" width="180" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> + <g transform="translate(-0.5 -0.5)"> + <switch> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 170px; height: 1px; padding-top: 605px; margin-left: 40px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> + @backstage/backend-defaults + </div> + </div> + </div> + </foreignObject> + <text x="40" y="609" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> + @backstage/backend-defaults + </text> + </switch> + </g> + <path d="M 120 420 Q 120 420 120 513.88" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 120 518.88 L 118.33 513.88 L 121.67 513.88 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 250 470 L 250 470 L 770 470 L 770 470" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/> + <path d="M 250 470 L 250 640 L 770 640 L 770 470" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="none"/> + <path d="M 259 480 L 259 480 L 490 480 L 490 480" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 259 480 L 259 630 L 490 630 L 490 480" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <g fill="rgb(0, 0, 0)" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> + <text x="374" y="497.5"> + Backend Plugin Core + </text> + </g> + <rect x="279" y="510" width="190" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> + <g transform="translate(-0.5 -0.5)"> + <switch> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 180px; height: 1px; padding-top: 525px; margin-left: 289px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> + @backstage/backend-plugin-api + </div> + </div> + </div> + </foreignObject> + <text x="289" y="529" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> + @backstage/backend-plugin-api + </text> + </switch> + </g> + <rect x="279" y="550" width="190" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> + <g transform="translate(-0.5 -0.5)"> + <switch> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 180px; height: 1px; padding-top: 565px; margin-left: 289px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> + @backstage/backend-test-utils + </div> + </div> + </div> + </foreignObject> + <text x="289" y="569" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> + @backstage/backend-test-utils + </text> + </switch> + </g> + <rect x="279" y="590" width="190" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> + <g transform="translate(-0.5 -0.5)"> + <switch> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 180px; height: 1px; padding-top: 605px; margin-left: 289px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> + @backstage/backend-dev-utils + </div> + </div> + </div> + </foreignObject> + <text x="289" y="609" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> + @backstage/backend-dev-utils + </text> + </switch> + </g> + <path d="M 510 480 L 510 480 L 760 480 L 760 480" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 510 480 L 510 630 L 760 630 L 760 480" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <g fill="rgb(0, 0, 0)" font-family="Helvetica" font-weight="bold" pointer-events="none" text-anchor="middle" font-size="12px"> + <text x="634.5" y="497.5"> + Backend Libraries + </text> + </g> + <rect x="530" y="510" width="210" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> + <g transform="translate(-0.5 -0.5)"> + <switch> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 200px; height: 1px; padding-top: 525px; margin-left: 540px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> + @backstage/backend-tasks + </div> + </div> + </div> + </foreignObject> + <text x="540" y="529" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> + @backstage/backend-tasks + </text> + </switch> + </g> + <rect x="525" y="550" width="210" height="30" fill="#fff2cc" stroke="#d6b656" pointer-events="none"/> + <g transform="translate(-0.5 -0.5)"> + <switch> + <foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"> + <div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 200px; height: 1px; padding-top: 565px; margin-left: 535px;"> + <div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"> + <div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: none; white-space: normal; overflow-wrap: normal;"> + @backstage/backend-openapi-utils + </div> + </div> + </div> + </foreignObject> + <text x="535" y="569" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px"> + @backstage/backend-openapi-utils + </text> + </switch> + </g> + <path d="M 140 420 Q 140 510 243.36 509.95" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> + <path d="M 248.36 509.95 L 243.36 511.62 L 243.36 508.29 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="none"/> </g> <switch> <g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/> <a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank"> <text text-anchor="middle" font-size="10px" x="50%" y="100%"> - Viewer does not support full SVG 1.1 + Text is not SVG - cannot display </text> </a> </switch> From ca7ba6694d65392e36a04fa4ffe61ae2dc6e29ec Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Thu, 2 May 2024 13:39:11 +0200 Subject: [PATCH 196/234] docs/versioning-policy: update to include new backend system Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- docs/overview/versioning-policy.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/docs/overview/versioning-policy.md b/docs/overview/versioning-policy.md index 0b1bc975bd..2ae73e1383 100644 --- a/docs/overview/versioning-policy.md +++ b/docs/overview/versioning-policy.md @@ -81,19 +81,21 @@ In order for Backstage to function properly the following versioning rules must be followed. The rules are referring to the [Package Architecture](https://backstage.io/docs/overview/architecture-overview#package-architecture). -- The versions of all the packages in the `Frontend App Core` must be from the - same release, and it is recommended to keep `Common Tooling` on that release - too. -- The Backstage dependencies of any given plugin should be from the same - release. This includes the packages from `Common Libraries`, - `Frontend Plugin Core`, and `Frontend Libraries`, or alternatively the - `Backend Libraries`. -- There must be no package that is from a newer release than the - `Frontend App Core` packages in the app. +- The versions of all packages for each of the "App Core" groups must be from the + same Backstage release. +- For each frontend and backend setup, the "App Core" packages must be ahead of or on the same Backstage release as the "Plugin Core" packages, including transitive dependencies of all installed plugins and modules. +- For any given plugin, the versions of all packages from the "Plugin Core" and + "Library" groups must be from the same Backstage release. - Frontend plugins with a corresponding backend plugin should be from the same release. The update to the backend plugin **MUST** be deployed before or together with the update to the frontend plugin. +It is allowed and often expected that the "Plugin Core" and "Library" packages +are from older releases than the "App Core" packages. It is also allowed to have +duplicate installations of the "Plugin Core" and "Library" packages. This is all +to make sure that upgrading Backstage is as smooth as possible and allows for +more flexibility across the entire plugin ecosystem. + ## Package Versioning Policy Every individual package is versioned according to [semver](https://semver.org). From 483a4f9425336091f53f79a6d6037a12dfba3f01 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Thu, 2 May 2024 13:59:48 +0200 Subject: [PATCH 197/234] microsite/data: update plugin link Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- microsite/data/plugins/betterscan.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/data/plugins/betterscan.yaml b/microsite/data/plugins/betterscan.yaml index 92d1eaa7b2..852ae0081c 100644 --- a/microsite/data/plugins/betterscan.yaml +++ b/microsite/data/plugins/betterscan.yaml @@ -4,7 +4,7 @@ author: Marcin Kozlowski authorUrl: https://betterscan.io category: Security description: View security scanned vulnerabilities in Code and Cloud scanned using Open Source and proprietary scanners directly in Backstage. -documentation: https://github.com/marcinguy/betterscan-ce +documentation: https://www.npmjs.com/package/@marcinguy/backstage-plugin-betterscan iconUrl: https://uploads-ssl.webflow.com/6339e3b81867539b5fe2498d/633a1643dcb06d3029867161_g4.svg npmPackageName: '@marcinguy/backstage-plugin-betterscan' addedDate: '2022-12-08' From 2a6f10d77a4d7b86f43a5d6915959093c6572970 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Thu, 2 May 2024 14:31:05 +0200 Subject: [PATCH 198/234] cli: only warn when bump fails + fix forbidden duplicate filter Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- .changeset/kind-toes-scream.md | 5 ++ .../cli/src/commands/versions/bump.test.ts | 65 +++++++------------ packages/cli/src/commands/versions/bump.ts | 26 ++++---- packages/cli/src/commands/versions/lint.ts | 5 +- 4 files changed, 41 insertions(+), 60 deletions(-) create mode 100644 .changeset/kind-toes-scream.md diff --git a/.changeset/kind-toes-scream.md b/.changeset/kind-toes-scream.md new file mode 100644 index 0000000000..d634f17ce4 --- /dev/null +++ b/.changeset/kind-toes-scream.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +The `versions:bump` command will no longer exit with a non-zero status if the version bump fails due to forbidden duplicate package installations. It will now also provide more information about how to troubleshoot such an error. The set of forbidden duplicates has also been expanded to include all `@backstage/*-app-api` packages. diff --git a/packages/cli/src/commands/versions/bump.test.ts b/packages/cli/src/commands/versions/bump.test.ts index c8174639a6..0dfe2f61c3 100644 --- a/packages/cli/src/commands/versions/bump.test.ts +++ b/packages/cli/src/commands/versions/bump.test.ts @@ -894,30 +894,19 @@ describe('bump', () => { newVersions: [], newRanges: [ { - name: 'first-duplicate', - oldRange: 'first-duplicate', - newRange: 'first-duplicate', - oldVersion: '1.0.0', - newVersion: '2.0.0', - }, - { - name: 'second-duplicate', - oldRange: 'second-duplicate', - newRange: 'second-duplicate', - oldVersion: '1.0.0', - newVersion: '2.0.0', - }, - { - name: 'third-duplicate', - oldRange: 'third-duplicate', - newRange: 'third-duplicate', + name: '@backstage/backend-app-api', + oldRange: '^1.0.0', + newRange: '^2.0.0', oldVersion: '1.0.0', newVersion: '2.0.0', }, ], }); mockDir.setContent({ - 'yarn.lock': lockfileMock, + 'yarn.lock': `${HEADER} +"@backstage/backend-app-api@^1.0.0": + version "1.0.0" +`, 'package.json': JSON.stringify({ workspaces: { packages: ['packages/*'], @@ -928,16 +917,7 @@ describe('bump', () => { 'package.json': JSON.stringify({ name: 'a', dependencies: { - '@backstage/core': '^1.0.5', - }, - }), - }, - b: { - 'package.json': JSON.stringify({ - name: 'b', - dependencies: { - '@backstage/core': '^1.0.3', - '@backstage/theme': '^1.0.0', + '@backstage/backend-app-api': '^1.0.0', }, }), }, @@ -952,7 +932,12 @@ describe('bump', () => { res( ctx.status(200), ctx.json({ - packages: [], + packages: [ + { + name: '@backstage/backend-app-api', + version: '2.0.0', + }, + ], }), ), ), @@ -962,24 +947,20 @@ describe('bump', () => { }); expectLogsToMatch(logs, [ 'Using default pattern glob @backstage/*', - 'Checking for updates of @backstage/core', - 'Checking for updates of @backstage/theme', - 'Checking for updates of @backstage/core-api', + 'Checking for updates of @backstage/backend-app-api', + 'Checking for updates of @backstage/backend-app-api', 'Some packages are outdated, updating', - 'unlocking @backstage/core@^1.0.3 ~> 1.0.6', - 'unlocking @backstage/core-api@^1.0.6 ~> 1.0.7', - 'unlocking @backstage/core-api@^1.0.3 ~> 1.0.7', - 'bumping @backstage/core in a to ^1.0.6', - 'bumping @backstage/core in b to ^1.0.6', - 'bumping @backstage/theme in b to ^2.0.0', + 'bumping @backstage/backend-app-api in a to ^2.0.0', 'Running yarn install to install new versions', 'Checking for moved packages to the @backstage-community namespace...', '⚠️ The following packages may have breaking changes:', - ' @backstage/theme : 1.0.0 ~> 2.0.0', - ' https://github.com/backstage/backstage/blob/master/packages/theme/CHANGELOG.md', + ' @backstage/backend-app-api : 1.0.0 ~> 2.0.0', + ' https://github.com/backstage/backstage/blob/master/packages/backend-app-api/CHANGELOG.md', 'Version bump complete!', - 'The following packages have duplicates but have been allowed:', - 'first-duplicate, second-duplicate, third-duplicate', + ' ⚠️ Warning! ⚠️', + ' The below package(s) have incompatible duplicate installations, likely due to a bad dependency in a plugin.', + ' You can investigate this by running `yarn why <package-name>`, and report the issue to the plugin maintainers.', + ' @backstage/backend-app-api', ]); }); }); diff --git a/packages/cli/src/commands/versions/bump.ts b/packages/cli/src/commands/versions/bump.ts index aad5a7831f..1544240704 100644 --- a/packages/cli/src/commands/versions/bump.ts +++ b/packages/cli/src/commands/versions/bump.ts @@ -331,24 +331,22 @@ export default async (opts: OptionValues) => { forbiddenDuplicatesFilter(name), ); if (forbiddenNewRanges.length > 0) { - throw new Error( - `Version bump failed for ${forbiddenNewRanges - .map(i => i.name) - .join(', ')}`, - ); - } - - const allowedDuplicates = result.newRanges.filter( - ({ name }) => !forbiddenDuplicatesFilter(name), - ); - - if (allowedDuplicates.length > 0) { + console.log(chalk.yellow(' ⚠️ Warning! ⚠️')); + console.log(); console.log( chalk.yellow( - 'The following packages have duplicates but have been allowed:', + ' The below package(s) have incompatible duplicate installations, likely due to a bad dependency in a plugin.', ), ); - console.log(chalk.yellow(allowedDuplicates.map(i => i.name).join(', '))); + console.log( + chalk.yellow( + ' You can investigate this by running `yarn why <package-name>`, and report the issue to the plugin maintainers.', + ), + ); + console.log(); + for (const { name } of forbiddenNewRanges) { + console.log(chalk.yellow(` ${name}`)); + } } }; diff --git a/packages/cli/src/commands/versions/lint.ts b/packages/cli/src/commands/versions/lint.ts index 9466fd0ab7..bfb2a752a0 100644 --- a/packages/cli/src/commands/versions/lint.ts +++ b/packages/cli/src/commands/versions/lint.ts @@ -27,10 +27,7 @@ export const includedFilter = (name: string) => INCLUDED.some(pattern => pattern.test(name)); // Packages that are not allowed to have any duplicates -const FORBID_DUPLICATES = [ - /^@backstage\/core-app-api$/, - /^@backstage\/plugin-/, -]; +const FORBID_DUPLICATES = [/^@backstage\/\w+-app-api$/, /^@backstage\/plugin-/]; // There are some packages that ARE explicitly allowed to have duplicates since // they handle that appropriately. This takes precedence over FORBID_DUPLICATES From e538b100433070d3dd6f06c3a7a115047ac3c02b Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Tue, 30 Apr 2024 09:50:26 +0300 Subject: [PATCH 199/234] feat: support relative notification links sent via email processor Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .changeset/giant-donkeys-talk.md | 5 + .../NotificationsEmailProcessor.test.ts | 225 ++++++++++++------ .../processor/NotificationsEmailProcessor.ts | 38 ++- 3 files changed, 188 insertions(+), 80 deletions(-) create mode 100644 .changeset/giant-donkeys-talk.md diff --git a/.changeset/giant-donkeys-talk.md b/.changeset/giant-donkeys-talk.md new file mode 100644 index 0000000000..62af2eb7e8 --- /dev/null +++ b/.changeset/giant-donkeys-talk.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-email': patch +--- + +Support relative links in notifications sent via email diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index 11eef9b999..cdc7b9059a 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -30,6 +30,36 @@ jest.mock('nodemailer', () => ({ createTransport: jest.fn(), })); +const DEFAULT_ENTITIES_RESPONSE = { + items: [ + { + kind: 'User', + spec: { + profile: { + email: 'mock@backstage.io', + }, + }, + }, + ], +}; + +const DEFAULT_SENDMAIL_CONFIG = { + app: { + baseUrl: 'https://example.org', + }, + notifications: { + processors: { + email: { + transportConfig: { + transport: 'sendmail', + path: '/usr/local/bin/sendmail', + }, + sender: 'backstage@backstage.io', + }, + }, + }, +}; + describe('NotificationsEmailProcessor', () => { const logger = mockServices.logger.mock(); const auth = mockServices.auth(); @@ -49,6 +79,10 @@ describe('NotificationsEmailProcessor', () => { const processor = new NotificationsEmailProcessor( logger, new ConfigReader({ + app: { + baseUrl: 'http://localhost:3000', + externalBaseUrl: 'https://example.org', + }, notifications: { processors: { email: { @@ -95,6 +129,10 @@ describe('NotificationsEmailProcessor', () => { const processor = new NotificationsEmailProcessor( logger, new ConfigReader({ + app: { + baseUrl: 'http://localhost:3000', + externalBaseUrl: 'https://example.org', + }, notifications: { processors: { email: { @@ -135,6 +173,10 @@ describe('NotificationsEmailProcessor', () => { const processor = new NotificationsEmailProcessor( logger, new ConfigReader({ + app: { + baseUrl: 'http://localhost:3000', + externalBaseUrl: 'https://example.org', + }, notifications: { processors: { email: { @@ -175,29 +217,10 @@ describe('NotificationsEmailProcessor', () => { it('should send user email', async () => { (createTransport as jest.Mock).mockReturnValue(mockTransport); - getEntityRefMock.mockResolvedValue({ - kind: 'User', - spec: { - profile: { - email: 'mock@backstage.io', - }, - }, - }); + getEntityRefMock.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE.items[0]); const processor = new NotificationsEmailProcessor( logger, - new ConfigReader({ - notifications: { - processors: { - email: { - transportConfig: { - transport: 'sendmail', - path: '/usr/local/bin/sendmail', - }, - sender: 'backstage@backstage.io', - }, - }, - }, - }), + mockServices.rootConfig({ data: DEFAULT_SENDMAIL_CONFIG }), mockCatalogClient as unknown as CatalogClient, auth, ); @@ -218,41 +241,29 @@ describe('NotificationsEmailProcessor', () => { expect(sendmailMock).toHaveBeenCalledWith({ from: 'backstage@backstage.io', - html: '<p></p>', + html: '<p><a href="https://example.org/notifications">https://example.org/notifications</a></p>', replyTo: undefined, subject: 'notification', - text: '', + text: 'https://example.org/notifications', to: 'mock@backstage.io', }); }); it('should send email to all', async () => { (createTransport as jest.Mock).mockReturnValue(mockTransport); - getEntitiesMock.mockResolvedValue({ - items: [ - { - kind: 'User', - spec: { - profile: { - email: 'mock@backstage.io', - }, - }, - }, - ], - }); + getEntitiesMock.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE); const processor = new NotificationsEmailProcessor( logger, - new ConfigReader({ - notifications: { - processors: { - email: { - transportConfig: { - transport: 'sendmail', - path: '/usr/local/bin/sendmail', - }, - sender: 'backstage@backstage.io', - broadcastConfig: { - receiver: 'users', + mockServices.rootConfig({ + data: { + ...DEFAULT_SENDMAIL_CONFIG, + notifications: { + processors: { + email: { + ...DEFAULT_SENDMAIL_CONFIG.notifications.processors.email, + broadcastConfig: { + receiver: 'users', + }, }, }, }, @@ -278,42 +289,30 @@ describe('NotificationsEmailProcessor', () => { expect(sendmailMock).toHaveBeenCalledWith({ from: 'backstage@backstage.io', - html: '<p></p>', + html: '<p><a href="https://example.org/notifications">https://example.org/notifications</a></p>', replyTo: undefined, subject: 'notification', - text: '', + text: 'https://example.org/notifications', to: 'mock@backstage.io', }); }); it('should send email to configured addresses', async () => { (createTransport as jest.Mock).mockReturnValue(mockTransport); - getEntitiesMock.mockResolvedValue({ - items: [ - { - kind: 'User', - spec: { - profile: { - email: 'mock@backstage.io', - }, - }, - }, - ], - }); + getEntitiesMock.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE); const processor = new NotificationsEmailProcessor( logger, - new ConfigReader({ - notifications: { - processors: { - email: { - transportConfig: { - transport: 'sendmail', - path: '/usr/local/bin/sendmail', - }, - sender: 'backstage@backstage.io', - broadcastConfig: { - receiver: 'config', - receiverEmails: ['broadcast@backstage.io'] as JsonArray, + mockServices.rootConfig({ + data: { + ...DEFAULT_SENDMAIL_CONFIG, + notifications: { + processors: { + email: { + ...DEFAULT_SENDMAIL_CONFIG.notifications.processors.email, + broadcastConfig: { + receiver: 'config', + receiverEmails: ['broadcast@backstage.io'] as JsonArray, + }, }, }, }, @@ -339,11 +338,89 @@ describe('NotificationsEmailProcessor', () => { expect(sendmailMock).toHaveBeenCalledWith({ from: 'backstage@backstage.io', - html: '<p></p>', + html: '<p><a href="https://example.org/notifications">https://example.org/notifications</a></p>', replyTo: undefined, subject: 'notification', - text: '', + text: 'https://example.org/notifications', to: 'broadcast@backstage.io', }); }); + + it('should send email with relative link to given address', async () => { + (createTransport as jest.Mock).mockReturnValue(mockTransport); + getEntityRefMock.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE.items[0]); + const processor = new NotificationsEmailProcessor( + logger, + mockServices.rootConfig({ + data: DEFAULT_SENDMAIL_CONFIG, + }), + mockCatalogClient as unknown as CatalogClient, + auth, + ); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + link: 'catalog/user/default/john.doe', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + + expect(sendmailMock).toHaveBeenCalledWith({ + from: 'backstage@backstage.io', + html: '<p><a href="https://example.org/catalog/user/default/john.doe">https://example.org/catalog/user/default/john.doe</a></p>', + replyTo: undefined, + subject: 'notification', + text: 'https://example.org/catalog/user/default/john.doe', + to: 'mock@backstage.io', + }); + }); + + it('should send email with absolute link to given address', async () => { + (createTransport as jest.Mock).mockReturnValue(mockTransport); + getEntityRefMock.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE.items[0]); + const processor = new NotificationsEmailProcessor( + logger, + mockServices.rootConfig({ + data: DEFAULT_SENDMAIL_CONFIG, + }), + mockCatalogClient as unknown as CatalogClient, + auth, + ); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + link: 'https://backstage.io', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + + expect(sendmailMock).toHaveBeenCalledWith({ + from: 'backstage@backstage.io', + html: '<p><a href="https://backstage.io/">https://backstage.io/</a></p>', + replyTo: undefined, + subject: 'notification', + text: 'https://backstage.io/', + to: 'mock@backstage.io', + }); + }); }); diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index ce944d9592..ecf4eb7773 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -50,6 +50,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private readonly cacheTtl: number; private readonly concurrencyLimit: number; private readonly throttleInterval: number; + private readonly frontendBaseUrl: string; constructor( private readonly logger: LoggerService, @@ -78,6 +79,7 @@ export class NotificationsEmailProcessor implements NotificationProcessor { this.cacheTtl = cacheConfig ? durationToMilliseconds(readDurationFromConfig(cacheConfig)) : 3_600_000; + this.frontendBaseUrl = config.getString('app.baseUrl'); } private async getTransporter() { @@ -215,20 +217,44 @@ export class NotificationsEmailProcessor implements NotificationProcessor { ); } - private async sendPlainEmail(notification: Notification, emails: string[]) { + private getNotificationLink(notification: Notification) { + if (notification.payload.link) { + try { + const url = new URL(notification.payload.link, this.frontendBaseUrl); + return url.toString(); + } catch (_e) { + // noop: fallback to relative URL + } + return notification.payload.link; + } + return `${this.frontendBaseUrl}/notifications`; + } + + private getHtmlContent(notification: Notification) { const contentParts: string[] = []; if (notification.payload.description) { contentParts.push(`${notification.payload.description}`); } - if (notification.payload.link) { - contentParts.push(`${notification.payload.link}`); - } + const link = this.getNotificationLink(notification); + contentParts.push(`<a href="${link}">${link}</a>`); + return `<p>${contentParts.join('<br/>')}</p>`; + } + private getTextContent(notification: Notification) { + const contentParts: string[] = []; + if (notification.payload.description) { + contentParts.push(notification.payload.description); + } + contentParts.push(this.getNotificationLink(notification)); + return contentParts.join('\n\n'); + } + + private async sendPlainEmail(notification: Notification, emails: string[]) { const mailOptions = { from: this.sender, subject: notification.payload.title, - html: `<p>${contentParts.join('<br/>')}</p>`, - text: contentParts.join('\n\n'), + html: this.getHtmlContent(notification), + text: this.getTextContent(notification), replyTo: this.replyTo, }; From 79bb100f0526888566b93b270ffb1b0ac0c9cced Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Tue, 30 Apr 2024 13:24:20 +0300 Subject: [PATCH 200/234] fix: ensure proper slashes in the email url Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .../NotificationsEmailProcessor.test.ts | 26 +++++++++++++++++++ .../processor/NotificationsEmailProcessor.ts | 8 +++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts index cdc7b9059a..79d1110968 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.test.ts @@ -383,6 +383,32 @@ describe('NotificationsEmailProcessor', () => { text: 'https://example.org/catalog/user/default/john.doe', to: 'mock@backstage.io', }); + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + link: '/catalog/user/default/jane.doe', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { title: 'notification' }, + }, + ); + + expect(sendmailMock).toHaveBeenCalledWith({ + from: 'backstage@backstage.io', + html: '<p><a href="https://example.org/catalog/user/default/jane.doe">https://example.org/catalog/user/default/jane.doe</a></p>', + replyTo: undefined, + subject: 'notification', + text: 'https://example.org/catalog/user/default/jane.doe', + to: 'mock@backstage.io', + }); }); it('should send email with absolute link to given address', async () => { diff --git a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts index ecf4eb7773..c06d28f903 100644 --- a/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts +++ b/plugins/notifications-backend-module-email/src/processor/NotificationsEmailProcessor.ts @@ -219,8 +219,14 @@ export class NotificationsEmailProcessor implements NotificationProcessor { private getNotificationLink(notification: Notification) { if (notification.payload.link) { + const stripLeadingSlash = (s: string) => s.replace(/^\//, ''); + const ensureTrailingSlash = (s: string) => s.replace(/\/?$/, '/'); + try { - const url = new URL(notification.payload.link, this.frontendBaseUrl); + const url = new URL( + stripLeadingSlash(notification.payload.link), + ensureTrailingSlash(this.frontendBaseUrl), + ); return url.toString(); } catch (_e) { // noop: fallback to relative URL From 8e9727b825d401bbfbafd22785335af20a41bad7 Mon Sep 17 00:00:00 2001 From: Chap Ambrose <cambrose@salesforce.com> Date: Thu, 2 May 2024 08:22:18 -0500 Subject: [PATCH 201/234] add ensureSchemaExists to config schema Signed-off-by: Chap Ambrose <cambrose@salesforce.com> --- packages/backend-common/config.d.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 700d9a6c6c..bdbec3a352 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -111,6 +111,13 @@ export interface Config { * Defaults to true if unspecified. */ ensureExists?: boolean; + /** + * Whether to ensure the given database schema exists by creating it if it does not. + * Defaults to false if unspecified. + * + * * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema + */ + ensureSchemaExists?: boolean; /** * How plugins databases are managed/divided in the provided database instance. * @@ -147,6 +154,13 @@ export interface Config { * Defaults to base config if unspecified. */ ensureExists?: boolean; + /** + * Whether to ensure the given database schema exists by creating it if it does not. + * Defaults to false if unspecified. + * + * * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema + */ + ensureSchemaExists?: boolean; /** * Arbitrary config object to pass to knex when initializing * (https://knexjs.org/#Installation-client). Most notable is the From 99fe60b13e017bdb73a073b554e1891a3e76d12c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Thu, 2 May 2024 16:01:08 +0200 Subject: [PATCH 202/234] Apply suggestions from code review Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- packages/backend-common/config.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index bdbec3a352..c6263cde97 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -115,7 +115,7 @@ export interface Config { * Whether to ensure the given database schema exists by creating it if it does not. * Defaults to false if unspecified. * - * * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema + * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema */ ensureSchemaExists?: boolean; /** @@ -158,7 +158,7 @@ export interface Config { * Whether to ensure the given database schema exists by creating it if it does not. * Defaults to false if unspecified. * - * * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema + * NOTE: Currently only supported by the `pg` client when pluginDivisionMode: schema */ ensureSchemaExists?: boolean; /** From f2a2a83b8df7378752c0ae09bc4c23d5f4578075 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Thu, 2 May 2024 17:55:54 +0200 Subject: [PATCH 203/234] catalog-node: update catalogAnalysisExtensionPoint Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- .changeset/brave-carrots-glow.md | 5 +++ .changeset/early-starfishes-hammer.md | 5 +++ .changeset/happy-radios-kiss.md | 5 +++ .changeset/rich-adults-float.md | 5 +++ .changeset/tough-eggs-wink.md | 5 +++ .../src/module/githubCatalogModule.test.ts | 5 ++- .../src/module/githubCatalogModule.ts | 2 +- plugins/catalog-backend/api-report.md | 11 ++--- plugins/catalog-backend/src/deprecated.ts | 6 +++ plugins/catalog-backend/src/index.ts | 1 - .../src/ingestion/LocationAnalyzer.ts | 6 ++- .../catalog-backend/src/ingestion/index.ts | 17 -------- .../catalog-backend/src/ingestion/types.ts | 33 --------------- .../src/service/CatalogBuilder.ts | 2 +- .../src/service/CatalogPlugin.ts | 40 +++++++++---------- .../src/service/createRouter.test.ts | 2 +- .../src/service/createRouter.ts | 2 +- plugins/catalog-node/api-report-alpha.md | 5 ++- plugins/catalog-node/api-report.md | 9 +++++ plugins/catalog-node/src/extensions.ts | 12 +++++- plugins/catalog-node/src/processing/index.ts | 1 + plugins/catalog-node/src/processing/types.ts | 19 ++++++++- 22 files changed, 107 insertions(+), 91 deletions(-) create mode 100644 .changeset/brave-carrots-glow.md create mode 100644 .changeset/early-starfishes-hammer.md create mode 100644 .changeset/happy-radios-kiss.md create mode 100644 .changeset/rich-adults-float.md create mode 100644 .changeset/tough-eggs-wink.md delete mode 100644 plugins/catalog-backend/src/ingestion/index.ts delete mode 100644 plugins/catalog-backend/src/ingestion/types.ts diff --git a/.changeset/brave-carrots-glow.md b/.changeset/brave-carrots-glow.md new file mode 100644 index 0000000000..b4493bf4f6 --- /dev/null +++ b/.changeset/brave-carrots-glow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': minor +--- + +Added `LocationAnalyzer` type, moved from `@backstage/plugin-catalog-backend`. diff --git a/.changeset/early-starfishes-hammer.md b/.changeset/early-starfishes-hammer.md new file mode 100644 index 0000000000..abe17e85a2 --- /dev/null +++ b/.changeset/early-starfishes-hammer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Deprecated the `LocationAnalyzer` type, which has been moved to `@backstage/plugin-catalog-node`. diff --git a/.changeset/happy-radios-kiss.md b/.changeset/happy-radios-kiss.md new file mode 100644 index 0000000000..1097c832d8 --- /dev/null +++ b/.changeset/happy-radios-kiss.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +The `/alpha` plugin export has had its implementation of the `catalogAnalysisExtensionPoint` updated to reflect the new API. diff --git a/.changeset/rich-adults-float.md b/.changeset/rich-adults-float.md new file mode 100644 index 0000000000..5a9746b8f5 --- /dev/null +++ b/.changeset/rich-adults-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Updated to use the new `catalogAnalysisExtensionPoint` API. diff --git a/.changeset/tough-eggs-wink.md b/.changeset/tough-eggs-wink.md new file mode 100644 index 0000000000..52644e3620 --- /dev/null +++ b/.changeset/tough-eggs-wink.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-node': minor +--- + +Breaking change to `/alpha` API where the `catalogAnalysisExtensionPoint` has been reworked. The `addLocationAnalyzer` method has been renamed to `addScmLocationAnalyzer`, and a new `setLocationAnalyzer` method has been added which allows the full `LocationAnalyzer` implementation to be overridden. diff --git a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts index 0f466fefb7..24ee7f7108 100644 --- a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts +++ b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.test.ts @@ -37,7 +37,8 @@ describe('githubCatalogModule', () => { }; const analysisExtensionPoint = { - addLocationAnalyzer: jest.fn(), + setLocationAnalyzer: jest.fn(), + addScmLocationAnalyzer: jest.fn(), }; const runner = jest.fn(); @@ -81,7 +82,7 @@ describe('githubCatalogModule', () => { 'github-provider:default', ); expect(runner).not.toHaveBeenCalled(); - expect(analysisExtensionPoint.addLocationAnalyzer).toHaveBeenCalledWith( + expect(analysisExtensionPoint.addScmLocationAnalyzer).toHaveBeenCalledWith( expect.any(GithubLocationAnalyzer), ); }); diff --git a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts index 0b46e1a023..809a1a5094 100644 --- a/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts +++ b/plugins/catalog-backend-module-github/src/module/githubCatalogModule.ts @@ -56,7 +56,7 @@ export const githubCatalogModule = createBackendModule({ discovery, auth, }) { - analyzers.addLocationAnalyzer( + analyzers.addScmLocationAnalyzer( new GithubLocationAnalyzer({ discovery, config, diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index 27c00257a5..1c65abf83e 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -41,6 +41,7 @@ import { EntityRelationSpec as EntityRelationSpec_2 } from '@backstage/plugin-ca import { EventBroker } 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'; @@ -167,7 +168,7 @@ export class CatalogBuilder { setEntityDataParser(parser: CatalogProcessorParser_2): CatalogBuilder; setEventBroker(broker: EventBroker): CatalogBuilder; setFieldFormatValidators(validators: Partial<Validators>): CatalogBuilder; - setLocationAnalyzer(locationAnalyzer: LocationAnalyzer): CatalogBuilder; + setLocationAnalyzer(locationAnalyzer: LocationAnalyzer_2): CatalogBuilder; setPlaceholderResolver( key: string, resolver: PlaceholderResolver_2, @@ -359,12 +360,8 @@ export class FileReaderProcessor implements CatalogProcessor_2 { ): Promise<boolean>; } -// @public (undocumented) -export type LocationAnalyzer = { - analyzeLocation( - location: AnalyzeLocationRequest_2, - ): Promise<AnalyzeLocationResponse_2>; -}; +// @public @deprecated (undocumented) +export type LocationAnalyzer = LocationAnalyzer_2; // @public @deprecated export class LocationEntityProcessor implements CatalogProcessor_2 { diff --git a/plugins/catalog-backend/src/deprecated.ts b/plugins/catalog-backend/src/deprecated.ts index 72c3cad225..2d08d2ce0c 100644 --- a/plugins/catalog-backend/src/deprecated.ts +++ b/plugins/catalog-backend/src/deprecated.ts @@ -48,6 +48,7 @@ import { 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 { @@ -177,6 +178,11 @@ export type LocationSpec = _LocationSpec; * @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 diff --git a/plugins/catalog-backend/src/index.ts b/plugins/catalog-backend/src/index.ts index 6a479babdc..d3ffa9a974 100644 --- a/plugins/catalog-backend/src/index.ts +++ b/plugins/catalog-backend/src/index.ts @@ -20,7 +20,6 @@ * @packageDocumentation */ -export * from './ingestion'; export * from './modules'; export * from './processing'; export * from './search'; diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index 61d319bf37..d574f3b299 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -17,12 +17,14 @@ import parseGitUrl from 'git-url-parse'; import { Entity } from '@backstage/catalog-model'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { LocationAnalyzer } from './types'; import { AnalyzeLocationRequest, AnalyzeLocationResponse, } from '@backstage/plugin-catalog-common'; -import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-node'; +import { + LocationAnalyzer, + ScmLocationAnalyzer, +} from '@backstage/plugin-catalog-node'; import { LoggerService } from '@backstage/backend-plugin-api'; export class RepoLocationAnalyzer implements LocationAnalyzer { diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts deleted file mode 100644 index a6555dd98d..0000000000 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export type { LocationAnalyzer } from './types'; diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts deleted file mode 100644 index 6ca0ab259b..0000000000 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ /dev/null @@ -1,33 +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 { - AnalyzeLocationRequest, - AnalyzeLocationResponse, -} from '@backstage/plugin-catalog-common'; - -/** @public */ -export type LocationAnalyzer = { - /** - * Generates an entity configuration for given git repository. It's used for - * importing new component to the backstage app. - * - * @param location - Git repository to analyze and generate config for. - */ - analyzeLocation( - location: AnalyzeLocationRequest, - ): Promise<AnalyzeLocationResponse>; -}; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 2438dc9009..f305bf2d8b 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -45,6 +45,7 @@ import { EntitiesSearchFilter, EntityProvider, PlaceholderResolver, + LocationAnalyzer, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-node'; import { @@ -64,7 +65,6 @@ import { yamlPlaceholderResolver, } from '../modules/core/PlaceholderProcessor'; import { defaultEntityDataParser } from '../modules/util/parse'; -import { LocationAnalyzer } from '../ingestion'; import { CatalogProcessingEngine, createRandomProcessingInterval, diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index dd44e20add..6171ebbc1c 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -20,7 +20,6 @@ import { import { Entity, Validators } from '@backstage/catalog-model'; import { CatalogBuilder, CatalogPermissionRuleInput } from './CatalogBuilder'; import { - CatalogAnalysisExtensionPoint, catalogAnalysisExtensionPoint, CatalogModelExtensionPoint, catalogModelExtensionPoint, @@ -33,6 +32,7 @@ import { CatalogProcessor, CatalogProcessorParser, EntityProvider, + LocationAnalyzer, PlaceholderResolver, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-node'; @@ -96,20 +96,6 @@ class CatalogProcessingExtensionPointImpl } } -class CatalogAnalysisExtensionPointImpl - implements CatalogAnalysisExtensionPoint -{ - #locationAnalyzers = new Array<ScmLocationAnalyzer>(); - - addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void { - this.#locationAnalyzers.push(analyzer); - } - - get locationAnalyzers() { - return this.#locationAnalyzers; - } -} - class CatalogPermissionExtensionPointImpl implements CatalogPermissionExtensionPoint { @@ -178,11 +164,19 @@ export const catalogPlugin = createBackendPlugin({ processingExtensions, ); - const analysisExtensions = new CatalogAnalysisExtensionPointImpl(); - env.registerExtensionPoint( - catalogAnalysisExtensionPoint, - analysisExtensions, - ); + let locationAnalyzer: LocationAnalyzer | undefined = undefined; + const scmLocationAnalyzers = new Array<ScmLocationAnalyzer>(); + env.registerExtensionPoint(catalogAnalysisExtensionPoint, { + setLocationAnalyzer(analyzer: LocationAnalyzer) { + if (locationAnalyzer) { + throw new Error('LocationAnalyzer has already been set'); + } + locationAnalyzer = analyzer; + }, + addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer) { + scmLocationAnalyzers.push(analyzer); + }, + }); const permissionExtensions = new CatalogPermissionExtensionPointImpl(); env.registerExtensionPoint( @@ -246,7 +240,11 @@ export const catalogPlugin = createBackendPlugin({ Object.entries(processingExtensions.placeholderResolvers).forEach( ([key, resolver]) => builder.setPlaceholderResolver(key, resolver), ); - builder.addLocationAnalyzers(...analysisExtensions.locationAnalyzers); + if (locationAnalyzer) { + builder.setLocationAnalyzer(locationAnalyzer); + } else { + builder.addLocationAnalyzers(...scmLocationAnalyzers); + } builder.addPermissions(...permissionExtensions.permissions); builder.addPermissionRules(...permissionExtensions.permissionRules); builder.setFieldFormatValidators(modelExtensions.fieldValidators); diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 292c4c1b7a..a2fae0e8bd 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -42,7 +42,7 @@ import { decodeCursor, encodeCursor } from './util'; import { wrapInOpenApiTestServer } from '@backstage/backend-openapi-utils'; import { Server } from 'http'; import { mockCredentials, mockServices } from '@backstage/backend-test-utils'; -import { LocationAnalyzer } from '../ingestion'; +import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; describe('createRouter readonly disabled', () => { let entitiesCatalog: jest.Mocked<EntitiesCatalog>; diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 508f0bb3d6..774ee61a6d 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -28,7 +28,6 @@ import express from 'express'; import yn from 'yn'; import { z } from 'zod'; import { EntitiesCatalog } from '../catalog/types'; -import { LocationAnalyzer } from '../ingestion'; import { CatalogProcessingOrchestrator } from '../processing/types'; import { validateEntityEnvelope } from '../processing/util'; import { @@ -55,6 +54,7 @@ import { HttpAuthService, LoggerService, } from '@backstage/backend-plugin-api'; +import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; /** * Options used by {@link createRouter}. diff --git a/plugins/catalog-node/api-report-alpha.md b/plugins/catalog-node/api-report-alpha.md index fb41d6f03e..cc3f7e5fb5 100644 --- a/plugins/catalog-node/api-report-alpha.md +++ b/plugins/catalog-node/api-report-alpha.md @@ -10,6 +10,7 @@ import { EntitiesSearchFilter } from '@backstage/plugin-catalog-node'; import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-node'; import { ExtensionPoint } from '@backstage/backend-plugin-api'; +import { LocationAnalyzer } from '@backstage/plugin-catalog-node'; import { Permission } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { PermissionRuleParams } from '@backstage/plugin-permission-common'; @@ -20,8 +21,8 @@ import { Validators } from '@backstage/catalog-model'; // @alpha (undocumented) export interface CatalogAnalysisExtensionPoint { - // (undocumented) - addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void; + addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer): void; + setLocationAnalyzer(analyzer: LocationAnalyzer): void; } // @alpha (undocumented) diff --git a/plugins/catalog-node/api-report.md b/plugins/catalog-node/api-report.md index a4ba95073f..7c1c8b5911 100644 --- a/plugins/catalog-node/api-report.md +++ b/plugins/catalog-node/api-report.md @@ -6,6 +6,8 @@ /// <reference types="node" /> import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationRequest } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationResponse } from '@backstage/plugin-catalog-common'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; @@ -166,6 +168,13 @@ export type EntityRelationSpec = { target: CompoundEntityRef; }; +// @public (undocumented) +export type LocationAnalyzer = { + analyzeLocation( + location: AnalyzeLocationRequest, + ): Promise<AnalyzeLocationResponse>; +}; + // @public @deprecated export type LocationSpec = LocationSpec_2; diff --git a/plugins/catalog-node/src/extensions.ts b/plugins/catalog-node/src/extensions.ts index 648b8c5974..56478ec1db 100644 --- a/plugins/catalog-node/src/extensions.ts +++ b/plugins/catalog-node/src/extensions.ts @@ -22,6 +22,7 @@ import { EntitiesSearchFilter, EntityProvider, PlaceholderResolver, + LocationAnalyzer, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-node'; import { @@ -79,7 +80,16 @@ export const catalogProcessingExtensionPoint = * @alpha */ export interface CatalogAnalysisExtensionPoint { - addLocationAnalyzer(analyzer: ScmLocationAnalyzer): void; + /** + * Replaces the entire location analyzer with a new one. This will cause any + * SCM analyzers added through `addScmLocationAnalyzer` to be ignored. + */ + setLocationAnalyzer(analyzer: LocationAnalyzer): void; + + /** + * Adds an analyzer for a specific SCM type to the default location analyzer. + */ + addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer): void; } /** diff --git a/plugins/catalog-node/src/processing/index.ts b/plugins/catalog-node/src/processing/index.ts index 8cffac6961..4e83e6077d 100644 --- a/plugins/catalog-node/src/processing/index.ts +++ b/plugins/catalog-node/src/processing/index.ts @@ -21,5 +21,6 @@ export type { PlaceholderResolverParams, PlaceholderResolverRead, PlaceholderResolverResolveUrl, + LocationAnalyzer, ScmLocationAnalyzer, } from './types'; diff --git a/plugins/catalog-node/src/processing/types.ts b/plugins/catalog-node/src/processing/types.ts index b6785c1b91..e3d3c305fc 100644 --- a/plugins/catalog-node/src/processing/types.ts +++ b/plugins/catalog-node/src/processing/types.ts @@ -15,7 +15,11 @@ */ import { Entity } from '@backstage/catalog-model'; -import { AnalyzeLocationExistingEntity } from '@backstage/plugin-catalog-common'; +import { + AnalyzeLocationExistingEntity, + AnalyzeLocationRequest, + AnalyzeLocationResponse, +} from '@backstage/plugin-catalog-common'; import { JsonValue } from '@backstage/types'; import { CatalogProcessorEmit } from '../api'; @@ -52,6 +56,19 @@ export type PlaceholderResolver = ( params: PlaceholderResolverParams, ) => Promise<JsonValue>; +/** @public */ +export type LocationAnalyzer = { + /** + * Generates an entity configuration for given git repository. It's used for + * importing new component to the backstage app. + * + * @param location - Git repository to analyze and generate config for. + */ + analyzeLocation( + location: AnalyzeLocationRequest, + ): Promise<AnalyzeLocationResponse>; +}; + /** @public */ export type AnalyzeOptions = { url: string; From 6cc3a9f66f237dd61a1dc85760fe57340c16fe75 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 19:48:42 +0000 Subject: [PATCH 204/234] chore(deps): update peter-evans/find-comment digest to 3eae4d3 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/api-breaking-changes-comment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/api-breaking-changes-comment.yml b/.github/workflows/api-breaking-changes-comment.yml index 4c7fa452e7..bc90a276f0 100644 --- a/.github/workflows/api-breaking-changes-comment.yml +++ b/.github/workflows/api-breaking-changes-comment.yml @@ -94,7 +94,7 @@ jobs: # Identify comment to be updated - name: Find comment for API Changes - uses: peter-evans/find-comment@d5fe37641ad8451bdd80312415672ba26c86575e # v3 + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3 id: find-comment with: issue-number: ${{ needs.setup.outputs.pr-number }} From 8ade1283af574c503d1bf98db642eaa39f6142bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 19:48:57 +0000 Subject: [PATCH 205/234] chore(deps): update actions/checkout action to v4.1.4 Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/api-breaking-changes.yml | 2 +- .github/workflows/automate_changeset_feedback.yml | 2 +- .github/workflows/automate_merge_message.yml | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/deploy_docker-image.yml | 2 +- .github/workflows/deploy_microsite.yml | 2 +- .github/workflows/deploy_nightly.yml | 2 +- .github/workflows/deploy_packages.yml | 4 ++-- .github/workflows/scorecard.yml | 2 +- .github/workflows/sync_code-formatting.yml | 2 +- .github/workflows/sync_dependabot-changesets.yml | 2 +- .github/workflows/sync_release-manifest.yml | 4 ++-- .github/workflows/sync_renovate-changesets.yml | 2 +- .github/workflows/sync_snyk-github-issues.yml | 2 +- .github/workflows/sync_snyk-monitor.yml | 2 +- .github/workflows/sync_version-packages.yml | 2 +- .github/workflows/uffizzi-build.yml | 4 ++-- .github/workflows/verify_accessibility.yml | 2 +- .github/workflows/verify_codeql.yml | 2 +- .github/workflows/verify_docs-quality.yml | 2 +- .github/workflows/verify_e2e-kubernetes.yml | 2 +- .github/workflows/verify_e2e-linux.yml | 2 +- .github/workflows/verify_e2e-techdocs.yml | 2 +- .github/workflows/verify_e2e-windows.yml | 2 +- .github/workflows/verify_fossa.yml | 2 +- .github/workflows/verify_microsite.yml | 2 +- .github/workflows/verify_microsite_accessibility.yml | 2 +- .github/workflows/verify_storybook.yml | 2 +- .github/workflows/verify_windows.yml | 2 +- 29 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/api-breaking-changes.yml b/.github/workflows/api-breaking-changes.yml index 07206718bb..901516305d 100644 --- a/.github/workflows/api-breaking-changes.yml +++ b/.github/workflows/api-breaking-changes.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_changeset_feedback.yml b/.github/workflows/automate_changeset_feedback.yml index 4d965499d2..fefc73bd9f 100644 --- a/.github/workflows/automate_changeset_feedback.yml +++ b/.github/workflows/automate_changeset_feedback.yml @@ -27,7 +27,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: # Fetch the commit that's merged into the base rather than the target ref # This will let us diff only the contents of the PR, without fetching more history diff --git a/.github/workflows/automate_merge_message.yml b/.github/workflows/automate_merge_message.yml index 38243ee0ac..d6520d16a6 100644 --- a/.github/workflows/automate_merge_message.yml +++ b/.github/workflows/automate_merge_message.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: '${{ github.event.pull_request.merge_commit_sha }}' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fdd31b2d57..c819ae8130 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -68,7 +68,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -197,7 +197,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: fetch master branch run: git fetch origin master diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index 020254c872..8c263fdd4f 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -25,7 +25,7 @@ jobs: egress-policy: audit - name: checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: path: backstage ref: ${{ github.event.client_payload.version && env.RELEASE_VERSION || github.ref }} diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index 6864ec67dc..b955b39b59 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml index 07c3db0f11..a3a1b5e283 100644 --- a/.github/workflows/deploy_nightly.yml +++ b/.github/workflows/deploy_nightly.yml @@ -19,7 +19,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index f9ef4c67eb..0c44d32384 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -65,7 +65,7 @@ jobs: INTEGRATION_TEST_AZURE_TOKEN: ${{ secrets.INTEGRATION_TEST_AZURE_TOKEN }} steps: - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -148,7 +148,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 67fdff4020..5acd11c9b0 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -34,7 +34,7 @@ jobs: egress-policy: audit - name: 'Checkout code' - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: persist-credentials: false diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index 5dbd0361c1..a59eea9c9b 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -14,7 +14,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: # Fetch changes to previous commit - required for 'only_changed' in Prettier action fetch-depth: 0 diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index 8b3be335ed..88997ea5dd 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -16,7 +16,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index b0a2499d43..1549ec0e90 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -21,7 +21,7 @@ jobs: run: npm install semver@7.3.5 fs-extra@10.0.0 @manypkg/get-packages@1.1.1 - name: Checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: path: backstage # 'v' prefix is added here for the tag, we keep it out of the manifest logic @@ -29,7 +29,7 @@ jobs: # Checkout backstage/versions into /backstage/versions, which is where store the output - name: Checkout versions - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: repository: backstage/versions path: backstage/versions diff --git a/.github/workflows/sync_renovate-changesets.yml b/.github/workflows/sync_renovate-changesets.yml index d02e35483c..e95a68ac2f 100644 --- a/.github/workflows/sync_renovate-changesets.yml +++ b/.github/workflows/sync_renovate-changesets.yml @@ -16,7 +16,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 2 ref: ${{ github.head_ref }} diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index d5a4048044..bebbbc4fbe 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -16,7 +16,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index e2507996b1..cb3efcfac3 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -29,7 +29,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Monitor and Synchronize Snyk Policies uses: snyk/actions/node@8349f9043a8b7f0f3ee8885bf28f0b388d2446e8 # master with: diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index b2ad80b6da..82a0eae7be 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -18,7 +18,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 20000 fetch-tags: true diff --git a/.github/workflows/uffizzi-build.yml b/.github/workflows/uffizzi-build.yml index 4a52af21bc..11a0054703 100644 --- a/.github/workflows/uffizzi-build.yml +++ b/.github/workflows/uffizzi-build.yml @@ -31,7 +31,7 @@ jobs: egress-policy: audit - name: checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: setup-node uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 @@ -89,7 +89,7 @@ jobs: egress-policy: audit - name: Checkout git repo - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Render Compose File run: | # update image after the build above diff --git a/.github/workflows/verify_accessibility.yml b/.github/workflows/verify_accessibility.yml index 1c25d709cb..b4615fee0a 100644 --- a/.github/workflows/verify_accessibility.yml +++ b/.github/workflows/verify_accessibility.yml @@ -24,7 +24,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Use Node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 920c9bab17..60cb89a045 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -47,7 +47,7 @@ jobs: egress-policy: audit - name: Checkout repository - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: # We must fetch at least the immediate parents so that if this is # a pull request then we can checkout the head. diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 3550df6fc0..2eb7608ccd 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -16,7 +16,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # Vale does not support file excludes, so we use the script to generate a list of files instead # The action also does not allow args or a local config file to be passed in, so the files array diff --git a/.github/workflows/verify_e2e-kubernetes.yml b/.github/workflows/verify_e2e-kubernetes.yml index 6ff41c8b74..3c425c25e9 100644 --- a/.github/workflows/verify_e2e-kubernetes.yml +++ b/.github/workflows/verify_e2e-kubernetes.yml @@ -26,7 +26,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index a750dfea3b..75c7a800d8 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -45,7 +45,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Configure Git run: | diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 3bcda5f2b6..d5f2594285 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -34,7 +34,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0 with: python-version: '3.9' diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index 078ddd6855..a6fa469666 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -42,7 +42,7 @@ jobs: git config --global core.autocrlf false git config --global core.eol lf - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Configure Git run: | diff --git a/.github/workflows/verify_fossa.yml b/.github/workflows/verify_fossa.yml index 801c5fbd28..ccc9e4509a 100644 --- a/.github/workflows/verify_fossa.yml +++ b/.github/workflows/verify_fossa.yml @@ -19,7 +19,7 @@ jobs: egress-policy: audit - name: Checkout - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Install Fossa run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash" diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 68378aa086..db6a3e2d40 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -28,7 +28,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_microsite_accessibility.yml b/.github/workflows/verify_microsite_accessibility.yml index e0fea1a72c..19417c5cee 100644 --- a/.github/workflows/verify_microsite_accessibility.yml +++ b/.github/workflows/verify_microsite_accessibility.yml @@ -19,7 +19,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Use Node.js 18.x uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 940c136797..6a37f5abb6 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -32,7 +32,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: fetch-depth: 0 # Required to retrieve git history diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index 32b0452eec..c7ff8f5ca9 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -33,7 +33,7 @@ jobs: with: egress-policy: audit - - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # v4.1.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 From 79a6358f50854b626d06598cbae0384e44c0b68f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Fri, 3 May 2024 10:40:49 +0200 Subject: [PATCH 206/234] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw <freben@gmail.com> Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- .../building-apps/02-configuring-extensions.md | 2 +- docs/frontend-system/building-apps/08-migrating.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/frontend-system/building-apps/02-configuring-extensions.md b/docs/frontend-system/building-apps/02-configuring-extensions.md index 5f090e23cd..4052ef47f0 100644 --- a/docs/frontend-system/building-apps/02-configuring-extensions.md +++ b/docs/frontend-system/building-apps/02-configuring-extensions.md @@ -6,7 +6,7 @@ sidebar_label: Configuring Extensions description: Documentation for how to configure extensions in a Backstage app --- -All extensions in a Backstage app can be configured through static configuration. This configuration is all done under a the `app.extensions` configuration key. For more general information on how to write configuration for Backstage, see the section on [writing configuration](../../conf/writing.md). +All extensions in a Backstage app can be configured through static configuration. This configuration is all done under the `app.extensions` configuration key. For more general information on how to write configuration for Backstage, see the section on [writing configuration](../../conf/writing.md). ## Extension Configuration Schema diff --git a/docs/frontend-system/building-apps/08-migrating.md b/docs/frontend-system/building-apps/08-migrating.md index 8bcc1f973f..dcc58325cf 100644 --- a/docs/frontend-system/building-apps/08-migrating.md +++ b/docs/frontend-system/building-apps/08-migrating.md @@ -117,7 +117,7 @@ You can then also add any additional extensions that you may need to create as p [Utility API](../utility-apis/01-index.md) factories are now installed as extensions instead. Pass the existing factory to `createApiExtension` and install it in the app. For more information, see the section on [configuring Utility APIs](../utility-apis/04-configuring.md). -For example, the following apis configuration: +For example, the following `apis` configuration: ```ts const app = createApp({ @@ -151,7 +151,7 @@ Icons are currently installed through the usual options to `createApp`, but will Plugins are now passed through the `features` options instead. -For example, the following plugins configuration: +For example, the following `plugins` configuration: ```tsx import { homePlugin } from '@backstage/plugin-home'; @@ -163,7 +163,7 @@ createApp({ }); ``` -Can be converted to the following features configuration: +Can be converted to the following `features` configuration: ```tsx // plugins are now default exported via alpha subpath From ae500129a9146c600453de465103f917b5fbbcb4 Mon Sep 17 00:00:00 2001 From: Nitin Ramnani <nitin.ramnani@infosys.com> Date: Fri, 3 May 2024 14:49:01 +0530 Subject: [PATCH 207/234] Added stories for multiple components Signed-off-by: Nitin Ramnani <nitin.ramnani@infosys.com> --- .../HeaderIconLinkRow.stories.tsx | 51 ++++++++++++++++ .../ResponseErrorPanel.stories.tsx | 38 ++++++++++++ .../layout/BottomLink/BottomLink.stories.tsx | 33 +++++++++++ .../ContentHeader/ContentHeader.stories.tsx | 58 +++++++++++++++++++ 4 files changed, 180 insertions(+) create mode 100644 packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx create mode 100644 packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx create mode 100644 packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx create mode 100644 packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx diff --git a/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx new file mode 100644 index 0000000000..e6891081e3 --- /dev/null +++ b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx @@ -0,0 +1,51 @@ +/* + * 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 React from 'react'; +import { HeaderIconLinkRow } from '../HeaderIconLinkRow'; +import { IconLinkVerticalProps } from './IconLinkVertical'; + + +type Props = { + links: IconLinkVerticalProps[]; + }; + +export default { + title: 'Data Display/HeaderIconLinkRow', + component: HeaderIconLinkRow, +}; + + + +export const Default = (args:Props) => <HeaderIconLinkRow {...args} /> +Default.args = { + links: [ + { + color: 'primary', + disabled: false, + href: "https://google.com", + label: "primary", + title: "title" + }, + { + color: 'secondary', + disabled: false, + href: "https://google.com", + label: "secondary", + title: "title-2" + }, + ] +}; \ No newline at end of file diff --git a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx new file mode 100644 index 0000000000..62eb523a02 --- /dev/null +++ b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx @@ -0,0 +1,38 @@ +/* + * 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 React from 'react'; +import { ResponseErrorPanel } from '../ResponseErrorPanel'; +import { ErrorPanelProps } from '../ErrorPanel'; + +export default { + title: 'Data Display/ResponseErrorPanel', + component: ResponseErrorPanel, +}; + +export const Default = (args:ErrorPanelProps) => <ResponseErrorPanel {...args} /> +Default.args = { + error: new Error('Error message from error object'), + defaultExpanded: false +}; + + +export const WithTitle = (args:ErrorPanelProps) => <ResponseErrorPanel {...args} /> +WithTitle.args = { + error: new Error('test'), + defaultExpanded: false, + title:"Title prop is passed" +}; diff --git a/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx new file mode 100644 index 0000000000..84f268681d --- /dev/null +++ b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx @@ -0,0 +1,33 @@ +/* + * 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 React from 'react'; +import { BottomLink } from '../BottomLink'; + +export default { + title: 'Layout/BottomLink', + component: BottomLink, +}; + +export const Default = (args:{ + link:string + title:string +}) => <BottomLink {...args} /> +Default.args = { + link: 'https://google.com', + title: 'This is bottom link' +}; diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx new file mode 100644 index 0000000000..ce6365c28e --- /dev/null +++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx @@ -0,0 +1,58 @@ +/* + * 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 React , {ReactNode} from 'react'; +import { ContentHeader } from '../ContentHeader'; + +export default { + title: 'Layout/ContentHeader', + component: ContentHeader, +}; + +type ContentHeaderProps = { + title?: string + titleComponent?: ReactNode; + description?: string; + textAlign?: 'left' | 'right' | 'center'; + }; + +export const Default = (args:ContentHeaderProps) => <ContentHeader {...args} ><div>Child of Content Header</div></ContentHeader> +Default.args = { + title: 'This is Content Header default aligned', + description:'This is description' +}; + + +export const Left = (args:ContentHeaderProps) => <ContentHeader {...args} ><div>Child of Content Header</div></ContentHeader> +Left.args = { + title: 'This is Content Header left aligned', + description:'This is description', + textAlign: 'left' +}; + +export const Right = (args:ContentHeaderProps) => <ContentHeader {...args} ><div>Child of Content Header</div></ContentHeader> +Right.args = { + title: 'This is Content Header right aligned', + description:'This is description', + textAlign: 'right' +}; + +export const Center = (args:ContentHeaderProps) => <ContentHeader {...args} ><div>Child of Content Header</div></ContentHeader> +Center.args = { + title: 'This is Content Header center aligned', + description:'This is description', + textAlign: 'center' +}; From baf298f57ec2a6fe1b43dd584cf78f2e0c959467 Mon Sep 17 00:00:00 2001 From: Nitin Ramnani <nitin.ramnani@infosys.com> Date: Fri, 3 May 2024 14:52:20 +0530 Subject: [PATCH 208/234] Added stories for multiple components Signed-off-by: Nitin Ramnani <nitin.ramnani@infosys.com> --- .../HeaderIconLinkRow.stories.tsx | 49 +++++++-------- .../ResponseErrorPanel.stories.tsx | 23 ++++--- .../layout/BottomLink/BottomLink.stories.tsx | 16 +++-- .../ContentHeader/ContentHeader.stories.tsx | 63 ++++++++++++------- 4 files changed, 82 insertions(+), 69 deletions(-) diff --git a/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx index e6891081e3..e472ab5599 100644 --- a/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx +++ b/packages/core-components/src/components/HeaderIconLinkRow/HeaderIconLinkRow.stories.tsx @@ -18,34 +18,31 @@ import React from 'react'; import { HeaderIconLinkRow } from '../HeaderIconLinkRow'; import { IconLinkVerticalProps } from './IconLinkVertical'; - type Props = { - links: IconLinkVerticalProps[]; - }; - -export default { - title: 'Data Display/HeaderIconLinkRow', - component: HeaderIconLinkRow, + links: IconLinkVerticalProps[]; }; +export default { + title: 'Data Display/HeaderIconLinkRow', + component: HeaderIconLinkRow, +}; - -export const Default = (args:Props) => <HeaderIconLinkRow {...args} /> +export const Default = (args: Props) => <HeaderIconLinkRow {...args} />; Default.args = { - links: [ - { - color: 'primary', - disabled: false, - href: "https://google.com", - label: "primary", - title: "title" - }, - { - color: 'secondary', - disabled: false, - href: "https://google.com", - label: "secondary", - title: "title-2" - }, - ] -}; \ No newline at end of file + links: [ + { + color: 'primary', + disabled: false, + href: 'https://google.com', + label: 'primary', + title: 'title', + }, + { + color: 'secondary', + disabled: false, + href: 'https://google.com', + label: 'secondary', + title: 'title-2', + }, + ], +}; diff --git a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx index 62eb523a02..8d03e7359f 100644 --- a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx +++ b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.stories.tsx @@ -19,20 +19,23 @@ import { ResponseErrorPanel } from '../ResponseErrorPanel'; import { ErrorPanelProps } from '../ErrorPanel'; export default { - title: 'Data Display/ResponseErrorPanel', - component: ResponseErrorPanel, + title: 'Data Display/ResponseErrorPanel', + component: ResponseErrorPanel, }; -export const Default = (args:ErrorPanelProps) => <ResponseErrorPanel {...args} /> +export const Default = (args: ErrorPanelProps) => ( + <ResponseErrorPanel {...args} /> +); Default.args = { - error: new Error('Error message from error object'), - defaultExpanded: false + error: new Error('Error message from error object'), + defaultExpanded: false, }; - -export const WithTitle = (args:ErrorPanelProps) => <ResponseErrorPanel {...args} /> +export const WithTitle = (args: ErrorPanelProps) => ( + <ResponseErrorPanel {...args} /> +); WithTitle.args = { - error: new Error('test'), - defaultExpanded: false, - title:"Title prop is passed" + error: new Error('test'), + defaultExpanded: false, + title: 'Title prop is passed', }; diff --git a/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx index 84f268681d..e05af86993 100644 --- a/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx +++ b/packages/core-components/src/layout/BottomLink/BottomLink.stories.tsx @@ -14,20 +14,18 @@ * limitations under the License. */ - import React from 'react'; import { BottomLink } from '../BottomLink'; export default { - title: 'Layout/BottomLink', - component: BottomLink, + title: 'Layout/BottomLink', + component: BottomLink, }; -export const Default = (args:{ - link:string - title:string -}) => <BottomLink {...args} /> +export const Default = (args: { link: string; title: string }) => ( + <BottomLink {...args} /> +); Default.args = { - link: 'https://google.com', - title: 'This is bottom link' + link: 'https://google.com', + title: 'This is bottom link', }; diff --git a/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx index ce6365c28e..27df00e72c 100644 --- a/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx +++ b/packages/core-components/src/layout/ContentHeader/ContentHeader.stories.tsx @@ -14,45 +14,60 @@ * limitations under the License. */ -import React , {ReactNode} from 'react'; +import React, { ReactNode } from 'react'; import { ContentHeader } from '../ContentHeader'; export default { - title: 'Layout/ContentHeader', - component: ContentHeader, + title: 'Layout/ContentHeader', + component: ContentHeader, }; type ContentHeaderProps = { - title?: string - titleComponent?: ReactNode; - description?: string; - textAlign?: 'left' | 'right' | 'center'; - }; + title?: string; + titleComponent?: ReactNode; + description?: string; + textAlign?: 'left' | 'right' | 'center'; +}; -export const Default = (args:ContentHeaderProps) => <ContentHeader {...args} ><div>Child of Content Header</div></ContentHeader> +export const Default = (args: ContentHeaderProps) => ( + <ContentHeader {...args}> + <div>Child of Content Header</div> + </ContentHeader> +); Default.args = { - title: 'This is Content Header default aligned', - description:'This is description' + title: 'This is Content Header default aligned', + description: 'This is description', }; - -export const Left = (args:ContentHeaderProps) => <ContentHeader {...args} ><div>Child of Content Header</div></ContentHeader> +export const Left = (args: ContentHeaderProps) => ( + <ContentHeader {...args}> + <div>Child of Content Header</div> + </ContentHeader> +); Left.args = { - title: 'This is Content Header left aligned', - description:'This is description', - textAlign: 'left' + title: 'This is Content Header left aligned', + description: 'This is description', + textAlign: 'left', }; -export const Right = (args:ContentHeaderProps) => <ContentHeader {...args} ><div>Child of Content Header</div></ContentHeader> +export const Right = (args: ContentHeaderProps) => ( + <ContentHeader {...args}> + <div>Child of Content Header</div> + </ContentHeader> +); Right.args = { - title: 'This is Content Header right aligned', - description:'This is description', - textAlign: 'right' + title: 'This is Content Header right aligned', + description: 'This is description', + textAlign: 'right', }; -export const Center = (args:ContentHeaderProps) => <ContentHeader {...args} ><div>Child of Content Header</div></ContentHeader> +export const Center = (args: ContentHeaderProps) => ( + <ContentHeader {...args}> + <div>Child of Content Header</div> + </ContentHeader> +); Center.args = { - title: 'This is Content Header center aligned', - description:'This is description', - textAlign: 'center' + title: 'This is Content Header center aligned', + description: 'This is description', + textAlign: 'center', }; From 2e20518cd2d16940fff1f8069e43593e0702e86a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Fri, 3 May 2024 11:43:07 +0200 Subject: [PATCH 209/234] catalog-node: allow location analyzer to be configured as a factory as well Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- .../src/service/CatalogPlugin.ts | 26 +++++++++++++++---- plugins/catalog-node/api-report-alpha.md | 8 +++++- plugins/catalog-node/src/extensions.ts | 17 +++++++++--- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/plugins/catalog-backend/src/service/CatalogPlugin.ts b/plugins/catalog-backend/src/service/CatalogPlugin.ts index 6171ebbc1c..ac869cdc2b 100644 --- a/plugins/catalog-backend/src/service/CatalogPlugin.ts +++ b/plugins/catalog-backend/src/service/CatalogPlugin.ts @@ -38,6 +38,7 @@ import { } from '@backstage/plugin-catalog-node'; import { merge } from 'lodash'; import { Permission } from '@backstage/plugin-permission-common'; +import { ForwardedError } from '@backstage/errors'; class CatalogProcessingExtensionPointImpl implements CatalogProcessingExtensionPoint @@ -164,14 +165,24 @@ export const catalogPlugin = createBackendPlugin({ processingExtensions, ); - let locationAnalyzer: LocationAnalyzer | undefined = undefined; + let locationAnalyzerFactory: + | ((options: { + scmLocationAnalyzers: ScmLocationAnalyzer[]; + }) => Promise<{ locationAnalyzer: LocationAnalyzer }>) + | undefined = undefined; const scmLocationAnalyzers = new Array<ScmLocationAnalyzer>(); env.registerExtensionPoint(catalogAnalysisExtensionPoint, { - setLocationAnalyzer(analyzer: LocationAnalyzer) { - if (locationAnalyzer) { + setLocationAnalyzer(analyzerOrFactory) { + if (locationAnalyzerFactory) { throw new Error('LocationAnalyzer has already been set'); } - locationAnalyzer = analyzer; + if (typeof analyzerOrFactory === 'function') { + locationAnalyzerFactory = analyzerOrFactory; + } else { + locationAnalyzerFactory = async () => ({ + locationAnalyzer: analyzerOrFactory, + }); + } }, addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer) { scmLocationAnalyzers.push(analyzer); @@ -240,7 +251,12 @@ export const catalogPlugin = createBackendPlugin({ Object.entries(processingExtensions.placeholderResolvers).forEach( ([key, resolver]) => builder.setPlaceholderResolver(key, resolver), ); - if (locationAnalyzer) { + if (locationAnalyzerFactory) { + const { locationAnalyzer } = await locationAnalyzerFactory({ + scmLocationAnalyzers, + }).catch(e => { + throw new ForwardedError('Failed to create LocationAnalyzer', e); + }); builder.setLocationAnalyzer(locationAnalyzer); } else { builder.addLocationAnalyzers(...scmLocationAnalyzers); diff --git a/plugins/catalog-node/api-report-alpha.md b/plugins/catalog-node/api-report-alpha.md index cc3f7e5fb5..b61d1b1d27 100644 --- a/plugins/catalog-node/api-report-alpha.md +++ b/plugins/catalog-node/api-report-alpha.md @@ -22,7 +22,13 @@ import { Validators } from '@backstage/catalog-model'; // @alpha (undocumented) export interface CatalogAnalysisExtensionPoint { addScmLocationAnalyzer(analyzer: ScmLocationAnalyzer): void; - setLocationAnalyzer(analyzer: LocationAnalyzer): void; + setLocationAnalyzer( + analyzerOrFactory: + | LocationAnalyzer + | ((options: { scmLocationAnalyzers: ScmLocationAnalyzer[] }) => Promise<{ + locationAnalyzer: LocationAnalyzer; + }>), + ): void; } // @alpha (undocumented) diff --git a/plugins/catalog-node/src/extensions.ts b/plugins/catalog-node/src/extensions.ts index 56478ec1db..7c1fe6c30d 100644 --- a/plugins/catalog-node/src/extensions.ts +++ b/plugins/catalog-node/src/extensions.ts @@ -81,10 +81,21 @@ export const catalogProcessingExtensionPoint = */ export interface CatalogAnalysisExtensionPoint { /** - * Replaces the entire location analyzer with a new one. This will cause any - * SCM analyzers added through `addScmLocationAnalyzer` to be ignored. + * Replaces the entire location analyzer with a new one. + * + * @remarks + * + * By providing a factory function you can access all the SCM analyzers that + * have been added through `addScmLocationAnalyzer`. If you provide a + * `LocationAnalyzer` directly, the SCM analyzers will be ignored. */ - setLocationAnalyzer(analyzer: LocationAnalyzer): void; + setLocationAnalyzer( + analyzerOrFactory: + | LocationAnalyzer + | ((options: { + scmLocationAnalyzers: ScmLocationAnalyzer[]; + }) => Promise<{ locationAnalyzer: LocationAnalyzer }>), + ): void; /** * Adds an analyzer for a specific SCM type to the default location analyzer. From 42eaf63a7525f7bce09838f78d29abfc196680ca Mon Sep 17 00:00:00 2001 From: Heikki Hellgren <heikki.hellgren@op.fi> Date: Fri, 3 May 2024 12:27:52 +0300 Subject: [PATCH 210/234] feat: increase default and allow changing snackbar auto hide duration Signed-off-by: Heikki Hellgren <heikki.hellgren@op.fi> --- .changeset/perfect-beers-explode.md | 5 +++++ plugins/notifications/api-report.md | 1 + .../NotificationsSideBarItem/NotificationsSideBarItem.tsx | 5 +++++ 3 files changed, 11 insertions(+) create mode 100644 .changeset/perfect-beers-explode.md diff --git a/.changeset/perfect-beers-explode.md b/.changeset/perfect-beers-explode.md new file mode 100644 index 0000000000..3045ca7f12 --- /dev/null +++ b/.changeset/perfect-beers-explode.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Increase default and allow modifying notification snackbar auto hide duration diff --git a/plugins/notifications/api-report.md b/plugins/notifications/api-report.md index 3699aa1bb0..9240bdb1c2 100644 --- a/plugins/notifications/api-report.md +++ b/plugins/notifications/api-report.md @@ -102,6 +102,7 @@ export const NotificationsSidebarItem: (props?: { webNotificationsEnabled?: boolean; titleCounterEnabled?: boolean; snackbarEnabled?: boolean; + snackbarAutoHideDuration?: number | null; className?: string; icon?: IconComponent; text?: string; diff --git a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx index 000eabcae6..7b55c63747 100644 --- a/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx +++ b/plugins/notifications/src/components/NotificationsSideBarItem/NotificationsSideBarItem.tsx @@ -83,6 +83,7 @@ export const NotificationsSidebarItem = (props?: { webNotificationsEnabled?: boolean; titleCounterEnabled?: boolean; snackbarEnabled?: boolean; + snackbarAutoHideDuration?: number | null; className?: string; icon?: IconComponent; text?: string; @@ -93,6 +94,7 @@ export const NotificationsSidebarItem = (props?: { webNotificationsEnabled = false, titleCounterEnabled = true, snackbarEnabled = true, + snackbarAutoHideDuration = 10000, icon = NotificationsIcon, text = 'Notifications', ...restProps @@ -100,6 +102,7 @@ export const NotificationsSidebarItem = (props?: { webNotificationsEnabled: false, titleCounterEnabled: true, snackbarEnabled: true, + snackbarAutoHideDuration: 10000, }; const { loading, error, value, retry } = useNotificationsApi(api => @@ -196,6 +199,7 @@ export const NotificationsSidebarItem = (props?: { variant: notification.payload.severity, anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, action, + autoHideDuration: snackbarAutoHideDuration, } as OptionsWithExtraProps<VariantType>); } }) @@ -216,6 +220,7 @@ export const NotificationsSidebarItem = (props?: { sendWebNotification, webNotificationsEnabled, snackbarEnabled, + snackbarAutoHideDuration, notificationsApi, alertApi, getSnackbarProperties, From 598e8e51dd3430702f05c0f89c33215a0c49ebf3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Fri, 3 May 2024 14:09:58 +0200 Subject: [PATCH 211/234] root: patch changesets to handle workspace ranges differently Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- ...le-release-plan-npm-6.0.0-f7b3005037.patch | 32 +++++++++++++++++++ package.json | 1 + yarn.lock | 16 +++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 .yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch diff --git a/.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch b/.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch new file mode 100644 index 0000000000..045e983dbe --- /dev/null +++ b/.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch @@ -0,0 +1,32 @@ +diff --git a/dist/changesets-assemble-release-plan.cjs.js b/dist/changesets-assemble-release-plan.cjs.js +index ee5c0f67fabadeb112e9f238d8b144a4d125830f..9b0e1a156dd88cee35f82faf718d82a8a8f80325 100644 +--- a/dist/changesets-assemble-release-plan.cjs.js ++++ b/dist/changesets-assemble-release-plan.cjs.js +@@ -179,12 +179,23 @@ function getDependencyVersionRanges(dependentPkgJSON, dependencyRelease) { + if (!versionRange) continue; + + if (versionRange.startsWith("workspace:")) { ++ // intentionally keep other workspace ranges untouched ++ // this has to be fixed but this should only be done when adding appropriate tests ++ let workspaceRange = versionRange.replace(/^workspace:/, ""); ++ switch (workspaceRange) { ++ case "*": ++ // workspace:* actually means the current exact version, and not a wildcard similar to a reguler * range ++ workspaceRange = dependencyRelease.oldVersion; ++ break; ++ case "~": ++ case "^": ++ // Use ^oldVersion for workspace:^ or ~oldVersion for workspace:~. ++ // The version range might have changed in dependent package, but that should have its own changeset bumping that package. ++ workspaceRange += dependencyRelease.oldVersion; ++ } + dependencyVersionRanges.push({ + depType: type, +- versionRange: // intentionally keep other workspace ranges untouched +- // this has to be fixed but this should only be done when adding appropriate tests +- versionRange === "workspace:*" ? // workspace:* actually means the current exact version, and not a wildcard similar to a reguler * range +- dependencyRelease.oldVersion : versionRange.replace(/^workspace:/, "") ++ versionRange: workspaceRange, + }); + } else { + dependencyVersionRanges.push({ diff --git a/package.json b/package.json index 5d9fbef716..768448402f 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ }, "prettier": "@spotify/prettier-config", "resolutions": { + "@changesets/assemble-release-plan@^6.0.0": "patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch", "@material-ui/pickers@^3.2.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch", "@material-ui/pickers@^3.3.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch", "@types/react": "^18", diff --git a/yarn.lock b/yarn.lock index c07c71c8b3..dbc233dd64 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7817,7 +7817,7 @@ __metadata: languageName: node linkType: hard -"@changesets/assemble-release-plan@npm:^6.0.0": +"@changesets/assemble-release-plan@npm:6.0.0": version: 6.0.0 resolution: "@changesets/assemble-release-plan@npm:6.0.0" dependencies: @@ -7831,6 +7831,20 @@ __metadata: languageName: node linkType: hard +"@changesets/assemble-release-plan@patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch::locator=root%40workspace%3A.": + version: 6.0.0 + resolution: "@changesets/assemble-release-plan@patch:@changesets/assemble-release-plan@npm%3A6.0.0#./.yarn/patches/@changesets-assemble-release-plan-npm-6.0.0-f7b3005037.patch::version=6.0.0&hash=43c5e4&locator=root%40workspace%3A." + dependencies: + "@babel/runtime": ^7.20.1 + "@changesets/errors": ^0.2.0 + "@changesets/get-dependents-graph": ^2.0.0 + "@changesets/types": ^6.0.0 + "@manypkg/get-packages": ^1.1.3 + semver: ^7.5.3 + checksum: b7a68e28d03379bdc2a1d7171963990e1b88d0e5efca5f5ed490c0f583d66d0ccbd4c04ebf4e5cd1c38e1346ff0e5c4c16e1b4973cdd97fabdbad0c6996fe016 + languageName: node + linkType: hard + "@changesets/changelog-git@npm:^0.2.0": version: 0.2.0 resolution: "@changesets/changelog-git@npm:0.2.0" From ece858b60957a8372aa89eab2abe12e46f541bb1 Mon Sep 17 00:00:00 2001 From: Jack Palmer <jackpalmer@spotify.com> Date: Fri, 3 May 2024 14:20:46 +0100 Subject: [PATCH 212/234] fix: Add config.d.ts for auth-backend-module-github-provider Signed-off-by: Jack Palmer <jackpalmer@spotify.com> --- .../config.d.ts | 34 +++++++++++++++++++ .../package.json | 30 ++++++++-------- 2 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 plugins/auth-backend-module-github-provider/config.d.ts diff --git a/plugins/auth-backend-module-github-provider/config.d.ts b/plugins/auth-backend-module-github-provider/config.d.ts new file mode 100644 index 0000000000..c8af7eb5db --- /dev/null +++ b/plugins/auth-backend-module-github-provider/config.d.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2020 The 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 { + auth?: { + providers?: { + /** @visibility frontend */ + github?: { + [authEnv: string]: { + clientId: string; + /** + * @visibility secret + */ + clientSecret: string; + callbackUrl?: string; + enterpriseInstanceUrl?: string; + }; + }; + }; + }; +} diff --git a/plugins/auth-backend-module-github-provider/package.json b/plugins/auth-backend-module-github-provider/package.json index 65217f0eeb..0e8db4d1a5 100644 --- a/plugins/auth-backend-module-github-provider/package.json +++ b/plugins/auth-backend-module-github-provider/package.json @@ -1,10 +1,10 @@ { "name": "@backstage/plugin-auth-backend-module-github-provider", - "description": "The github-provider backend module for the auth plugin.", "version": "0.1.15-next.1", - "main": "src/index.ts", - "types": "src/index.ts", - "license": "Apache-2.0", + "description": "The github-provider backend module for the auth plugin.", + "backstage": { + "role": "backend-plugin-module" + }, "publishConfig": { "access": "public", "main": "dist/index.cjs.js", @@ -15,17 +15,21 @@ "url": "https://github.com/backstage/backstage", "directory": "plugins/auth-backend-module-github-provider" }, - "backstage": { - "role": "backend-plugin-module" - }, + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "dist", + "config.d.ts" + ], "scripts": { - "start": "backstage-cli package start", "build": "backstage-cli package build", - "lint": "backstage-cli package lint", - "test": "backstage-cli package test", "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", "prepack": "backstage-cli package prepack", - "postpack": "backstage-cli package postpack" + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", @@ -39,7 +43,5 @@ "@backstage/plugin-auth-backend": "workspace:^", "supertest": "^6.3.3" }, - "files": [ - "dist" - ] + "configSchema": "config.d.ts" } From e4fb486a3edc86617eaccd95f601323e0b983d0a Mon Sep 17 00:00:00 2001 From: Jack Palmer <jackpalmer@spotify.com> Date: Fri, 3 May 2024 14:23:11 +0100 Subject: [PATCH 213/234] chore: add changeset Signed-off-by: Jack Palmer <jackpalmer@spotify.com> --- .changeset/blue-balloons-draw.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/blue-balloons-draw.md diff --git a/.changeset/blue-balloons-draw.md b/.changeset/blue-balloons-draw.md new file mode 100644 index 0000000000..8208af5105 --- /dev/null +++ b/.changeset/blue-balloons-draw.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend-module-github-provider': patch +--- + +fix: Add missing config.d.ts for auth-backend-module-github-provider From 8f6a945b0ee0b33bc32dbce0bccd5e53c68ef0d6 Mon Sep 17 00:00:00 2001 From: Jack Palmer <jackpalmer@spotify.com> Date: Fri, 3 May 2024 14:24:23 +0100 Subject: [PATCH 214/234] fix: Typo in copyright Signed-off-by: Jack Palmer <jackpalmer@spotify.com> --- plugins/auth-backend-module-github-provider/config.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend-module-github-provider/config.d.ts b/plugins/auth-backend-module-github-provider/config.d.ts index c8af7eb5db..e79711310d 100644 --- a/plugins/auth-backend-module-github-provider/config.d.ts +++ b/plugins/auth-backend-module-github-provider/config.d.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. From 4d15444275f26bedf4037bad5c93f73c7aa48cd5 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino <vincenzos@spotify.com> Date: Fri, 3 May 2024 16:47:25 +0200 Subject: [PATCH 215/234] cli: fix repo fix workspace path on windows Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com> --- packages/cli/src/commands/repo/fix.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/commands/repo/fix.ts b/packages/cli/src/commands/repo/fix.ts index b975203a73..049f5a8416 100644 --- a/packages/cli/src/commands/repo/fix.ts +++ b/packages/cli/src/commands/repo/fix.ts @@ -22,11 +22,7 @@ import { } from '@backstage/cli-node'; import { OptionValues } from 'commander'; import fs from 'fs-extra'; -import { - resolve as resolvePath, - join as joinPath, - relative as relativePath, -} from 'path'; +import { resolve as resolvePath, posix, relative as relativePath } from 'path'; import { paths } from '../../lib/paths'; /** @@ -205,7 +201,7 @@ export function createRepositoryFieldFixer() { const rootDir = rootRepoField.directory || ''; return (pkg: FixablePackage) => { - const expectedPath = joinPath( + const expectedPath = posix.join( rootDir, relativePath(paths.targetRoot, pkg.dir), ); From c52052bdceda72743e2d0305d1f6aff3c2d6728c Mon Sep 17 00:00:00 2001 From: Jack Palmer <jackpalmer@spotify.com> Date: Fri, 3 May 2024 15:49:28 +0100 Subject: [PATCH 216/234] fox: Add config.d.ts for aws-alb and rename iss to issuer Signed-off-by: Jack Palmer <jackpalmer@spotify.com> --- .../config.d.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 plugins/auth-backend-module-aws-alb-provider/config.d.ts diff --git a/plugins/auth-backend-module-aws-alb-provider/config.d.ts b/plugins/auth-backend-module-aws-alb-provider/config.d.ts new file mode 100644 index 0000000000..1978e5a4df --- /dev/null +++ b/plugins/auth-backend-module-aws-alb-provider/config.d.ts @@ -0,0 +1,27 @@ +/* + * 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. + */ + +export interface Config { + auth?: { + providers?: { + /** @visibility frontend */ + awsalb?: { + issuer?: string; + region: string; + }; + }; + }; +} From 9f974a05da653abd5a24ea43f7f496e43291066b Mon Sep 17 00:00:00 2001 From: Jack Palmer <jackpalmer@spotify.com> Date: Fri, 3 May 2024 15:49:53 +0100 Subject: [PATCH 217/234] fix: Tidy auth-backend config.d.ts Signed-off-by: Jack Palmer <jackpalmer@spotify.com> --- plugins/auth-backend/config.d.ts | 42 -------------------------------- 1 file changed, 42 deletions(-) diff --git a/plugins/auth-backend/config.d.ts b/plugins/auth-backend/config.d.ts index f0ceaa4224..a7ea18ed1d 100644 --- a/plugins/auth-backend/config.d.ts +++ b/plugins/auth-backend/config.d.ts @@ -89,29 +89,6 @@ export interface Config { * @additionalProperties true */ providers?: { - /** @visibility frontend */ - google?: { - [authEnv: string]: { - clientId: string; - /** - * @visibility secret - */ - clientSecret: string; - callbackUrl?: string; - }; - }; - /** @visibility frontend */ - github?: { - [authEnv: string]: { - clientId: string; - /** - * @visibility secret - */ - clientSecret: string; - callbackUrl?: string; - enterpriseInstanceUrl?: string; - }; - }; /** @visibility frontend */ saml?: { entryPoint: string; @@ -137,20 +114,6 @@ export interface Config { acceptedClockSkewMs?: number; }; /** @visibility frontend */ - oauth2?: { - [authEnv: string]: { - clientId: string; - /** - * @visibility secret - */ - clientSecret: string; - authorizationUrl: string; - tokenUrl: string; - scope?: string; - disableRefresh?: boolean; - }; - }; - /** @visibility frontend */ auth0?: { [authEnv: string]: { clientId: string; @@ -177,11 +140,6 @@ export interface Config { callbackUrl?: string; }; }; - /** @visibility frontend */ - awsalb?: { - iss?: string; - region: string; - }; /** * The backstage token expiration. */ From ed4cd84b10ae123882b0cf549ea9f541ae7d843d Mon Sep 17 00:00:00 2001 From: Jack Palmer <jackpalmer@spotify.com> Date: Fri, 3 May 2024 15:51:05 +0100 Subject: [PATCH 218/234] chore: Update changelog Signed-off-by: Jack Palmer <jackpalmer@spotify.com> --- .changeset/blue-balloons-draw.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.changeset/blue-balloons-draw.md b/.changeset/blue-balloons-draw.md index 8208af5105..d17760a92b 100644 --- a/.changeset/blue-balloons-draw.md +++ b/.changeset/blue-balloons-draw.md @@ -3,3 +3,5 @@ --- fix: Add missing config.d.ts for auth-backend-module-github-provider +fix: Add missing config.d.ts for auth-backend-module-aws-alb-provider +fix: Remove duplicate provider config from auth-backend From cc3c51833bf48731751d435b21a341964deb2ad4 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino <vincenzos@spotify.com> Date: Fri, 3 May 2024 16:54:18 +0200 Subject: [PATCH 219/234] cli: repo fix changeset Signed-off-by: Vincenzo Scamporlino <vincenzos@spotify.com> --- .changeset/slimy-kids-behave.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/slimy-kids-behave.md diff --git a/.changeset/slimy-kids-behave.md b/.changeset/slimy-kids-behave.md new file mode 100644 index 0000000000..5e635878ff --- /dev/null +++ b/.changeset/slimy-kids-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixed an issue causing the `repo fix` command to set an incorrect `workspace` property using Windows From 4a0577e0ea14c7b490e7ef0d0a459a8b375e292b Mon Sep 17 00:00:00 2001 From: Jack Palmer <jackpalmer@spotify.com> Date: Fri, 3 May 2024 15:54:41 +0100 Subject: [PATCH 220/234] chore: Fix changelog Signed-off-by: Jack Palmer <jackpalmer@spotify.com> --- .changeset/blue-balloons-draw.md | 7 ------- .changeset/little-rockets-live.md | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 .changeset/blue-balloons-draw.md create mode 100644 .changeset/little-rockets-live.md diff --git a/.changeset/blue-balloons-draw.md b/.changeset/blue-balloons-draw.md deleted file mode 100644 index d17760a92b..0000000000 --- a/.changeset/blue-balloons-draw.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-auth-backend-module-github-provider': patch ---- - -fix: Add missing config.d.ts for auth-backend-module-github-provider -fix: Add missing config.d.ts for auth-backend-module-aws-alb-provider -fix: Remove duplicate provider config from auth-backend diff --git a/.changeset/little-rockets-live.md b/.changeset/little-rockets-live.md new file mode 100644 index 0000000000..767fda917e --- /dev/null +++ b/.changeset/little-rockets-live.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-auth-backend-module-aws-alb-provider': patch +'@backstage/plugin-auth-backend-module-github-provider': patch +'@backstage/plugin-auth-backend': patch +--- + +fix: Move config declarations to appropriate auth backend modules From a8ea3c5918f8b7c5e54cad5f11aaadf11c856079 Mon Sep 17 00:00:00 2001 From: Jeeva Ramanathan <jeevaramanathan.m@infosys.com> Date: Fri, 3 May 2024 20:34:59 +0530 Subject: [PATCH 221/234] Check pipeline Signed-off-by: Jeeva Ramanathan <jeevaramanathan.m@infosys.com> From 073b2ebf638851d28dc8f1434d0b36f94eebfaf6 Mon Sep 17 00:00:00 2001 From: Beth Griggs <bethanyngriggs@gmail.com> Date: Fri, 3 May 2024 15:21:32 +0100 Subject: [PATCH 222/234] chore(test): increase test coverage of WinstonLogger Signed-off-by: Beth Griggs <bethanyngriggs@gmail.com> --- .../src/logging/WinstonLogger.test.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/packages/backend-app-api/src/logging/WinstonLogger.test.ts b/packages/backend-app-api/src/logging/WinstonLogger.test.ts index c7173b1cbe..bec6a9fc0f 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.test.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.test.ts @@ -22,6 +22,17 @@ function msg(info: TransformableInfo): TransformableInfo { } describe('WinstonLogger', () => { + it('creates a winston logger instance with default options', () => { + const logger = WinstonLogger.create({}); + expect(logger).toBeInstanceOf(WinstonLogger); + }); + + it('creates a child logger', () => { + const logger = WinstonLogger.create({}); + const childLogger = logger.child({ plugin: 'test-plugin' }); + expect(childLogger).toBeInstanceOf(WinstonLogger); + }); + it('redacter should redact and escape regex', () => { const redacter = WinstonLogger.redacter(); const log = { @@ -47,4 +58,24 @@ describe('WinstonLogger', () => { }), ); }); + + it('redacter should redact nested object', () => { + const redacter = WinstonLogger.redacter(); + const log = { + level: 'error', + message: { + nested: 'hello (world) from nested object', + }, + }; + + redacter.add(['hello']); + expect(redacter.format.transform(msg(log))).toEqual( + msg({ + ...log, + message: { + nested: '[REDACTED] (world) from nested', + }, + }), + ); + }); }); From 0cda20fa31a4a92ad69a3a3e87d93d4ee64bb0df Mon Sep 17 00:00:00 2001 From: Bethany Griggs <bethanyngriggs@gmail.com> Date: Fri, 3 May 2024 18:08:37 +0100 Subject: [PATCH 223/234] fixup! typo Signed-off-by: Bethany Griggs <bethanyngriggs@gmail.com> --- packages/backend-app-api/src/logging/WinstonLogger.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-app-api/src/logging/WinstonLogger.test.ts b/packages/backend-app-api/src/logging/WinstonLogger.test.ts index bec6a9fc0f..d025719d9a 100644 --- a/packages/backend-app-api/src/logging/WinstonLogger.test.ts +++ b/packages/backend-app-api/src/logging/WinstonLogger.test.ts @@ -73,7 +73,7 @@ describe('WinstonLogger', () => { msg({ ...log, message: { - nested: '[REDACTED] (world) from nested', + nested: '[REDACTED] (world) from nested object', }, }), ); From a1f3cf28646d84e1ba24f31292d55e6ac11e7384 Mon Sep 17 00:00:00 2001 From: Stephon Parker <sgparker62@gmail.com> Date: Sat, 4 May 2024 10:12:37 -0400 Subject: [PATCH 224/234] updating the devtools plugin backend readme to add an unlisted property that will cause the build to fail Signed-off-by: Stephon Parker <sgparker62@gmail.com> --- plugins/devtools-backend/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/devtools-backend/README.md b/plugins/devtools-backend/README.md index 4439c8cd9f..bf0eb64a98 100644 --- a/plugins/devtools-backend/README.md +++ b/plugins/devtools-backend/README.md @@ -28,6 +28,7 @@ Here's how to get the DevTools Backend up and running: logger: env.logger, config: env.config, permissions: env.permissions, + discovery: env.discovery, }); } ``` From b741e21fa704ff78675641ebc506da6717164fb1 Mon Sep 17 00:00:00 2001 From: Tharun Paul <55498156+paul-tharun@users.noreply.github.com> Date: Sat, 4 May 2024 23:08:10 +0530 Subject: [PATCH 225/234] Fix hyperlink to what-is-a-plugin link Signed-off-by: Tharun Paul <55498156+paul-tharun@users.noreply.github.com> --- docs/faq/product.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq/product.md b/docs/faq/product.md index 79465cf53d..edbb1ffb4d 100644 --- a/docs/faq/product.md +++ b/docs/faq/product.md @@ -37,7 +37,7 @@ more, read our blog post, Yes, we've already started releasing open source versions of some of the plugins we use here, and we'll continue to do so. -[Plugins](#what-is-a-plugin-in-backstage) are the building blocks of +[Plugins](technical.md#what-is-a-plugin-in-backstage) are the building blocks of functionality in Backstage. We have over 120 plugins inside Spotify — many of those are specialized for our use, so will remain internal and proprietary to us. But we estimate that about a third of our existing plugins make good open From 036feca470fa144919d6c1ebfc9751d18e9425ce Mon Sep 17 00:00:00 2001 From: Stephon Parker <sgparker62@gmail.com> Date: Sat, 4 May 2024 19:01:06 -0400 Subject: [PATCH 226/234] adding changeset to final pr state Signed-off-by: Stephon Parker <sgparker62@gmail.com> --- .changeset/grumpy-toes-tap.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/grumpy-toes-tap.md diff --git a/.changeset/grumpy-toes-tap.md b/.changeset/grumpy-toes-tap.md new file mode 100644 index 0000000000..ff3fbbf2df --- /dev/null +++ b/.changeset/grumpy-toes-tap.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-devtools-backend': patch +--- + +Added discovery property to the readme documentation to ensure that it will build when setting it up as new to a Backstage instance From 2866e60532ba78fe0cd2051134c157d2201e898f Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa <sarabadu@gmail.com> Date: Sun, 5 May 2024 14:29:52 +0200 Subject: [PATCH 227/234] add template editor docs Signed-off-by: Juan Pablo Garcia Ripa <sarabadu@gmail.com> --- .../software-templates/context-menu.png | Bin 0 -> 91429 bytes .../template-editor-dry-run.png | Bin 0 -> 105995 bytes .../template-editor-load-dir.png | Bin 0 -> 597409 bytes .../software-templates/writing-templates.md | 36 ++++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 docs/assets/software-templates/context-menu.png create mode 100644 docs/assets/software-templates/template-editor-dry-run.png create mode 100644 docs/assets/software-templates/template-editor-load-dir.png diff --git a/docs/assets/software-templates/context-menu.png b/docs/assets/software-templates/context-menu.png new file mode 100644 index 0000000000000000000000000000000000000000..fa4f52cc48c29cbcacb372ef20bace4412930262 GIT binary patch literal 91429 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 literal 0 HcmV?d00001 diff --git a/docs/assets/software-templates/template-editor-dry-run.png b/docs/assets/software-templates/template-editor-dry-run.png new file mode 100644 index 0000000000000000000000000000000000000000..6095fb5cb0bcb2b45277cbdc1a3899e4a4fa42b2 GIT binary patch literal 105995 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­uS@C_AyQtgfQJ!?pIS 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 LNkvXXu0mjfloK)$ literal 0 HcmV?d00001 diff --git a/docs/assets/software-templates/template-editor-load-dir.png b/docs/assets/software-templates/template-editor-load-dir.png new file mode 100644 index 0000000000000000000000000000000000000000..8fb22ab47c870d01798f992f725fbe314b8c216b GIT binary patch literal 597409 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%0A~ku6#a8P!&X+ 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�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ⅈ#v&DS#wqn!98Q$5p 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%0AU*5! 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 o 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%0Af(^KDXA22toL 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 ZfgBe_mkn<r22KJ09;s+3mnvGk`agSK@nHY} literal 0 HcmV?d00001 diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index ad542ba4a3..d67f886e12 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -738,3 +738,39 @@ The `projectSlug` filter generates a project slug from a repository URL - **Input**: `github.com?repo=backstage&org=backstage` - **Output**: `backstage/backstage` + +## Template Editor + +Writing template is most of the times an iterative process. You will need to test your template to make sure it has a good user experience and that it works as expected. To help on this process the scaffolder comes with a build in template editor that allows you to test your template in a real environment for querying data and execute the actions on dry-run mode to see the results of those one. + +To access to the template editor you can go to the templates page and select "Template Editor" from the context menu or navigate to the `{scaffolder-path}/edit` url. (i.e. the default route would be `/create/edit`) + +![Context menu](../../assets/software-templates/context-menu.png) + +The template editor has 3 main sections: + +1. **Load Template Directory**: Load a local template directory, allowing you to both edit and try executing your own template. +2. **Edit Template Form**: Preview and edit a template form, either using a sample template or by loading a template from the catalog. +3. **Custom Field Explorer**: View and play around with available installed custom field extensions. + +### Load Template Directory + +Allow to load a directory on your local file system that contains a template and editing the files in it while previewing the form and executing the template. + +![template editor load dir](../../assets/software-templates/template-editor-load-dir.png) + +if you complete the form in the right side and click on `Create` button, the template will be executed in dry-run mode and the result will be shown in the `Dry-run result` drawer that will pop-up at the bottom of the screen. + +Here we could find all the file system results of the template execution as well as the logs of each action that was executed. + +![dry run drawer example](../../assets/software-templates/template-editor-dry-run.png) + +### Edit Template Form + +This is a reduced version of the template editor that allows you to select any template from the catalog and do some modifications on the form presented to the user to test some changes. + +Have in mind that changes in this form will not be saved on the template and is meant to test out changes to replicate them manually on the template file after. + +### Custom Field Explorer + +The custom filed explorer allows you to select any custom field loaded on the backstage instance and test different values and configurations. From cd24dca046e0c9ddb6be003d6e12ce82161915f2 Mon Sep 17 00:00:00 2001 From: aramissennyeydd <aramis.sennyey@doordash.com> Date: Sun, 5 May 2024 16:14:43 -0400 Subject: [PATCH 228/234] docs(nbs): proxying Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com> --- docs/plugins/proxying.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md index 40d155e13b..bff38fea40 100644 --- a/docs/plugins/proxying.md +++ b/docs/plugins/proxying.md @@ -15,6 +15,16 @@ can be the best choice for communicating with an API. The plugin is already added to a default Backstage project. +### New Backend + +To add it to a project, add the following line in `packages/backend/src/index.ts`: + +```ts +backend.add(import('@backstage/plugin-proxy-backend/alpha')); +``` + +### Old Backend + In `packages/backend/src/index.ts`: ```ts From 0911fa60c8d6cf2ee521fbfe22a4707c905d9951 Mon Sep 17 00:00:00 2001 From: aramissennyeydd <aramis.sennyey@doordash.com> Date: Sun, 5 May 2024 16:37:41 -0400 Subject: [PATCH 229/234] docs(nbs): plugin observability Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com> --- docs/plugins/observability.md | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/docs/plugins/observability.md b/docs/plugins/observability.md index d8778f07f8..bb6f5abbe4 100644 --- a/docs/plugins/observability.md +++ b/docs/plugins/observability.md @@ -37,7 +37,39 @@ An example log line could look as follows: ## Health Checks -The example backend in the Backstage repository +### New Backend + +The new backend is moving towards health checks being plugin-based, as such there is no current plugin for providing a health check route. You can add this yourself easily though, + +```ts +import { + coreServices, + createBackendModule, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; + +const healthCheck = createBackendPlugin({ + pluginId: 'healthcheck', + + register(env) { + env.registerInit({ + deps: { + rootHttpRouter: coreServices.rootHttpRouter, + }, + init: async ({ rootHttpRouter }) => { + // You can adjust the route name and response as you need. + rootHttpRouter.use('/healthcheck', (req, res) => { + res.json({ status: 'ok' }); + }); + }, + }); + }, +}); +``` + +### Old Backend + +The example old backend in the Backstage repository [supplies](https://github.com/backstage/backstage/blob/bc18571b7a742863a770b2a54e785d6bbef7e184/packages/backend/src/index.ts#L99) a very basic health check endpoint on the `/healthcheck` route. You may add such a handler to your backend as well, and supply your own logic to it that fits From b17e7bea59faebfbe685e58e47b8decd3cb09367 Mon Sep 17 00:00:00 2001 From: aramissennyeydd <aramis.sennyey@doordash.com> Date: Sun, 5 May 2024 16:52:44 -0400 Subject: [PATCH 230/234] add section about logging Signed-off-by: aramissennyeydd <aramis.sennyey@doordash.com> --- docs/plugins/observability.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/plugins/observability.md b/docs/plugins/observability.md index bb6f5abbe4..e13653706f 100644 --- a/docs/plugins/observability.md +++ b/docs/plugins/observability.md @@ -15,6 +15,32 @@ See how to install Datadog Events in your app ## Logging +### New Backend + +The backend supplies a central logging service, [`rootLogger`](../backend-system/core-services/root-logger.md), as well as a plugin based logger, [`logger`](../backend-system/core-services/logger.md) from `coreServices`. To add additional granularity to your logs, you can create children from the plugin based logger, using the `.child()` method and provide is with JSON data. For example, if you wanted to log items for a specific span in your plugin, you could do + +```ts +export function createRouter({ logger }) { + const router = Router(); + + router.post('/task/:taskId/queue', (req, res) => { + const { taskId } = req.params; + const taskLogger = logger.child({ task: taskId }); + taskLogger.log('Queueing this task.'); + }); + + router.get('/task/:taskId/results', (req, res) => { + const { taskId } = req.params; + const taskLogger = logger.child({ task: taskId }); + taskLogger.log('Getting the results of this task.'); + }); +} +``` + +You can also add additional metadata to all logs for your Backstage instance by overriding the `rootLogger` implementation, you can see an example in [the `logger` docs](../backend-system/core-services/logger.md#configuring-the-service). + +### Old Backend + The backend supplies a central [winston](https://github.com/winstonjs/winston) root logger that plugins are expected to use for their logging needs. In the default production setup, it emits structured JSON logs on stdout, with a field From 812dff05b9b99b7665c9d09bd00bb4a86799ca06 Mon Sep 17 00:00:00 2001 From: Mike Ball <mikedball@gmail.com> Date: Sun, 5 May 2024 20:27:35 -0400 Subject: [PATCH 231/234] chore(plugin-template): add missing semicolon This adds a previously-missing semicolon to the `ExampleFetchComponent.test.tsx` file templated out by `backstage-cli new --select plugin`. Signed-off-by: Mike Ball <mikedball@gmail.com> --- .changeset/tasty-moles-jog.md | 5 +++++ .../ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/tasty-moles-jog.md diff --git a/.changeset/tasty-moles-jog.md b/.changeset/tasty-moles-jog.md new file mode 100644 index 0000000000..f3ac3b63e4 --- /dev/null +++ b/.changeset/tasty-moles-jog.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Add previously-missing semicolon to tsx file templated by `backstage-cli new --select plugin`. diff --git a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs index 1fe424ab9b..1e746ff39a 100644 --- a/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs +++ b/packages/cli/templates/default-plugin/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx.hbs @@ -8,7 +8,7 @@ describe('ExampleFetchComponent', () => { // Wait for the table to render const table = await screen.findByRole('table'); - const nationality = screen.getAllByText('GB') + const nationality = screen.getAllByText('GB'); // Assert that the table contains the expected user data expect(table).toBeInTheDocument(); expect(screen.getByAltText('Carolyn')).toBeInTheDocument(); From e96202968cfb06c768df69bee98dfd3f2cd7afbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= <freben@gmail.com> Date: Mon, 6 May 2024 07:13:26 +0200 Subject: [PATCH 232/234] Update .changeset/tasty-moles-jog.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw <freben@gmail.com> --- .changeset/tasty-moles-jog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/tasty-moles-jog.md b/.changeset/tasty-moles-jog.md index f3ac3b63e4..f28b77337e 100644 --- a/.changeset/tasty-moles-jog.md +++ b/.changeset/tasty-moles-jog.md @@ -2,4 +2,4 @@ '@backstage/cli': patch --- -Add previously-missing semicolon to tsx file templated by `backstage-cli new --select plugin`. +Add previously-missing semicolon in file templated by `backstage-cli new --select plugin`. From fba2993675b5609ee75efc880bf49b1878b03819 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Mon, 6 May 2024 11:44:30 +0200 Subject: [PATCH 233/234] Update docs/features/software-templates/writing-templates.md Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- docs/features/software-templates/writing-templates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index d67f886e12..bf21b91746 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -759,7 +759,7 @@ Allow to load a directory on your local file system that contains a template and ![template editor load dir](../../assets/software-templates/template-editor-load-dir.png) -if you complete the form in the right side and click on `Create` button, the template will be executed in dry-run mode and the result will be shown in the `Dry-run result` drawer that will pop-up at the bottom of the screen. +If you complete the form in the right side and click on `Create` button, the template will be executed in dry-run mode and the result will be shown in the `Dry-run result` drawer that will pop-up at the bottom of the screen. Here we could find all the file system results of the template execution as well as the logs of each action that was executed. From e754da5127336da75e6caaf2133ec0aa71ec44fa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg <poldsberg@gmail.com> Date: Mon, 6 May 2024 11:54:57 +0200 Subject: [PATCH 234/234] e2e-test: remove unused puppeteer dependency Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com> --- packages/e2e-test/package.json | 1 - yarn.lock | 259 ++++++--------------------------- 2 files changed, 42 insertions(+), 218 deletions(-) diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index a41c724d5a..e4ddf47d73 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -43,7 +43,6 @@ "@backstage/cli": "workspace:^", "@types/fs-extra": "^11.0.0", "@types/node": "^18.17.8", - "@types/puppeteer": "^7.0.0", "nodemon": "^3.0.1" }, "nodemonConfig": { diff --git a/yarn.lock b/yarn.lock index ea85c43c11..0eb13f4629 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12254,24 +12254,6 @@ __metadata: languageName: node linkType: hard -"@puppeteer/browsers@npm:2.2.1": - version: 2.2.1 - resolution: "@puppeteer/browsers@npm:2.2.1" - dependencies: - debug: 4.3.4 - extract-zip: 2.0.1 - progress: 2.0.3 - proxy-agent: 6.4.0 - semver: 7.6.0 - tar-fs: 3.0.5 - unbzip2-stream: 1.4.3 - yargs: 17.7.2 - bin: - browsers: lib/cjs/main-cli.js - checksum: c2ec8bac9978ae6279d67442a3a81c517db1e172bd4603d1983eb48de12b088d8b565b1817abe21ba6df76be9a68e3fc543d4c7111c964a93f3ac9b14f7c76e5 - languageName: node - linkType: hard - "@radix-ui/primitive@npm:1.0.1": version: 1.0.1 resolution: "@radix-ui/primitive@npm:1.0.1" @@ -16495,15 +16477,6 @@ __metadata: languageName: node linkType: hard -"@types/puppeteer@npm:^7.0.0": - version: 7.0.4 - resolution: "@types/puppeteer@npm:7.0.4" - dependencies: - puppeteer: "*" - checksum: c84a44b054454c13935a9cf0f8983166e238532397af4f321c918d89b43a91f854460e3d0dda122f72c258b444dbcdc04ada950e35adc1938ff3b7831c6fd7a4 - languageName: node - linkType: hard - "@types/qs@npm:*": version: 6.9.6 resolution: "@types/qs@npm:6.9.6" @@ -17081,7 +17054,7 @@ __metadata: languageName: node linkType: hard -"@types/yauzl@npm:^2.10.0, @types/yauzl@npm:^2.9.1": +"@types/yauzl@npm:^2.10.0": version: 2.10.3 resolution: "@types/yauzl@npm:2.10.3" dependencies: @@ -19655,7 +19628,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.2.1, buffer@npm:^5.5.0, buffer@npm:^5.7.1": +"buffer@npm:^5.5.0, buffer@npm:^5.7.1": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -20145,19 +20118,6 @@ __metadata: languageName: node linkType: hard -"chromium-bidi@npm:0.5.17": - version: 0.5.17 - resolution: "chromium-bidi@npm:0.5.17" - dependencies: - mitt: 3.0.1 - urlpattern-polyfill: 10.0.0 - zod: 3.22.4 - peerDependencies: - devtools-protocol: "*" - checksum: 522da996ed5abfb47707583cc24785f9aa05d87bd968dbd520f245cf8972fa3ec102f8d1d72fa07558daa70495d8c6f2bf364d8599eb60b77504e528601d8a30 - languageName: node - linkType: hard - "ci-info@npm:^2.0.0": version: 2.0.0 resolution: "ci-info@npm:2.0.0" @@ -21166,23 +21126,6 @@ __metadata: languageName: node linkType: hard -"cosmiconfig@npm:9.0.0": - version: 9.0.0 - resolution: "cosmiconfig@npm:9.0.0" - dependencies: - env-paths: ^2.2.1 - import-fresh: ^3.3.0 - js-yaml: ^4.1.0 - parse-json: ^5.2.0 - peerDependencies: - typescript: ">=4.9.5" - peerDependenciesMeta: - typescript: - optional: true - checksum: a30c424b53d442ea0bdd24cb1b3d0d8687c8dda4a17ab6afcdc439f8964438801619cdb66e8e79f63b9caa3e6586b60d8bab9ce203e72df6c5e80179b971fe8f - languageName: node - linkType: hard - "cosmiconfig@npm:^6.0.0": version: 6.0.0 resolution: "cosmiconfig@npm:6.0.0" @@ -22423,13 +22366,6 @@ __metadata: languageName: node linkType: hard -"devtools-protocol@npm:0.0.1262051": - version: 0.0.1262051 - resolution: "devtools-protocol@npm:0.0.1262051" - checksum: beaad00059964a661ab056d5e993492742c612c0370c6f08acd91490181c4d4ecf57d316eedb5a37fb6bb59321901d09ce50762f79ea09a50751d86f601b8f8e - languageName: node - linkType: hard - "dezalgo@npm:^1.0.0, dezalgo@npm:^1.0.4": version: 1.0.4 resolution: "dezalgo@npm:1.0.4" @@ -22839,7 +22775,6 @@ __metadata: "@backstage/errors": "workspace:^" "@types/fs-extra": ^11.0.0 "@types/node": ^18.17.8 - "@types/puppeteer": ^7.0.0 chalk: ^4.0.0 commander: ^12.0.0 cross-fetch: ^4.0.0 @@ -24589,23 +24524,6 @@ __metadata: languageName: node linkType: hard -"extract-zip@npm:2.0.1": - version: 2.0.1 - resolution: "extract-zip@npm:2.0.1" - dependencies: - "@types/yauzl": ^2.9.1 - debug: ^4.1.1 - get-stream: ^5.1.0 - yauzl: ^2.10.0 - dependenciesMeta: - "@types/yauzl": - optional: true - bin: - extract-zip: cli.js - checksum: 8cbda9debdd6d6980819cc69734d874ddd71051c9fe5bde1ef307ebcedfe949ba57b004894b585f758b7c9eeeea0e3d87f2dda89b7d25320459c2c9643ebb635 - languageName: node - linkType: hard - "extsprintf@npm:1.3.0": version: 1.3.0 resolution: "extsprintf@npm:1.3.0" @@ -24838,15 +24756,6 @@ __metadata: languageName: node linkType: hard -"fd-slicer@npm:~1.1.0": - version: 1.1.0 - resolution: "fd-slicer@npm:1.1.0" - dependencies: - pend: ~1.2.0 - checksum: c8585fd5713f4476eb8261150900d2cb7f6ff2d87f8feb306ccc8a1122efd152f1783bdb2b8dc891395744583436bfd8081d8e63ece0ec8687eeefea394d4ff2 - languageName: node - linkType: hard - "fecha@npm:^4.2.0": version: 4.2.0 resolution: "fecha@npm:4.2.0" @@ -26713,7 +26622,7 @@ __metadata: languageName: node linkType: hard -"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.1": +"http-proxy-agent@npm:^7.0.0": version: 7.0.2 resolution: "http-proxy-agent@npm:7.0.2" dependencies: @@ -26811,7 +26720,7 @@ __metadata: languageName: node linkType: hard -"https-proxy-agent@npm:7.0.4, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.2, https-proxy-agent@npm:^7.0.3": +"https-proxy-agent@npm:7.0.4, https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.2": version: 7.0.4 resolution: "https-proxy-agent@npm:7.0.4" dependencies: @@ -31790,13 +31699,6 @@ __metadata: languageName: node linkType: hard -"mitt@npm:3.0.1": - version: 3.0.1 - resolution: "mitt@npm:3.0.1" - checksum: b55a489ac9c2949ab166b7f060601d3b6d893a852515ae9eca4e11df01c013876df777ea109317622b5c1c60e8aae252558e33c8c94e14124db38f64a39614b1 - languageName: node - linkType: hard - "mixme@npm:^0.5.1": version: 0.5.4 resolution: "mixme@npm:0.5.4" @@ -33532,7 +33434,7 @@ __metadata: languageName: node linkType: hard -"pac-proxy-agent@npm:^7.0.0, pac-proxy-agent@npm:^7.0.1": +"pac-proxy-agent@npm:^7.0.0": version: 7.0.1 resolution: "pac-proxy-agent@npm:7.0.1" dependencies: @@ -35175,13 +35077,6 @@ __metadata: languageName: node linkType: hard -"progress@npm:2.0.3": - version: 2.0.3 - resolution: "progress@npm:2.0.3" - checksum: f67403fe7b34912148d9252cb7481266a354bd99ce82c835f79070643bb3c6583d10dbcfda4d41e04bbc1d8437e9af0fb1e1f2135727878f5308682a579429b7 - languageName: node - linkType: hard - "prom-client@npm:^15.0.0": version: 15.1.2 resolution: "prom-client@npm:15.1.2" @@ -35369,22 +35264,6 @@ __metadata: languageName: node linkType: hard -"proxy-agent@npm:6.4.0": - version: 6.4.0 - resolution: "proxy-agent@npm:6.4.0" - dependencies: - agent-base: ^7.0.2 - debug: ^4.3.4 - http-proxy-agent: ^7.0.1 - https-proxy-agent: ^7.0.3 - lru-cache: ^7.14.1 - pac-proxy-agent: ^7.0.1 - proxy-from-env: ^1.1.0 - socks-proxy-agent: ^8.0.2 - checksum: 4d3794ad5e07486298902f0a7f250d0f869fa0e92d790767ca3f793a81374ce0ab6c605f8ab8e791c4d754da96656b48d1c24cb7094bfd310a15867e4a0841d7 - languageName: node - linkType: hard - "proxy-from-env@npm:^1.1.0": version: 1.1.0 resolution: "proxy-from-env@npm:1.1.0" @@ -35467,33 +35346,6 @@ __metadata: languageName: node linkType: hard -"puppeteer-core@npm:22.6.4": - version: 22.6.4 - resolution: "puppeteer-core@npm:22.6.4" - dependencies: - "@puppeteer/browsers": 2.2.1 - chromium-bidi: 0.5.17 - debug: 4.3.4 - devtools-protocol: 0.0.1262051 - ws: 8.16.0 - checksum: 76d4328e7d2a788a7a2dc3a8b19571f8eda842ff990da2fe773c3e5c01236a03856b67d9a2a6ae976f8fa74ab38813e2c7e7c4b15b9715965b2156e9ca1f9a78 - languageName: node - linkType: hard - -"puppeteer@npm:*": - version: 22.6.4 - resolution: "puppeteer@npm:22.6.4" - dependencies: - "@puppeteer/browsers": 2.2.1 - cosmiconfig: 9.0.0 - devtools-protocol: 0.0.1262051 - puppeteer-core: 22.6.4 - bin: - puppeteer: lib/esm/puppeteer/node/cli.js - checksum: 7dcbda7fc3b999f3ee7808d1c67da41be0aeabd66b0831711a46a897a2681d117f044263e57aeb7d0a096c420bb9f5871c5c2b21c38e40d323fa675e1de322c9 - languageName: node - linkType: hard - "pure-color@npm:^1.2.0": version: 1.3.0 resolution: "pure-color@npm:1.3.0" @@ -37844,7 +37696,16 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.6.0, semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.4.0, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": +"semver@npm:^6.0.0, semver@npm:^6.1.0, semver@npm:^6.2.0, semver@npm:^6.3.0, semver@npm:^6.3.1": + version: 6.3.1 + resolution: "semver@npm:6.3.1" + bin: + semver: bin/semver.js + checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2 + languageName: node + linkType: hard + +"semver@npm:^7.0.0, semver@npm:^7.1.1, semver@npm:^7.1.3, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.3.8, semver@npm:^7.4.0, semver@npm:^7.5.3, semver@npm:^7.5.4, semver@npm:^7.6.0": version: 7.6.0 resolution: "semver@npm:7.6.0" dependencies: @@ -37855,15 +37716,6 @@ __metadata: languageName: node linkType: hard -"semver@npm:^6.0.0, semver@npm:^6.1.0, semver@npm:^6.2.0, semver@npm:^6.3.0, semver@npm:^6.3.1": - version: 6.3.1 - resolution: "semver@npm:6.3.1" - bin: - semver: bin/semver.js - checksum: ae47d06de28836adb9d3e25f22a92943477371292d9b665fb023fae278d345d508ca1958232af086d85e0155aee22e313e100971898bbb8d5d89b8b1d4054ca2 - languageName: node - linkType: hard - "send@npm:0.18.0": version: 0.18.0 resolution: "send@npm:0.18.0" @@ -39604,7 +39456,19 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:3.0.5, tar-fs@npm:^3.0.5": +"tar-fs@npm:^2.0.0": + version: 2.1.1 + resolution: "tar-fs@npm:2.1.1" + dependencies: + chownr: ^1.1.1 + mkdirp-classic: ^0.5.2 + pump: ^3.0.0 + tar-stream: ^2.1.4 + checksum: f5b9a70059f5b2969e65f037b4e4da2daf0fa762d3d232ffd96e819e3f94665dbbbe62f76f084f1acb4dbdcce16c6e4dac08d12ffc6d24b8d76720f4d9cf032d + languageName: node + linkType: hard + +"tar-fs@npm:^3.0.5": version: 3.0.5 resolution: "tar-fs@npm:3.0.5" dependencies: @@ -39621,18 +39485,6 @@ __metadata: languageName: node linkType: hard -"tar-fs@npm:^2.0.0": - version: 2.1.1 - resolution: "tar-fs@npm:2.1.1" - dependencies: - chownr: ^1.1.1 - mkdirp-classic: ^0.5.2 - pump: ^3.0.0 - tar-stream: ^2.1.4 - checksum: f5b9a70059f5b2969e65f037b4e4da2daf0fa762d3d232ffd96e819e3f94665dbbbe62f76f084f1acb4dbdcce16c6e4dac08d12ffc6d24b8d76720f4d9cf032d - languageName: node - linkType: hard - "tar-fs@npm:~2.0.1": version: 2.0.1 resolution: "tar-fs@npm:2.0.1" @@ -39900,7 +39752,7 @@ __metadata: languageName: node linkType: hard -"through@npm:2, through@npm:^2.3.6, through@npm:^2.3.8": +"through@npm:2, through@npm:^2.3.6": version: 2.3.8 resolution: "through@npm:2.3.8" checksum: a38c3e059853c494af95d50c072b83f8b676a9ba2818dcc5b108ef252230735c54e0185437618596c790bbba8fcdaef5b290405981ffa09dce67b1f1bf190cbd @@ -40757,16 +40609,6 @@ __metadata: languageName: node linkType: hard -"unbzip2-stream@npm:1.4.3": - version: 1.4.3 - resolution: "unbzip2-stream@npm:1.4.3" - dependencies: - buffer: ^5.2.1 - through: ^2.3.8 - checksum: 0e67c4a91f4fa0fc7b4045f8b914d3498c2fc2e8c39c359977708ec85ac6d6029840e97f508675fdbdf21fcb8d276ca502043406f3682b70f075e69aae626d1d - languageName: node - linkType: hard - "undefsafe@npm:^2.0.5": version: 2.0.5 resolution: "undefsafe@npm:2.0.5" @@ -41164,13 +41006,6 @@ __metadata: languageName: node linkType: hard -"urlpattern-polyfill@npm:10.0.0": - version: 10.0.0 - resolution: "urlpattern-polyfill@npm:10.0.0" - checksum: 61d890f151ea4ecf34a3dcab32c65ad1f3cda857c9d154af198260c6e5b2ad96d024593409baaa6d4428dd1ab206c14799bf37fe011117ac93a6a44913ac5aa4 - languageName: node - linkType: hard - "urlpattern-polyfill@npm:^9.0.0": version: 9.0.0 resolution: "urlpattern-polyfill@npm:9.0.0" @@ -42289,7 +42124,7 @@ __metadata: languageName: node linkType: hard -"ws@npm:*, ws@npm:8.16.0, ws@npm:^8.11.0, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.14.2, ws@npm:^8.16.0, ws@npm:^8.8.0": +"ws@npm:*, ws@npm:^8.11.0, ws@npm:^8.12.0, ws@npm:^8.13.0, ws@npm:^8.14.2, ws@npm:^8.16.0, ws@npm:^8.8.0": version: 8.16.0 resolution: "ws@npm:8.16.0" peerDependencies: @@ -42580,21 +42415,6 @@ __metadata: languageName: node linkType: hard -"yargs@npm:17.7.2, yargs@npm:^17.1.1, yargs@npm:^17.3.1, yargs@npm:^17.7.1, yargs@npm:^17.7.2": - version: 17.7.2 - resolution: "yargs@npm:17.7.2" - dependencies: - cliui: ^8.0.1 - escalade: ^3.1.1 - get-caller-file: ^2.0.5 - require-directory: ^2.1.1 - string-width: ^4.2.3 - y18n: ^5.0.5 - yargs-parser: ^21.1.1 - checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a - languageName: node - linkType: hard - "yargs@npm:^15.1.0": version: 15.4.1 resolution: "yargs@npm:15.4.1" @@ -42629,13 +42449,18 @@ __metadata: languageName: node linkType: hard -"yauzl@npm:^2.10.0": - version: 2.10.0 - resolution: "yauzl@npm:2.10.0" +"yargs@npm:^17.1.1, yargs@npm:^17.3.1, yargs@npm:^17.7.1, yargs@npm:^17.7.2": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" dependencies: - buffer-crc32: ~0.2.3 - fd-slicer: ~1.1.0 - checksum: 7f21fe0bbad6e2cb130044a5d1d0d5a0e5bf3d8d4f8c4e6ee12163ce798fee3de7388d22a7a0907f563ac5f9d40f8699a223d3d5c1718da90b0156da6904022b + cliui: ^8.0.1 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.3 + y18n: ^5.0.5 + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a languageName: node linkType: hard @@ -42810,7 +42635,7 @@ __metadata: languageName: node linkType: hard -"zod@npm:3.22.4, zod@npm:^3.22.4": +"zod@npm:^3.22.4": version: 3.22.4 resolution: "zod@npm:3.22.4" checksum: 80bfd7f8039b24fddeb0718a2ec7c02aa9856e4838d6aa4864335a047b6b37a3273b191ef335bf0b2002e5c514ef261ffcda5a589fb084a48c336ffc4cdbab7f