Merge branch 'master' into scaffolder/autocomplete-github-branches

This commit is contained in:
Benjamin Janssens
2025-02-04 15:14:14 +01:00
149 changed files with 1408 additions and 881 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-react': patch
---
Hide text output button if only one is present
+17
View File
@@ -0,0 +1,17 @@
---
'@backstage/plugin-scaffolder-backend-module-github': patch
---
**DEPRECATION**: The `getOctokitOptions` function signature with `repoUrl` option has been deprecated in favour of a function signature with individual `host`, `owner`, and `repo` parameters:
```diff
const octokitOptions = await getOctokitOptions({
integrations,
credentialsProvider,
token,
- repoUrl,
+ host,
+ owner,
+ repo,
});
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-plugin-api': minor
---
**BREAKING ALPHA**: Removed the deprecated `featureDiscoveryServiceRef` and `FeatureDiscoveryService`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-home': patch
---
feat: add cancel button for WidgetSettingsOverlay
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
---
`createGitlabProjectMigrateAction` can now output the `migrationId`
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/cli-node': patch
'@backstage/cli': patch
---
Fixed an issue where default feature type information wasn't being added to package.json/exports before publishing if exports didn't exist beforehand
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-defaults': patch
---
Internal refactor to stop importing the removed `FeatureDiscoveryService` from `@backstage/backend-plugin-api`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
Upgrade `dagre` to `@dagrejs/dagre`
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-app-api': minor
---
**BREAKING ALPHA**: Removed the deprecated `featureDiscoveryServiceFactory`. Existing usage can be replaced with `discoveryFeatureLoader` from `@backstage/backend-defaults`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-defaults': patch
---
Allow passing IP type to use with cloud-sql-connector
+10
View File
@@ -0,0 +1,10 @@
---
'@backstage/plugin-kubernetes-backend': patch
'@backstage/plugin-kubernetes-cluster': patch
'@backstage/plugin-kubernetes-common': patch
'@backstage/plugin-kubernetes': patch
---
The `/clusters` endpoint is now protected by the `kubernetes.clusters.read` permission.
The `/services/:serviceId` endpoint is now protected by the `kubernetes.resources.read` permission.
The `/resources` endpoints are now protected by the `kubernetes.resources.read` permission.
+22
View File
@@ -0,0 +1,22 @@
---
'@backstage/plugin-auth-backend-module-cloudflare-access-provider': minor
'@backstage/plugin-auth-backend-module-bitbucket-server-provider': minor
'@backstage/plugin-auth-backend-module-vmware-cloud-provider': minor
'@backstage/plugin-auth-backend-module-atlassian-provider': minor
'@backstage/plugin-auth-backend-module-bitbucket-provider': minor
'@backstage/plugin-auth-backend-module-microsoft-provider': minor
'@backstage/plugin-auth-backend-module-onelogin-provider': minor
'@backstage/plugin-auth-backend-module-pinniped-provider': minor
'@backstage/plugin-auth-backend-module-aws-alb-provider': minor
'@backstage/plugin-auth-backend-module-gcp-iap-provider': minor
'@backstage/plugin-auth-backend-module-github-provider': minor
'@backstage/plugin-auth-backend-module-gitlab-provider': minor
'@backstage/plugin-auth-backend-module-google-provider': minor
'@backstage/plugin-auth-backend-module-oauth2-provider': minor
'@backstage/plugin-auth-backend-module-auth0-provider': minor
'@backstage/plugin-auth-backend-module-oidc-provider': minor
'@backstage/plugin-auth-backend-module-okta-provider': minor
'@backstage/plugin-auth-node': minor
---
Added `auth.providers.<providerId>.sessionDuration` config for auth providers to allow the lifespan of user sessions to be configured.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
'@backstage/plugin-scaffolder': patch
---
Fixed bug of passing wrong value to `onChange` handler when using `GitLab` autocomplete
+12
View File
@@ -0,0 +1,12 @@
---
'@backstage/plugin-scaffolder-backend': minor
---
Added experimental flag for scaffolder to wait for running tasks to complete on shutdown
Enabling the `EXPERIMENTAL_gracefulShutdown` flag in the scaffolder config will make the
scaffolder block the shutdown process until all running tasks have completed. This is useful
when there is a need to ensure that all tasks have completed before the scaffolder is shut down.
Please note, that the `TaskWorker` `stop` method is now asynchronous and awaited for the
tasks to complete when the experimental flag is enabled.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-dynamic-feature-service': minor
---
**BREAKING**: removed the deprecated `dynamicPluginsFeatureDiscoveryServiceFactory`.
@@ -3,6 +3,8 @@ on:
push:
branches:
- master
paths:
- '.github/workflows/sync_version-packages.yml'
concurrency:
group: sync-version-packages
@@ -23,6 +25,10 @@ jobs:
fetch-depth: 20000
fetch-tags: true
token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
- name: Fetch tags # See https://github.com/actions/checkout/issues/2041
run: git fetch --tags
- name: Install Dependencies
run: yarn --immutable
- name: Create Release Pull Request
+6
View File
@@ -51,6 +51,8 @@ auth:
additionalScopes:
- 'read:jira-user'
- 'read:jira-work'
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
signIn:
resolvers:
# See https://backstage.io/docs/auth/atlassian/provider#resolvers for more resolvers
@@ -67,6 +69,10 @@ The Atlassian provider is a structure with the following configuration keys:
**NOTE:** The scopes `offline_access`, `read:jira-work`, and `read:jira-user` are provided by default.
### Optional
- `sessionDuration`: Lifespan of the user session.
### Resolvers
This provider includes several resolvers out of the box that you can use:
+3
View File
@@ -44,6 +44,8 @@ auth:
audience: ${AUTH_AUTH0_AUDIENCE}
connection: ${AUTH_AUTH0_CONNECTION}
connectionScope: ${AUTH_AUTH0_CONNECTION_SCOPE}
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
session:
secret: ${AUTH_SESSION_SECRET}
```
@@ -66,6 +68,7 @@ Auth0 requires a session, so you need to give the session a secret key.
- `audience`: The intended recipients of the token.
- `connection`: Social identity provider name. To check the available social connections, please visit [Auth0 Social Connections](https://marketplace.auth0.com/features/social-connections).
- `connectionScope`: Additional scopes in the interactive token request. It should always be used in combination with the `connection` parameter.
- `sessionDuration`: Lifespan of the user session.
### Resolvers
+6
View File
@@ -23,6 +23,8 @@ auth:
signer: 'arn:aws:elasticloadbalancing:us-east-2:123456789012:loadbalancer/app/my-load-balancer/1234567890123456'
# this is the region where your ALB instance resides
region: 'us-west-2'
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
signIn:
resolvers:
# See https://backstage.io/docs/auth/aws-alb/provider#resolvers for more resolvers
@@ -31,6 +33,10 @@ auth:
Ensure that you have set the signer correctly. It is also recommended that you restrict your target groups' security policy to only accept connections from that ALB.
### Optional
- `sessionDuration`: Lifespan of the user session.
### Resolvers
This provider includes several resolvers out of the box that you can use:
+6
View File
@@ -37,6 +37,8 @@ auth:
development:
clientId: ${AUTH_BITBUCKET_CLIENT_ID}
clientSecret: ${AUTH_BITBUCKET_CLIENT_SECRET}
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
signIn:
resolvers:
# See https://backstage.io/docs/auth/bitbucket/provider#resolvers for more resolvers
@@ -49,6 +51,10 @@ The Bitbucket provider is a structure with two configuration keys:
`b59241722e3c3b4816e2`
- `clientSecret`: The Secret tied to the generated Key.
### Optional
- `sessionDuration`: Lifespan of the user session.
### Resolvers
This provider includes several resolvers out of the box that you can use:
+6
View File
@@ -34,6 +34,8 @@ auth:
host: bitbucket.example.org
clientId: ${AUTH_BITBUCKET_SERVER_CLIENT_ID}
clientSecret: ${AUTH_BITBUCKET_SERVER_CLIENT_SECRET}
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
```
The Bitbucket Server provider is a structure with two configuration keys:
@@ -41,6 +43,10 @@ The Bitbucket Server provider is a structure with two configuration keys:
- `clientId`: The client ID that was generated by Bitbucket, e.g. `b0f868455c15dcdff5c5fb5d173ae684`.
- `clientSecret`: The client secret tied to the generated client ID.
### Optional
- `sessionDuration`: Lifespan of the user session.
### Resolvers
This provider includes several resolvers out of the box that you can use:
+6
View File
@@ -38,6 +38,8 @@ auth:
# You can customize the authorization cookie name, by default
# CF_Authorization is used
authorizationCookieName: <MY_CAUTHORIZATION_COOKIE_NAME>
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
# This picks what sign in resolver(s) you want to use.
signIn:
resolvers:
@@ -47,6 +49,10 @@ auth:
This config section must be in place for the provider to load at all.
### Optional
- `sessionDuration`: Lifespan of the user session.
### Resolvers
This provider includes several resolvers out of the box that you can use:
+3
View File
@@ -49,6 +49,8 @@ auth:
clientSecret: ${AUTH_GITHUB_CLIENT_SECRET}
## uncomment if using GitHub Enterprise
# enterpriseInstanceUrl: ${AUTH_GITHUB_ENTERPRISE_INSTANCE_URL}
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
signIn:
resolvers:
# See https://backstage.io/docs/auth/github/provider#resolvers for more resolvers
@@ -66,6 +68,7 @@ The GitHub provider is a structure with these configuration keys:
initiating an OAuth flow, e.g.,
`https://your-intermediate-service.com/handler`. Only needed if Backstage is
not the immediate receiver (e.g., one OAuth app for many backstage instances).
- `sessionDuration` (optional): Lifespan of the user session.
- `signIn`: The configuration for the sign-in process, including the **resolvers**
that should be used to match the user from the auth provider with the user
entity in the Backstage catalog (typically a single resolver is sufficient).
+3
View File
@@ -45,6 +45,8 @@ auth:
# audience: https://gitlab.company.com
## uncomment if using a custom redirect URI
# callbackUrl: https://${BASE_URL}/api/auth/gitlab/handler/frame
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
signIn:
resolvers:
# See https://backstage.io/docs/auth/gitlab/provider#resolvers for more resolvers
@@ -61,6 +63,7 @@ The GitLab provider is a structure with three configuration keys:
- `callbackUrl` (optional): The URL matching the Redirect URI registered when creating your GitLab OAuth App, e.g.
`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.
- `sessionDuration` (optional): Lifespan of the user session.
### Resolvers
+6
View File
@@ -42,6 +42,8 @@ auth:
development:
clientId: ${AUTH_GOOGLE_CLIENT_ID}
clientSecret: ${AUTH_GOOGLE_CLIENT_SECRET}
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
signIn:
resolvers:
# See https://backstage.io/docs/auth/google/provider#resolvers for more resolvers
@@ -54,6 +56,10 @@ The Google provider is a structure with two configuration keys:
`10023341500512-beui241gjwwkrdkr2eh7dprewj2pp1q.apps.googleusercontent.com`
- `clientSecret`: The client secret tied to the generated client ID.
### Optional
- `sessionDuration`: Lifespan of the user session.
### Resolvers
This provider includes several resolvers out of the box that you can use:
+1
View File
@@ -84,6 +84,7 @@ 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, to be requested in addition to the required ones.
- `skipUserProfile` (optional): If true, skips loading the user profile even if the `User.Read` scope is present. This is a performance optimization during login and can be used with resolvers that only needs the email address in `spec.profile.email` obtained when the `email` OAuth2 scope is present.
- `sessionDuration` (optional): Lifespan of the user session.
### Resolvers
+1
View File
@@ -263,6 +263,7 @@ check the App Registration you created:
basically the same thing.
- `prompt`: Recommended to use `auto` so the browser will request login to the IDP if the
user has no active session.
- `sessionDuration` (optional): Lifespan of the user session.
Note that for the time being, any change in this yaml file requires a restart of the app,
also you need to have the `session.secret` part to use OIDC (some other providers might
+3
View File
@@ -45,6 +45,8 @@ auth:
audience: ${AUTH_OKTA_DOMAIN}
authServerId: ${AUTH_OKTA_AUTH_SERVER_ID} # Optional
idp: ${AUTH_OKTA_IDP} # Optional
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # Optional: supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
# https://developer.okta.com/docs/reference/api/oidc/#scope-dependent-claims-not-always-returned
additionalScopes: ${AUTH_OKTA_ADDITIONAL_SCOPES} # Optional
signIn:
@@ -62,6 +64,7 @@ The values referenced are found on the Application page on your Okta site.
`https://company.okta.com`
- `authServerId`: The authorization server ID for the Application
- `idp`: The identity provider for the application, e.g. `0oaulob4BFVa4zQvt0g3`
- `sessionDuration`: Lifespan of the user session.
`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.
+6
View File
@@ -38,6 +38,8 @@ auth:
clientId: ${AUTH_ONELOGIN_CLIENT_ID}
clientSecret: ${AUTH_ONELOGIN_CLIENT_SECRET}
issuer: https://<company>.onelogin.com/oidc/2
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
signIn:
resolvers:
# See https://backstage.io/docs/auth/onelogin/provider#resolvers for more resolvers
@@ -51,6 +53,10 @@ found on the SSO tab** for the OneLogin Application:
- `clientSecret`: The client secret
- `issuer`: The issuer URL
### Optional
- `sessionDuration`: Lifespan of the user session.
### Resolvers
This provider includes several resolvers out of the box that you can use:
+6
View File
@@ -47,6 +47,8 @@ auth:
development:
clientId: ${APP_ID}
organizationId: ${ORG_ID}
## uncomment to set lifespan of user session
# sessionDuration: { hours: 24 } # supports `ms` library format (e.g. '24h', '2 days'), ISO duration, "human duration" as used in code
signIn:
resolvers:
# See https://backstage.io/docs/auth/vmware-cloud/provider#resolvers for more resolvers
@@ -68,6 +70,10 @@ key for signing session cookies set by Backstage.
:::
### Optional
- `sessionDuration`: Lifespan of the user session.
### Resolvers
This provider includes several resolvers out of the box that you can use:
+1 -1
View File
@@ -1,6 +1,6 @@
---
id: product
title: Product FAQ
title: Non-technical FAQ
description: Questions related to product and design.
---
+18 -2
View File
@@ -80,11 +80,13 @@ The default value is `false`.
#### Internationalization
To customize or translate the **Delete Pod** text, use the following approach:
To customize or translate text in some of the components, use the following approach:
```js
import { createTranslationMessages } from '@backstage/core-plugin-api/alpha';
import { kubernetesReactTranslationRef } from '@backstage/plugin-kubernetes-react/alpha';
import { kubernetesTranslationRef } from '@backstage/plugin-kubernetes/alpha';
import { kubernetesClusterTranslationRef } from '@backstage/plugin-kubernetes-cluster/alpha';
const app = createApp({
__experimentalTranslations: {
@@ -94,7 +96,21 @@ const app = createApp({
messages: {
"podDrawer.buttons.delete": 'Restart Pod'
}
})
}),
createTranslationMessages({
ref: kubernetesTranslationRef,
messages: {
'kubernetesContentPage.permissionAlert.title': 'Insufficient permissions',
'kubernetesContentPage.permissionAlert.message': 'You do not have permissions to view Kubernetes objects.',
},
}),
createTranslationMessages({
ref: kubernetesClusterTranslationRef,
messages: {
'kubernetesClusterContentPage.permissionAlert.title': 'Insufficient permissions',
'kubernetesClusterContentPage.permissionAlert.message': 'You do not have permissions to view Kubernetes objects.',
},
}),
]
},
...
+18
View File
@@ -0,0 +1,18 @@
---
id: permissions
title: Permissions
description: Configuring permissions for Kubernetes plugin
---
The Kubernetes plugin integrates with the permission framework. Administrators can define PermissionPolicies
to restrict access to the `/clusters`, `/services/:serviceId`, `/resources` and `/proxy` endpoints.
This feature assumes your Backstage instance has enabled the [permissions framework](https://backstage.io/docs/permissions/getting-started).
### Available permissions
| Name | Policy | Description |
| ------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
| kubernetes.clusters.read | read | Allows the user to read Kubernetes clusters information under `/clusters` |
| kubernetes.resources.read | read | Allows the user to read Kubernetes resources information under `/services/:serviceId` and `/resources` |
| kubernetes.proxy | | Allows the user to make arbitrary requests to the [REST API](https://kubernetes.io/docs/reference/using-api/api-concepts/) under `/proxy` |
+1 -32
View File
@@ -7,36 +7,5 @@ description: Guidelines for how to use the Backstage logos and icons
Guidelines for how to use the Backstage logo and icon can be found
[here](https://backstage.io/logo_assets/Backstage_Identity_Assets_Overview.pdf).
The assets below are all in `.svg` format. Other formats are available in the
[repository](https://github.com/backstage/backstage/tree/master/microsite/static/logo_assets).
## Backstage logo
<a href="https://backstage.io/logo_assets/svg/Logo_White.svg" aria-label='Link to Backstage White logo svg'>
<img src="https://backstage.io/logo_assets/svg/Logo_White.svg" alt='Backstage White logo' width="600" />
</a>
<a href="https://backstage.io/logo_assets/svg/Logo_Teal.svg" aria-label='Link to Backstage Teal logo svg'>
<img src="https://backstage.io/logo_assets/svg/Logo_Teal.svg" alt='Backstage Teal logo' width="600" />
</a>
<a href="https://backstage.io/logo_assets/svg/Logo_Black.svg" aria-label='Link to Backstage Black logo svg'>
<img src="https://backstage.io/logo_assets/svg/Logo_Black.svg" alt='Backstage Black logo' width="600" className="logoWhite" class="logoWhite" />
</a>
## Backstage icon
<div>
<a href="https://backstage.io/logo_assets/svg/Icon_White.svg" aria-label='Link to Backstage White icon'>
<img src="https://backstage.io/logo_assets/svg/Icon_White.svg" alt='Backstage White Icon' width="180" height="180" />
</a>
<a href="https://backstage.io/logo_assets/svg/Icon_Teal.svg" aria-label='Link to Backstage Teal icon'>
<img src="https://backstage.io/logo_assets/svg/Icon_Teal.svg" alt='Backstage Teal Icon' width="180" height="180" />
</a>
<a href="https://backstage.io/logo_assets/svg/Icon_Gradient.svg" aria-label='Link to Backstage Gradient icon'>
<img src="https://backstage.io/logo_assets/svg/Icon_Gradient.svg" alt='Backstage Gradient Icon' width="180" height="180" />
</a>
<a href="https://backstage.io/logo_assets/svg/Icon_Black.svg" aria-label='Link to Backstage Black icon'>
<img src="https://backstage.io/logo_assets/svg/Icon_Black.svg" alt='Backstage Black Icon' width="180" height="180" className="logoWhite" class="logoWhite" />
</a>
</div>
You can find the assets at https://github.com/cncf/artwork/tree/main/projects/backstage.
+1
View File
@@ -1,6 +1,7 @@
---
id: threat-model
title: Backstage Threat Model
sidebar_label: Security & Thread Model
description: A document describing the threat model for Backstage.
---
+4
View File
@@ -405,6 +405,10 @@ const config: Config = {
label: 'GitHub',
to: 'https://github.com/backstage/',
},
{
label: 'Assets',
to: 'https://github.com/cncf/artwork/tree/main/projects/backstage',
},
],
},
],
+35 -27
View File
@@ -5,14 +5,11 @@ module.exports = {
Overview: [
'overview/what-is-backstage',
'overview/architecture-overview',
'overview/roadmap',
'overview/vision',
'overview/background',
'overview/adopting',
'overview/versioning-policy',
'overview/vision',
'overview/roadmap',
'overview/threat-model',
'overview/support',
'overview/logos',
'overview/versioning-policy',
],
'Getting Started': [
'getting-started/index',
@@ -46,6 +43,7 @@ module.exports = {
'getting-started/create-a-component',
],
},
'overview/support',
'getting-started/keeping-backstage-updated',
],
'Core Features': [
@@ -494,36 +492,46 @@ module.exports = {
],
},
],
'Designing for Backstage': [
'dls/design',
'dls/component-design-guidelines',
'dls/contributing-to-storybook',
'dls/figma',
],
Tutorials: [
'tutorials/quickstart-app-plugin',
'tutorials/react-router-stable-migration',
'tutorials/react18-migration',
'tutorials/package-role-migration',
'tutorials/migrating-away-from-core',
'tutorials/configuring-plugin-databases',
'tutorials/switching-sqlite-postgres',
'tutorials/using-backstage-proxy-within-plugin',
'tutorials/yarn-migration',
'tutorials/migrate-to-mui5',
'tutorials/auth-service-migration',
'tutorials/enable-public-entry',
'tutorials/setup-opentelemetry',
{ 'Non-technical': ['overview/adopting'] },
{
Techical: [
'tutorials/quickstart-app-plugin',
'tutorials/configuring-plugin-databases',
'tutorials/switching-sqlite-postgres',
'tutorials/using-backstage-proxy-within-plugin',
'tutorials/enable-public-entry',
'tutorials/setup-opentelemetry',
'accessibility/index',
],
},
{
Migrations: [
'tutorials/react-router-stable-migration',
'tutorials/react18-migration',
'tutorials/package-role-migration',
'tutorials/migrating-away-from-core',
'tutorials/yarn-migration',
'tutorials/migrate-to-mui5',
'tutorials/auth-service-migration',
],
},
],
FAQ: ['faq/index', 'faq/product', 'faq/technical'],
Accessibility: ['accessibility/index'],
Contribute: [
'contribute/index',
'contribute/getting-involved',
'contribute/project-structure',
],
References: [
{
'Designing for Backstage': [
'dls/design',
'dls/component-design-guidelines',
'dls/contributing-to-storybook',
'dls/figma',
],
},
{
type: 'category',
label: 'Architecture Decision Records (ADRs)',
@@ -10,7 +10,7 @@
}
p {
text-align: justify;
text-align: left;
}
}
+13 -7
View File
@@ -33,14 +33,14 @@ const Community = () => {
},
{
content: 'Subscribe to the',
label: 'Community newsletter',
label: 'Community Newsletter',
link: 'https://info.backstage.spotify.com/newsletter_subscribe',
},
];
const officialInitiatives: ICollectionItem[] = [
{
title: 'Community sessions',
title: 'Community Sessions',
content:
'Maintainers and adopters meet monthly to share updates, demos, and ideas. You can find recorded session on our YouTube channel!',
link: 'https://github.com/backstage/community/tree/main/backstage-community-sessions#backstage-community-sessions',
@@ -63,6 +63,13 @@ const Community = () => {
link: 'https://training.linuxfoundation.org/training/introduction-to-backstage-developer-portals-made-easy-lfs142x/',
label: 'Learn more',
},
{
title: 'Certified Backstage Associate (CBA)',
content:
'Designed for IT engineers, developers, platform engineers, and other IT professionals, the CBA proves you have the skills and the mindset to work with Backstage.',
link: 'https://www.cncf.io/training/certification/cba/',
label: 'Learn more',
},
];
const partners: { name: string; url: string; logo: string }[] = [
@@ -125,11 +132,10 @@ const Community = () => {
>
Join the vibrant community around Backstage through social media
and different meetups. To ensure that you have a welcoming
environment, we follow the
environment, we follow the{' '}
<Link to="https://github.com/cncf/foundation/blob/master/code-of-conduct.md">
{' '}
CNCF Code of Conduct{' '}
</Link>
CNCF Code of Conduct
</Link>{' '}
in everything we do.
</ContentBlock>
@@ -159,7 +165,7 @@ const Community = () => {
<BannerSectionGrid
header={
<>
<h2 className="text--primary">Offical Backstage initiatives</h2>
<h2 className="text--primary">Offical Backstage Initiatives</h2>
<h1>Stay tuned to the latest developments</h1>
</>
+1 -1
View File
@@ -13,7 +13,7 @@
.banner {
p {
flex: 1;
text-align: justify;
text-align: left;
}
iframe {
-4
View File
@@ -20,16 +20,12 @@
"license": "Apache-2.0",
"exports": {
".": "./src/index.ts",
"./alpha": "./src/alpha.ts",
"./package.json": "./package.json"
},
"main": "src/index.ts",
"types": "src/index.ts",
"typesVersions": {
"*": {
"alpha": [
"src/alpha.ts"
],
"package.json": [
"package.json"
]
@@ -1,17 +0,0 @@
## API Report File for "@backstage/backend-app-api"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { FeatureDiscoveryService } from '@backstage/backend-plugin-api/alpha';
import { ServiceFactory } from '@backstage/backend-plugin-api';
// @alpha @deprecated (undocumented)
export const featureDiscoveryServiceFactory: ServiceFactory<
FeatureDiscoveryService,
'root',
'singleton'
>;
// (No @packageDocumentation comment for this package)
```
@@ -1,328 +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 {
startTestBackend,
mockServices,
createMockDirectory,
} from '@backstage/backend-test-utils';
import { featureDiscoveryServiceFactory } from './featureDiscoveryServiceFactory';
const mockDir = createMockDirectory();
process.argv[1] = mockDir.path;
const pluginApiPath = require.resolve('@backstage/backend-plugin-api');
describe('featureDiscoveryServiceFactory', () => {
beforeEach(() => {
mockDir.setContent({
'package.json': JSON.stringify({
name: 'example-app',
dependencies: {
'detected-plugin': '0.0.0',
'detected-module': '0.0.0',
'detected-plugin-with-alpha': '0.0.0',
'detected-library': '0.0.0',
},
}),
'node_modules/detected-plugin': {
'package.json': JSON.stringify({
name: 'detected-plugin',
main: 'index.js',
backstage: {
role: 'backend-plugin',
},
}),
'index.js': `
const { createBackendPlugin, coreServices } = require('${pluginApiPath}');
exports.default = createBackendPlugin({
pluginId: 'detected',
register(env) {
env.registerInit({
deps: { logger: coreServices.rootLogger },
async init({ logger }) {
logger.warn('detected-plugin');
},
});
},
});
`,
},
'node_modules/detected-module': {
'package.json': JSON.stringify({
name: 'detected-module',
main: 'index.js',
backstage: {
role: 'backend-plugin-module',
},
}),
'index.js': `
const { createBackendModule, coreServices } = require('${pluginApiPath}');
exports.default = createBackendModule({
pluginId: 'detected',
moduleId: 'derp',
register(env) {
env.registerInit({
deps: { logger: coreServices.rootLogger },
async init({ logger }) {
logger.warn('detected-module');
},
});
},
});
`,
},
'node_modules/detected-plugin-with-alpha': {
'package.json': JSON.stringify({
name: 'detected-plugin-with-alpha',
main: 'index.js',
exports: {
'.': {
default: 'index.js',
},
'./alpha': {
default: 'alpha.js',
},
'./package.json': './package.json',
},
backstage: {
role: 'backend-plugin',
},
}),
'index.js': `exports.default = undefined;`,
'alpha.js': `
const { createBackendPlugin, coreServices } = require('${pluginApiPath}');
exports.default = createBackendPlugin({
pluginId: 'detected-alpha',
register(env) {
env.registerInit({
deps: { logger: coreServices.rootLogger },
async init({ logger }) {
logger.warn('detected-plugin-with-alpha');
},
});
},
});
`,
},
'node_modules/detected-library': {
'package.json': JSON.stringify({
name: 'detected-library',
main: 'index.js',
backstage: {
role: 'node-library',
},
}),
'index.js': `
const { createServiceFactory, createServiceRef, coreServices } = require('${pluginApiPath}');
exports.default = createServiceFactory({
service: createServiceRef({ id: 'test', scope: 'root' }),
deps: { logger: coreServices.rootLogger },
factory({ logger }) {
logger.warn('detected-library');
return {};
},
});
`,
},
});
});
it('should detect plugin and module packages when "all" is specified', async () => {
const mock = mockServices.rootLogger.mock({ child: () => mock });
await startTestBackend({
features: [
mock.factory,
featureDiscoveryServiceFactory,
mockServices.rootConfig.factory({
data: { backend: { packages: 'all' } },
}),
],
});
expect(mock.warn).toHaveBeenCalledWith('detected-plugin');
expect(mock.warn).toHaveBeenCalledWith('detected-module');
expect(mock.warn).toHaveBeenCalledWith('detected-plugin-with-alpha');
expect(mock.warn).toHaveBeenCalledWith('detected-library');
});
it('detects only the packages that are listed as included', async () => {
const mock = mockServices.rootLogger.mock({ child: () => mock });
await startTestBackend({
features: [
mock.factory,
featureDiscoveryServiceFactory,
mockServices.rootConfig.factory({
data: {
backend: {
packages: {
include: [
'detected-plugin',
'detected-plugin-with-alpha',
'detected-library',
],
},
},
},
}),
],
});
expect(mock.warn).toHaveBeenCalledWith('detected-plugin');
expect(mock.warn).toHaveBeenCalledWith('detected-plugin-with-alpha');
expect(mock.warn).toHaveBeenCalledWith('detected-library');
expect(mock.warn).not.toHaveBeenCalledWith('detected-module');
});
it('does not detect packages when included is an empty list', async () => {
const mock = mockServices.rootLogger.mock({ child: () => mock });
await startTestBackend({
features: [
mock.factory,
featureDiscoveryServiceFactory,
mockServices.rootConfig.factory({
data: {
backend: {
packages: {
include: [],
},
},
},
}),
],
});
expect(mock.warn).not.toHaveBeenCalledWith('detected-plugin');
expect(mock.warn).not.toHaveBeenCalledWith('detected-plugin-with-alpha');
expect(mock.warn).not.toHaveBeenCalledWith('detected-module');
expect(mock.warn).not.toHaveBeenCalledWith('detected-library');
});
it('does not detect an excluded packages', async () => {
const mock = mockServices.rootLogger.mock({ child: () => mock });
await startTestBackend({
features: [
mock.factory,
featureDiscoveryServiceFactory,
mockServices.rootConfig.factory({
data: {
backend: {
packages: {
exclude: ['detected-plugin', 'detected-module'],
},
},
},
}),
],
});
expect(mock.warn).not.toHaveBeenCalledWith('detected-plugin');
expect(mock.warn).not.toHaveBeenCalledWith('detected-module');
expect(mock.warn).toHaveBeenCalledWith('detected-plugin-with-alpha');
expect(mock.warn).toHaveBeenCalledWith('detected-library');
});
it('does not excluded packages when it is an empty list', async () => {
const mock = mockServices.rootLogger.mock({ child: () => mock });
await startTestBackend({
features: [
mock.factory,
featureDiscoveryServiceFactory,
mockServices.rootConfig.factory({
data: {
backend: {
packages: {
exclude: [],
},
},
},
}),
],
});
expect(mock.warn).toHaveBeenCalledWith('detected-plugin');
expect(mock.warn).toHaveBeenCalledWith('detected-module');
expect(mock.warn).toHaveBeenCalledWith('detected-plugin-with-alpha');
expect(mock.warn).toHaveBeenCalledWith('detected-library');
});
it('does not detect packages that are included and excluded', async () => {
const mock = mockServices.rootLogger.mock({ child: () => mock });
await startTestBackend({
features: [
mock.factory,
featureDiscoveryServiceFactory,
mockServices.rootConfig.factory({
data: {
backend: {
packages: {
include: [
'detected-plugin',
'detected-module',
'detected-plugin-with-alpha',
],
exclude: ['detected-module'],
},
},
},
}),
],
});
expect(mock.warn).toHaveBeenCalledWith('detected-plugin');
expect(mock.warn).not.toHaveBeenCalledWith('detected-library');
expect(mock.warn).not.toHaveBeenCalledWith('detected-module');
expect(mock.warn).toHaveBeenCalledWith('detected-plugin-with-alpha');
});
it('does not detect any packages when "packages" is empty', async () => {
const mock = mockServices.rootLogger.mock({ child: () => mock });
await startTestBackend({
features: [
mock.factory,
featureDiscoveryServiceFactory,
mockServices.rootConfig.factory({
data: { backend: { packages: {} } },
}),
],
});
expect(mock.warn).not.toHaveBeenCalled();
});
it('does not detect any packages when "packages" is not present', async () => {
const mock = mockServices.rootLogger.mock({ child: () => mock });
await startTestBackend({
features: [
mock.factory,
featureDiscoveryServiceFactory,
mockServices.rootConfig.factory({
data: { backend: {} },
}),
],
});
expect(mock.warn).not.toHaveBeenCalled();
});
});
@@ -1,38 +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 {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { featureDiscoveryServiceRef } from '@backstage/backend-plugin-api/alpha';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { PackageDiscoveryService } from '../../../backend-defaults/src/PackageDiscoveryService';
/**
* @alpha
* @deprecated The `featureDiscoveryServiceFactory` is deprecated in favor of using {@link @backstage/backend-defaults#discoveryFeatureLoader} instead.
*/
export const featureDiscoveryServiceFactory = createServiceFactory({
service: featureDiscoveryServiceRef,
deps: {
config: coreServices.rootConfig,
logger: coreServices.rootLogger,
},
factory({ config, logger }) {
return new PackageDiscoveryService(config, logger);
},
});
@@ -37,7 +37,6 @@ import type { InternalServiceFactory } from '../../../backend-plugin-api/src/ser
import { ForwardedError, ConflictError, assertError } from '@backstage/errors';
import {
instanceMetadataServiceRef,
featureDiscoveryServiceRef,
BackendFeatureMeta,
} from '@backstage/backend-plugin-api/alpha';
import { DependencyGraph } from '../lib/DependencyGraph';
@@ -252,20 +251,6 @@ export class BackendInitializer {
this.#addFeature(await feature);
}
const featureDiscovery = await this.#serviceRegistry.get(
// TODO: Let's leave this in place and remove it once the deprecated service is removed. We can do that post-1.0 since it's alpha
featureDiscoveryServiceRef,
'root',
);
if (featureDiscovery) {
const { features } = await featureDiscovery.getBackendFeatures();
for (const feature of features) {
this.#addFeature(unwrapFeature(feature));
}
this.#serviceRegistry.checkForCircularDeps();
}
await this.#applyBackendFeatureLoaders(this.#registeredFeatureLoaders);
this.#serviceRegistry.add(
+4
View File
@@ -429,6 +429,10 @@ export interface Config {
* The instance connection name for the cloudsql instance, e.g. `project:region:instance`
*/
instance: string;
/**
* The ip address type to use for the connection. Defaults to 'PUBLIC'
*/
ipAddressType?: 'PUBLIC' | 'PRIVATE' | 'PSC';
}
| {
/**
@@ -22,7 +22,6 @@ import {
RootConfigService,
RootLoggerService,
} from '@backstage/backend-plugin-api';
import { FeatureDiscoveryService } from '@backstage/backend-plugin-api/alpha';
import { BackstagePackageJson } from '@backstage/cli-node';
const DETECTED_PACKAGE_ROLES = [
@@ -79,7 +78,7 @@ async function findClosestPackageDir(
}
/** @internal */
export class PackageDiscoveryService implements FeatureDiscoveryService {
export class PackageDiscoveryService {
constructor(
private readonly config: RootConfigService,
private readonly logger: RootLoggerService,
@@ -221,6 +221,63 @@ describe('postgres', () => {
});
});
it('passes default settings to cloud-sql-connector', async () => {
const { Connector } = jest.requireMock(
'@google-cloud/cloud-sql-connector',
) as jest.Mocked<typeof import('@google-cloud/cloud-sql-connector')>;
const mockStream = (): any => {};
Connector.prototype.getOptions.mockResolvedValue({ stream: mockStream });
await buildPgDatabaseConfig(
new ConfigReader({
client: 'pg',
connection: {
type: 'cloudsql',
user: 'ben@gke.com',
instance: 'project:region:instance',
port: 5423,
},
}),
{ connection: { database: 'other_db' } },
);
expect(Connector.prototype.getOptions).toHaveBeenCalledWith({
authType: 'IAM',
instanceConnectionName: 'project:region:instance',
ipType: 'PUBLIC',
});
});
it('passes ip settings to cloud-sql-connector', async () => {
const { Connector } = jest.requireMock(
'@google-cloud/cloud-sql-connector',
) as jest.Mocked<typeof import('@google-cloud/cloud-sql-connector')>;
const mockStream = (): any => {};
Connector.prototype.getOptions.mockResolvedValue({ stream: mockStream });
await buildPgDatabaseConfig(
new ConfigReader({
client: 'pg',
connection: {
type: 'cloudsql',
user: 'ben@gke.com',
instance: 'project:region:instance',
ipAddressType: 'PRIVATE',
port: 5423,
},
}),
{ connection: { database: 'other_db' } },
);
expect(Connector.prototype.getOptions).toHaveBeenCalledWith({
authType: 'IAM',
instanceConnectionName: 'project:region:instance',
ipType: 'PRIVATE',
});
});
it('throws an error when the connection type is not supported', async () => {
await expect(
buildPgDatabaseConfig(
@@ -107,7 +107,7 @@ export async function buildPgDatabaseConfig(
const connector = new CloudSqlConnector();
const clientOpts = await connector.getOptions({
instanceConnectionName: config.connection.instance,
ipType: IpAddressTypes.PUBLIC,
ipType: config.connection.ipAddressType ?? IpAddressTypes.PUBLIC,
authType: AuthTypes.IAM,
});
@@ -45,7 +45,6 @@
"test": "backstage-cli package test"
},
"dependencies": {
"@backstage/backend-app-api": "workspace:^",
"@backstage/backend-common": "^0.25.0",
"@backstage/backend-defaults": "workspace:^",
"@backstage/backend-plugin-api": "workspace:^",
@@ -74,6 +73,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
"@backstage/backend-app-api": "workspace:^",
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-app-backend": "workspace:^",
@@ -13,7 +13,6 @@ import { DiscoveryService } from '@backstage/backend-plugin-api';
import { EventBroker } from '@backstage/plugin-events-node';
import { EventsBackend } from '@backstage/plugin-events-backend';
import { EventsService } from '@backstage/plugin-events-node';
import { FeatureDiscoveryService } from '@backstage/backend-plugin-api/alpha';
import { HttpPostIngressOptions } from '@backstage/plugin-events-node';
import { IdentityApi } from '@backstage/plugin-auth-node';
import { IndexBuilder } from '@backstage/plugin-search-backend-node';
@@ -150,13 +149,6 @@ export interface DynamicPluginsFactoryOptions {
// @public @deprecated (undocumented)
export const dynamicPluginsFeatureDiscoveryLoader: BackendFeature;
// @public @deprecated (undocumented)
export const dynamicPluginsFeatureDiscoveryServiceFactory: ServiceFactory<
FeatureDiscoveryService,
'root',
'singleton'
>;
// @public
export const dynamicPluginsFeatureLoader: ((
options?: DynamicPluginsFeatureLoaderOptions,
@@ -32,7 +32,6 @@ export type {
export {
DynamicPluginManager,
dynamicPluginsFeatureDiscoveryServiceFactory,
dynamicPluginsServiceFactory,
dynamicPluginsServiceFactoryWithOptions,
dynamicPluginsServiceRef,
@@ -37,10 +37,6 @@ import {
import { PackageRole, PackageRoles } from '@backstage/cli-node';
import { findPaths } from '@backstage/cli-common';
import * as fs from 'fs';
import {
FeatureDiscoveryService,
featureDiscoveryServiceRef,
} from '@backstage/backend-plugin-api/alpha';
/**
* @public
@@ -307,55 +303,27 @@ export const dynamicPluginsServiceFactory = Object.assign(
dynamicPluginsServiceFactoryWithOptions(),
);
class DynamicPluginsEnabledFeatureDiscoveryService
implements FeatureDiscoveryService
{
constructor(
private readonly dynamicPlugins: DynamicPluginProvider,
private readonly featureDiscoveryService?: FeatureDiscoveryService,
) {}
class DynamicPluginsEnabledFeatureDiscoveryService {
constructor(private readonly dynamicPlugins: DynamicPluginProvider) {}
async getBackendFeatures(): Promise<{ features: Array<BackendFeature> }> {
const staticFeatures =
(await this.featureDiscoveryService?.getBackendFeatures())?.features ??
[];
return {
features: [
...this.dynamicPlugins
.backendPlugins()
.flatMap((plugin): BackendFeature[] => {
if (plugin.installer?.kind === 'new') {
const installed = plugin.installer.install();
if (Array.isArray(installed)) {
return installed;
}
return [installed];
features: this.dynamicPlugins
.backendPlugins()
.flatMap((plugin): BackendFeature[] => {
if (plugin.installer?.kind === 'new') {
const installed = plugin.installer.install();
if (Array.isArray(installed)) {
return installed;
}
return [];
}),
...staticFeatures,
],
return [installed];
}
return [];
}),
};
}
}
/**
* @public
* @deprecated Use {@link dynamicPluginsFeatureLoader} instead, which gathers all services and features required for dynamic plugins.
*/
export const dynamicPluginsFeatureDiscoveryServiceFactory =
createServiceFactory({
service: featureDiscoveryServiceRef,
deps: {
config: coreServices.rootConfig,
dynamicPlugins: dynamicPluginsServiceRef,
},
factory({ dynamicPlugins }) {
return new DynamicPluginsEnabledFeatureDiscoveryService(dynamicPlugins);
},
});
/**
* @public
* @deprecated Use {@link dynamicPluginsFeatureLoader} instead, which gathers all services and features required for dynamic plugins.
@@ -3,7 +3,6 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { BackendFeature } from '@backstage/backend-plugin-api';
import { ServiceRef } from '@backstage/backend-plugin-api';
// @alpha (undocumented)
@@ -18,21 +17,6 @@ export type BackendFeatureMeta =
moduleId: string;
};
// @alpha (undocumented)
export interface FeatureDiscoveryService {
// (undocumented)
getBackendFeatures(): Promise<{
features: Array<BackendFeature>;
}>;
}
// @alpha @deprecated
export const featureDiscoveryServiceRef: ServiceRef<
FeatureDiscoveryService,
'root',
'singleton'
>;
// @alpha (undocumented)
export interface InstanceMetadataService {
// (undocumented)
+1 -20
View File
@@ -14,26 +14,7 @@
* limitations under the License.
*/
import {
BackendFeature,
createServiceRef,
} from '@backstage/backend-plugin-api';
/** @alpha */
export interface FeatureDiscoveryService {
getBackendFeatures(): Promise<{ features: Array<BackendFeature> }>;
}
/**
* An optional service that can be used to dynamically load in additional BackendFeatures at runtime.
* @alpha
* @deprecated The `featureDiscoveryServiceRef` is deprecated in favor of using {@link @backstage/backend-defaults#discoveryFeatureLoader} instead.
*/
export const featureDiscoveryServiceRef =
createServiceRef<FeatureDiscoveryService>({
id: 'core.featureDiscovery',
scope: 'root',
});
import { createServiceRef } from '@backstage/backend-plugin-api';
/**
* EXPERIMENTAL: Instance metadata service.
+12
View File
@@ -12,6 +12,9 @@ export type BackstagePackage = {
packageJson: BackstagePackageJson;
};
// @public (undocumented)
export type BackstagePackageFeatureType = (typeof packageFeatureType)[number];
// @public
export interface BackstagePackageJson {
// (undocumented)
@@ -22,6 +25,7 @@ export interface BackstagePackageJson {
pluginId?: string | null;
pluginPackage?: string;
pluginPackages?: string[];
features?: Record<string, BackstagePackageFeatureType>;
};
// (undocumented)
bundled?: boolean;
@@ -112,6 +116,14 @@ export type LockfileDiffEntry = {
range: string;
};
// @public
export const packageFeatureType: readonly [
'@backstage/BackendFeature',
'@backstage/BackstagePlugin',
'@backstage/FrontendPlugin',
'@backstage/FrontendModule',
];
// @public
export class PackageGraph extends Map<string, PackageGraphNode> {
collectPackageNames(
@@ -22,6 +22,24 @@ import { GitUtils } from '../git';
import { Lockfile } from './Lockfile';
import { JsonValue } from '@backstage/types';
/**
* A list of the feature types we want to extract from the project
* and include in the metadata
*
* @public
*/
export const packageFeatureType = [
'@backstage/BackendFeature',
'@backstage/BackstagePlugin',
'@backstage/FrontendPlugin',
'@backstage/FrontendModule',
] as const;
/**
* @public
*/
export type BackstagePackageFeatureType = (typeof packageFeatureType)[number];
/**
* Known fields in Backstage package.json files.
*
@@ -72,6 +90,11 @@ export interface BackstagePackageJson {
* All packages that are part of the plugin. Must always and only be set for plugin packages and plugin library packages.
*/
pluginPackages?: string[];
/**
* The feature types exported from the package, indexed by path.
*/
features?: Record<string, BackstagePackageFeatureType>;
};
exports?: JsonValue;
+2
View File
@@ -16,10 +16,12 @@
export { isMonoRepo } from './isMonoRepo';
export {
packageFeatureType,
PackageGraph,
type PackageGraphNode,
type BackstagePackage,
type BackstagePackageJson,
type BackstagePackageFeatureType,
} from './PackageGraph';
export {
Lockfile,
@@ -14,9 +14,8 @@
* limitations under the License.
*/
import { PackageRole } from '@backstage/cli-node';
import { PackageRole, BackstagePackageFeatureType } from '@backstage/cli-node';
import { Project } from 'ts-morph';
import { BackstagePackageFeatureType } from '../typeDistProject';
const mockEntryPoint = 'dist/index.d.ts';
@@ -188,7 +188,16 @@ async function rewriteEntryPoints(
// This ensures that the `backstage` field is at the top of the
// `exports` field in the package.json because order is important.
// https://nodejs.org/docs/latest-v20.x/api/packages.html#conditional-exports
//
// Adding this to the `exports` field in the package.json is to temporarily
// support any existing behavior that relies on this, however not all packages
// have exports field in their package.json.
exp = { backstage: defaultFeatureType, ...exp };
// Add the default feature type to the backstage metadata in the package.json
pkg.backstage = pkg.backstage ?? {};
pkg.backstage.features = pkg.backstage.features ?? {};
pkg.backstage.features[entryPoint.mount] = defaultFeatureType;
}
}
+2 -5
View File
@@ -14,12 +14,9 @@
* limitations under the License.
*/
import { PackageRole } from '@backstage/cli-node';
import { PackageRole, BackstagePackageFeatureType } from '@backstage/cli-node';
import createFeatureEnvironment from './__testUtils__/createFeatureEnvironment';
import {
getEntryPointDefaultFeatureType,
BackstagePackageFeatureType,
} from './typeDistProject';
import { getEntryPointDefaultFeatureType } from './typeDistProject';
describe('typeDistProject', () => {
describe('for package role', () => {
+6 -13
View File
@@ -13,7 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { PackageRole } from '@backstage/cli-node';
import {
BackstagePackageFeatureType,
packageFeatureType,
PackageRole,
} from '@backstage/cli-node';
import { resolve as resolvePath } from 'path';
import { Project, SourceFile, SyntaxKind, ts, Type } from 'ts-morph';
import { paths } from './paths';
@@ -35,17 +39,6 @@ const targetPackageRoles: PackageRole[] = [
'node-library',
];
// A list of the feature types we want to extract from the project
// and include in the metadata
const targetFeatureTypes = [
'@backstage/BackendFeature',
'@backstage/BackstagePlugin',
'@backstage/FrontendPlugin',
'@backstage/FrontendModule',
] as const;
export type BackstagePackageFeatureType = (typeof targetFeatureTypes)[number];
export const getEntryPointDefaultFeatureType = (
role: PackageRole,
packageDir: string,
@@ -146,6 +139,6 @@ function isTargetFeatureType(
type: string | BackstagePackageFeatureType,
): type is BackstagePackageFeatureType {
return (
!!type && targetFeatureTypes.includes(type as BackstagePackageFeatureType)
!!type && packageFeatureType.includes(type as BackstagePackageFeatureType)
);
}
+1 -2
View File
@@ -59,6 +59,7 @@
"@backstage/errors": "workspace:^",
"@backstage/theme": "workspace:^",
"@backstage/version-bridge": "workspace:^",
"@dagrejs/dagre": "^1.1.4",
"@date-io/core": "^1.3.13",
"@material-table/core": "^3.1.0",
"@material-ui/core": "^4.12.2",
@@ -72,7 +73,6 @@
"d3-selection": "^3.0.0",
"d3-shape": "^3.0.0",
"d3-zoom": "^3.0.0",
"dagre": "^0.8.5",
"js-yaml": "^4.1.0",
"linkify-react": "4.1.3",
"linkifyjs": "4.1.3",
@@ -106,7 +106,6 @@
"@types/d3-selection": "^3.0.1",
"@types/d3-shape": "^3.0.1",
"@types/d3-zoom": "^3.0.1",
"@types/dagre": "^0.7.44",
"@types/google-protobuf": "^3.7.2",
"@types/react": "^18.0.0",
"@types/react-helmet": "^6.1.0",
@@ -18,7 +18,7 @@ import React from 'react';
import * as d3Zoom from 'd3-zoom';
import * as d3Selection from 'd3-selection';
import useTheme from '@material-ui/core/styles/useTheme';
import dagre from 'dagre';
import dagre from '@dagrejs/dagre';
import debounce from 'lodash/debounce';
import { DependencyGraphTypes as Types } from './types';
import { Node } from './Node';
@@ -21,7 +21,7 @@ import makeStyles from '@material-ui/core/styles/makeStyles';
import { DependencyGraphTypes as Types } from './types';
import { ARROW_MARKER_ID, EDGE_TEST_ID, LABEL_TEST_ID } from './constants';
import { DefaultLabel } from './DefaultLabel';
import dagre from 'dagre';
import dagre from '@dagrejs/dagre';
/* Based on: https://github.com/dagrejs/dagre/wiki#configuring-the-layout */
export type EdgeProperties = {
@@ -15,7 +15,7 @@
*/
import React from 'react';
import dagre from 'dagre';
import dagre from '@dagrejs/dagre';
import { render } from '@testing-library/react';
import { Node } from './Node';
import { DependencyGraphTypes as Types } from './types';
@@ -19,7 +19,7 @@ import makeStyles from '@material-ui/core/styles/makeStyles';
import { DefaultNode } from './DefaultNode';
import { DependencyGraphTypes as Types } from './types';
import { NODE_TEST_ID } from './constants';
import dagre from 'dagre';
import dagre from '@dagrejs/dagre';
/** @public */
export type DependencyGraphNodeClassKey = 'node';
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -38,6 +40,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -45,6 +45,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"supertest": "^7.0.0"
},
"configSchema": "config.d.ts"
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -30,6 +32,7 @@ export interface Config {
audience?: string;
connection?: string;
connectionScope?: string;
sessionDuration?: HumanDuration | string;
};
};
};
@@ -45,6 +45,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"@types/passport-auth0": "^1.0.5",
"@types/passport-oauth2": "^1.4.15",
"supertest": "^7.0.0"
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -48,6 +50,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -48,6 +48,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/types": "workspace:^",
"express": "^4.18.2",
"msw": "^2.0.8"
}
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -36,6 +38,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -45,6 +45,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"supertest": "^7.0.0"
},
"configSchema": "config.d.ts"
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -27,6 +29,7 @@ export interface Config {
clientSecret: string;
host: string;
callbackUrl?: string;
sessionDuration?: HumanDuration | string;
};
};
};
@@ -44,6 +44,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"@types/passport-oauth2": "^1.4.15",
"supertest": "^7.0.0"
},
@@ -43,6 +43,7 @@ export interface Config {
* The backstage token expiration.
*/
backstageTokenExpiration?: HumanDuration | string;
sessionDuration?: HumanDuration | string;
};
};
}
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -43,6 +45,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -38,6 +40,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -43,6 +43,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"supertest": "^7.0.0"
},
"configSchema": "config.d.ts"
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -38,6 +40,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -45,6 +45,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"supertest": "^7.0.0"
},
"configSchema": "config.d.ts"
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
/** Configuration options for the auth plugin */
auth?: {
@@ -38,6 +40,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -47,6 +47,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"@types/passport-google-oauth20": "^2.0.3",
"supertest": "^7.0.0"
},
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -40,6 +42,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -46,6 +46,7 @@
"@backstage/cli": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"@types/passport-microsoft": "^1.0.0",
"msw": "^1.0.0",
"supertest": "^7.0.0"
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -42,6 +44,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -44,6 +44,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"supertest": "^7.0.0"
},
"configSchema": "config.d.ts"
+3
View File
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -40,6 +42,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -46,6 +46,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/types": "workspace:^",
"cookie-parser": "^1.4.6",
"express-promise-router": "^4.1.1",
"express-session": "^1.17.3",
+3
View File
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -40,6 +42,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -45,6 +45,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"supertest": "^7.0.0"
},
"configSchema": "config.d.ts"
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -37,6 +39,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -45,6 +45,7 @@
"@backstage/backend-test-utils": "workspace:^",
"@backstage/cli": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"supertest": "^7.0.0"
},
"configSchema": "config.d.ts"
@@ -36,6 +36,7 @@
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/plugin-auth-node": "workspace:^",
"@backstage/types": "workspace:^",
"luxon": "^3.4.3",
"openid-client": "^5.4.3"
},
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
/** Configuration options for the auth plugin */
auth?: {
@@ -27,6 +29,7 @@ export interface Config {
*/
clientSecret: string;
scope?: string;
sessionDuration?: HumanDuration | string;
};
};
};
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { HumanDuration } from '@backstage/types';
export interface Config {
auth?: {
providers?: {
@@ -34,6 +36,7 @@ export interface Config {
| { resolver: 'emailMatchingUserEntityProfileEmail' }
>;
};
sessionDuration?: HumanDuration | string;
};
};
};
@@ -47,6 +47,7 @@
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/plugin-auth-backend": "workspace:^",
"@backstage/types": "workspace:^",
"msw": "^2.0.8",
"supertest": "^7.0.0"
}
@@ -16,6 +16,7 @@
import { CookieOptions, Request, Response } from 'express';
import { CookieConfigurer } from '../types';
import { HumanDuration, durationToMilliseconds } from '@backstage/types';
const THOUSAND_DAYS_MS = 1000 * 24 * 60 * 60 * 1000;
const TEN_MINUTES_MS = 600 * 1000;
@@ -55,6 +56,7 @@ export class OAuthCookieManager {
private readonly nonceCookie: string;
private readonly refreshTokenCookie: string;
private readonly grantedScopeCookie: string;
private readonly maxAge: number;
constructor(
private readonly options: {
@@ -63,6 +65,7 @@ export class OAuthCookieManager {
baseUrl: string;
callbackUrl: string;
cookieConfigurer?: CookieConfigurer;
sessionDuration?: HumanDuration;
},
) {
this.cookieConfigurer = options.cookieConfigurer ?? defaultCookieConfigurer;
@@ -70,6 +73,9 @@ export class OAuthCookieManager {
this.nonceCookie = `${options.providerId}-nonce`;
this.refreshTokenCookie = `${options.providerId}-refresh-token`;
this.grantedScopeCookie = `${options.providerId}-granted-scope`;
this.maxAge = options.sessionDuration
? durationToMilliseconds(options.sessionDuration)
: THOUSAND_DAYS_MS;
}
private getConfig(origin?: string, pathSuffix: string = '') {
@@ -103,7 +109,7 @@ export class OAuthCookieManager {
res,
this.refreshTokenCookie,
refreshToken,
THOUSAND_DAYS_MS,
this.maxAge,
origin,
);
}
@@ -117,13 +123,7 @@ export class OAuthCookieManager {
}
setGrantedScopes(res: Response, scope: string, origin?: string): void {
this.setCookie(
res,
this.grantedScopeCookie,
scope,
THOUSAND_DAYS_MS,
origin,
);
this.setCookie(res, this.grantedScopeCookie, scope, this.maxAge, origin);
}
getNonce(req: Request): string | undefined {

Some files were not shown because too many files have changed in this diff Show More