Merge branch 'backstage:master' into master
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-github-actions': patch
|
||||
---
|
||||
|
||||
Show empty state only when workflow API call has completed
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/app-defaults': patch
|
||||
'@backstage/core-app-api': patch
|
||||
'@backstage/core-plugin-api': patch
|
||||
---
|
||||
|
||||
Add `FetchApi` and related `fetchApiRef` which implement fetch, with an added Backstage token header when available.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
Fixed bug in backend-common to allow passing of remote option in order to enable passing remote url in --config option. The remote option should be passed along with reloadIntervalSeconds from packages/backend/src/index.ts (Updated the file as well)
|
||||
|
||||
These changes are needed in `packages/backend/src/index.ts` if remote URLs are desired to be passed in --config option and read and watch remote files for config.
|
||||
|
||||
```diff
|
||||
@@ -86,7 +86,11 @@ async function main() {
|
||||
const config = await loadBackendConfig({
|
||||
argv: process.argv,
|
||||
logger,
|
||||
+ remote: {
|
||||
+ reloadIntervalSeconds: 60 * 10 // Check remote config changes every 10 minutes. Change to your desired interval in seconds
|
||||
+ }
|
||||
});
|
||||
+
|
||||
const createEnv = makeCreateEnv(config);
|
||||
|
||||
const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck'));
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/config-loader': patch
|
||||
---
|
||||
|
||||
In case remote.reloadIntervalSeconds is passed, it must be a valid positive value
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-user-settings': patch
|
||||
---
|
||||
|
||||
Fix undefined identity bug in UserSettingsProfileCard caused by using deprecated methods of the IdentityApi
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-react': patch
|
||||
---
|
||||
|
||||
added useOwnedEntities hook to get the list of entities of the logged-in user
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
Fixed bug in OwnershipCard component where text wasn't correctly pluralized
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder-backend-module-rails': patch
|
||||
---
|
||||
|
||||
Add new options to rails new (force and skipTests)
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-common': minor
|
||||
---
|
||||
|
||||
Create catalog-common and add catalog permissions.
|
||||
@@ -0,0 +1,25 @@
|
||||
---
|
||||
'@backstage/plugin-catalog': patch
|
||||
---
|
||||
|
||||
Deprecated the `CatalogClientWrapper` class.
|
||||
|
||||
The default implementation of `catalogApiRef` that this plugin exposes, is now powered by the new `fetchApiRef`. The default implementation of _that_ API, in turn, has the ability to inject the user's Backstage token in requests in a similar manner to what the deprecated `CatalogClientWrapper` used to do. The latter has therefore been taken out of the default catalog API implementation.
|
||||
|
||||
If you use a custom `fetchApiRef` implementation that does NOT issue tokens, or use a custom `catalogApiRef` implementation which does NOT use the default `fetchApiRef`, you can still for some time wrap your catalog API in this class to get back the old behavior:
|
||||
|
||||
```ts
|
||||
// Add this to your packages/app/src/plugins.ts if you want to get back the old
|
||||
// catalog client behavior:
|
||||
createApiFactory({
|
||||
api: catalogApiRef,
|
||||
deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },
|
||||
factory: ({ discoveryApi, identityApi }) =>
|
||||
new CatalogClientWrapper({
|
||||
client: new CatalogClient({ discoveryApi }),
|
||||
identityApi,
|
||||
}),
|
||||
}),
|
||||
```
|
||||
|
||||
But do consider migrating to making use of the `fetchApiRef` as soon as convenient, since the wrapper class will be removed in a future release.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/catalog-client': patch
|
||||
---
|
||||
|
||||
Add the ability to supply a custom `fetchApi`. In the default frontend app setup, this will use the `fetchApiRef` implementation.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/test-utils': minor
|
||||
---
|
||||
|
||||
Removed deprecated `Keyboard` class which has been superseded by `@testing-library/user-event#userEvent`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/config-loader': minor
|
||||
---
|
||||
|
||||
Removed deprecated option `configPaths` as it has been superseded by `configTargets`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Added OwnedEntityPicker field which displays Owned Entities in options
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-auth-backend': patch
|
||||
---
|
||||
|
||||
Fixed the fallback identity population to correctly generate an entity reference for `userEntityRef` if no token is provided.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/catalog-client': patch
|
||||
---
|
||||
|
||||
Add support for passing paging parameters to the getEntities call of the catalog client
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend': minor
|
||||
---
|
||||
|
||||
Search result location filtering
|
||||
|
||||
This change introduces a filter for search results based on their location protocol. The intention is to filter out unsafe or
|
||||
malicious values before they can be consumed by the frontend. By default locations must be http/https URLs (or paths).
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
In @backstage/plugin-scaffolder - When user will have one option available in hostUrl or owner - autoselect and select component should be readonly.
|
||||
|
||||
in @backstage/core-components - Select component has extended API with few more props: native : boolean, disabled: boolean. native - if set to true - Select component will use native browser select picker (not rendered by Material UI lib ).
|
||||
disabled - if set to true - action on component will not be possible.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-jenkins-backend': patch
|
||||
---
|
||||
|
||||
feature: add crumbIssuer option to Jenkins (optional) configuration, improve the UI to show a notification after executing the action re-build
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-jenkins': patch
|
||||
---
|
||||
|
||||
feature: add crumbIssuer option to Jenkins (optional) configuration, improve the UI to show a notification after executing the action re-build
|
||||
@@ -1,14 +0,0 @@
|
||||
---
|
||||
'@backstage/plugin-apache-airflow': minor
|
||||
---
|
||||
|
||||
Introduces a new plugin for the Apache Airflow workflow management platform.
|
||||
This implementation has been tested with the Apache Airflow v2 API,
|
||||
authenticating with basic authentication through the Backstage proxy plugin.
|
||||
|
||||
Supported functionality includes:
|
||||
|
||||
- Information card of version information of the Airflow instance
|
||||
- Information card of instance health for the meta-database and scheduler
|
||||
- Table of DAGs with meta information and status, along with a link to view
|
||||
details in the Airflow UI
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-plugin-api': minor
|
||||
---
|
||||
|
||||
Removed deprecated types `ApiRefType` and `ApiRefsToTypes`
|
||||
@@ -0,0 +1,11 @@
|
||||
catalog:
|
||||
- plugins/catalog/**/*
|
||||
- plugins/catalog-*/**/*
|
||||
- packages/catalog-*/**/*
|
||||
scaffolder:
|
||||
- plugins/scaffolder/**/*
|
||||
- plugins/scaffolder-*/**/*
|
||||
search:
|
||||
- plugins/search/**/*
|
||||
- plugins/search-*/**/*
|
||||
- packages/search-*/**/*
|
||||
@@ -12,6 +12,7 @@ Atlassian
|
||||
automations
|
||||
autoscaling
|
||||
Autoscaling
|
||||
autoselect
|
||||
Avro
|
||||
backrub
|
||||
Bigtable
|
||||
@@ -53,6 +54,7 @@ configs
|
||||
const
|
||||
cookiecutter
|
||||
cron
|
||||
cronjobs
|
||||
css
|
||||
Datadog
|
||||
dataflow
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
name: Create Changeset PR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install Dependencies
|
||||
run: yarn --frozen-lockfile
|
||||
- name: Create Release Pull Request
|
||||
|
||||
@@ -11,7 +11,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Fossa
|
||||
run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash"
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
name: 'Pull Request Labeler'
|
||||
on:
|
||||
- pull_request_target
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@v3
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
sync-labels: true
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
recent activity from the author. It will be closed if no further activity occurs.
|
||||
If you are the author and the PR has been closed, feel free to re-open the PR and continue the contribution!
|
||||
days-before-pr-stale: 7
|
||||
days-before-pr-close: 3
|
||||
days-before-pr-close: 5
|
||||
exempt-pr-labels: reviewer-approved,awaiting-review
|
||||
stale-pr-label: stale
|
||||
operations-per-run: 100
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
# [Backstage](https://backstage.io)
|
||||
|
||||
> 🎄 The maintainers will be taking a break over the holidays from week beginning 20th Dec. The Repository and Discord may be quieter than usual. We will be back next year, rested and restored, on Jan. 3. 🎄
|
||||
|
||||
> 🎅 Happy holidays and a Happy New Year to all, and especially those that have made this year special for Backstage! 🎅
|
||||
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://www.cncf.io/projects)
|
||||
[](https://github.com/backstage/backstage/actions?query=workflow%3A%22Main+Master+Build%22)
|
||||
|
||||
@@ -61,3 +61,125 @@ breaking change to make `theme` optional. This means that if you currently
|
||||
construct the themes that you pass on to `createApp` using `AppTheme` as an
|
||||
intermediate type, you will need to work around this in some way, for example by
|
||||
passing the themes to `createApp` more directly.
|
||||
|
||||
### Generic Auth API Refs
|
||||
|
||||
`Released 2021-12-16 in @backstage/core-plugin-api v0.3.1`
|
||||
|
||||
There are four auth Utility API references in `@backstage/core-plugin-api` that
|
||||
were too generic to be useful. The APIs in question are `auth0AuthApiRef`,
|
||||
`oauth2ApiRef`, `oidcAuthApiRef`, and `samlAuthApiRef`. The issue with these
|
||||
APIs was that they had no actual contract of what the backing auth provider was.
|
||||
This made it more or less impossible to use these providers in open source
|
||||
plugins in any meaningful way. We also did not want to keep these Utility API
|
||||
references around just as helpers either, instead opting to remove them and let
|
||||
integrators define their own APIs that are more specific to their auth provider.
|
||||
This is also falls in line with a long-term goal to unify all auth providers to
|
||||
not have separate frontend implementations.
|
||||
|
||||
If you're currently using one of these API references for either Sign-In or
|
||||
access delegation within an app, there are a couple of steps you need to take to
|
||||
migrate to your own custom API.
|
||||
|
||||
First, you'll need to define a new Utility API reference. If you're only using
|
||||
the API for sign-in, you can put the definition in `packages/app/src/apis.ts`.
|
||||
However, if you need to access your auth API inside plugins you you'll need to
|
||||
export it from a common package. If you don't already have one we recommended
|
||||
creating `@internal/apis` and from there export the API reference.
|
||||
|
||||
```ts
|
||||
// `ProfileInfoApi & BackstageIdentityApi & SessionApi` are required for sign-in
|
||||
// Include `OAuthApi & OpenIdConnectApi` only if applicable
|
||||
export const acmeAuthApiRef: ApiRef<
|
||||
OAuthApi &
|
||||
OpenIdConnectApi &
|
||||
ProfileInfoApi &
|
||||
BackstageIdentityApi &
|
||||
SessionApi
|
||||
> = createApiRef({
|
||||
id: 'internal.auth.acme',
|
||||
});
|
||||
```
|
||||
|
||||
Next you'll want to wire up the API inside `packages/app/src/apis.ts`, which
|
||||
varies depending on which API you're replacing. If you for example are replacing
|
||||
the `oauth2ApiRef`, the factory might look like this:
|
||||
|
||||
```ts
|
||||
// oauth2
|
||||
createApiFactory({
|
||||
api: acmeAuthApiRef,
|
||||
deps: {
|
||||
discoveryApi: discoveryApiRef,
|
||||
oauthRequestApi: oauthRequestApiRef,
|
||||
configApi: configApiRef,
|
||||
},
|
||||
factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
|
||||
OAuth2.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
Provider specific factory implementations, copy the code you need into the
|
||||
factory method depending on which apiRef you previously used.
|
||||
|
||||
```ts
|
||||
// samlAuthApiRef
|
||||
SamlAuth.create({
|
||||
discoveryApi,
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
});
|
||||
|
||||
// oidcAuthApiRef
|
||||
OAuth2.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider: {
|
||||
id: 'oidc',
|
||||
title: 'Your Identity Provider',
|
||||
icon: () => null,
|
||||
},
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
});
|
||||
|
||||
// auth0AuthApiRef
|
||||
OAuth2.create({
|
||||
discoveryApi,
|
||||
oauthRequestApi,
|
||||
provider: {
|
||||
id: 'auth0',
|
||||
title: 'Auth0',
|
||||
icon: () => null,
|
||||
},
|
||||
defaultScopes: ['openid', 'email', 'profile'],
|
||||
environment: configApi.getOptionalString('auth.environment'),
|
||||
});
|
||||
```
|
||||
|
||||
Finally, for the provider to show up in your settings menu, you also need to
|
||||
update the settings route in `packages/app/src/App.tsx` to pass the
|
||||
`acmeAuthApiRef` to the `UserSettingsPage`. This replaces all existing provider
|
||||
items, so you might want to add back any of the default ones that you are using
|
||||
from the
|
||||
[DefaultProviderSettings](https://github.com/backstage/backstage/blob/a3ec122170e0205fd3f9c307b98b1c5e4f55bf5f/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx#L35).
|
||||
|
||||
```tsx
|
||||
<Route
|
||||
path="/settings"
|
||||
element={
|
||||
<UserSettingsPage
|
||||
providerSettings={
|
||||
<ProviderSettingsItem
|
||||
title="ACME"
|
||||
description="Provides sign-in via ACME"
|
||||
apiRef={acmeAuthApiRef}
|
||||
icon={Star}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
```
|
||||
|
||||
@@ -48,6 +48,6 @@ The Microsoft provider is a structure with three configuration keys:
|
||||
|
||||
## Adding the provider to the Backstage frontend
|
||||
|
||||
To add the provider to the frontend, add the `microsoftAuthApi` reference and
|
||||
To add the provider to the frontend, add the `microsoftAuthApiRef` reference and
|
||||
`SignInPage` component as shown in
|
||||
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
|
||||
|
||||
@@ -67,7 +67,7 @@ production build.
|
||||
|
||||
## Configuration Files
|
||||
|
||||
It is possible to have multiple configuration files (bundled and/or remote),
|
||||
It is possible to have multiple configuration files (bundled and/or remote\*),
|
||||
both to support different environments, but also to define configuration that is
|
||||
local to specific packages. The configuration files to load are selected using a
|
||||
`--config <local-path|url>` flag, and it is possible to load any number of
|
||||
@@ -77,6 +77,9 @@ root when running the backend, you would use `--config ../../my-config.yaml`,
|
||||
and for config file on a config server you would use
|
||||
`--config https://some.domain.io/app-config.yaml`
|
||||
|
||||
**Note**: In case URLs are passed, it is also needed to set the remote option in
|
||||
the loadBackendConfig call.
|
||||
|
||||
If no `config` flags are specified, the default behavior is to load
|
||||
`app-config.yaml` and, if it exists, `app-config.local.yaml` from the repo root.
|
||||
In the provided project setup, `app-config.local.yaml` is `.gitignore`'d, making
|
||||
|
||||
@@ -264,6 +264,12 @@ following objects:
|
||||
- horizontalpodautoscalers
|
||||
- ingresses
|
||||
|
||||
The following RBAC permissions are required on the batch API group for the
|
||||
following objects:
|
||||
|
||||
- jobs
|
||||
- cronjobs
|
||||
|
||||
## Surfacing your Kubernetes components as part of an entity
|
||||
|
||||
There are two ways to surface your Kubernetes components as part of an entity.
|
||||
|
||||
@@ -90,6 +90,18 @@ within your instance. The configuration options are documented in the
|
||||
The underlying functionality is using official ElasticSearch client version 7.x,
|
||||
meaning that ElasticSearch version 7 is the only one confirmed to be supported.
|
||||
|
||||
Should you need to create your own bespoke search experiences that require more
|
||||
than just a query translator (such as faceted search or Relay pagination), you
|
||||
can access the configuration of the search engine in order to create new elastic
|
||||
search clients. The version of the client need not be the same as one used
|
||||
internally by the elastic search engine plugin. For example:
|
||||
|
||||
```typescript
|
||||
import { Client } from '@elastic/elastic-search';
|
||||
|
||||
const client = searchEngine.newClient(options => new Client(options));
|
||||
```
|
||||
|
||||
## Example configurations
|
||||
|
||||
### AWS
|
||||
|
||||
@@ -26,7 +26,7 @@ You can create your own Field Extension by using the
|
||||
`API` like below:
|
||||
|
||||
```tsx
|
||||
//packages/app/scaffolder/MyCustomExtension/MyCustomExtension.tsx
|
||||
//packages/app/src/scaffolder/MyCustomExtension/MyCustomExtension.tsx
|
||||
import React from 'react';
|
||||
import { FieldProps, FieldValidation } from '@rjsf/core';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
@@ -68,7 +68,7 @@ export const myCustomValidation = (
|
||||
```
|
||||
|
||||
```tsx
|
||||
// packages/app/scaffolder/MyCustomExtension/extensions.ts
|
||||
// packages/app/src/scaffolder/MyCustomExtension/extensions.ts
|
||||
|
||||
/*
|
||||
This is where the magic happens and creates the custom field extension.
|
||||
@@ -94,7 +94,7 @@ export const MyCustomFieldExtension = plugin.provide(
|
||||
```
|
||||
|
||||
```tsx
|
||||
// packages/app/scaffolder/MyCustomExtension/index.ts
|
||||
// packages/app/src/scaffolder/MyCustomExtension/index.ts
|
||||
|
||||
export { MyCustomFieldExtension } from './extension';
|
||||
```
|
||||
@@ -102,7 +102,7 @@ export { MyCustomFieldExtension } from './extension';
|
||||
Once all these files are in place, you then need to provide your custom
|
||||
extension to the `scaffolder` plugin.
|
||||
|
||||
You do this in `packages/app/App.tsx`. You need to provide the
|
||||
You do this in `packages/app/src/App.tsx`. You need to provide the
|
||||
`customFieldExtensions` as children to the `ScaffolderPage`.
|
||||
|
||||
```tsx
|
||||
@@ -118,7 +118,7 @@ const routes = (
|
||||
Should look something like this instead:
|
||||
|
||||
```tsx
|
||||
import { MyCustomFieldExtension } from './scafffolder/MyCustomExtension';
|
||||
import { MyCustomFieldExtension } from './scaffolder/MyCustomExtension';
|
||||
const routes = (
|
||||
<FlatRoutes>
|
||||
...
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
# TechDocs CLI
|
||||
---
|
||||
id: cli
|
||||
title: TechDocs CLI
|
||||
# prettier-ignore
|
||||
description: TechDocs CLI - a utility command line interface for managing TechDocs sites in Backstage.
|
||||
---
|
||||
|
||||
Utility command line interface for managing TechDocs sites in
|
||||
[Backstage](https://github.com/backstage/backstage).
|
||||
|
||||
+5
-4
@@ -55,9 +55,9 @@
|
||||
},
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"@microsoft/api-documenter": "^7.13.68",
|
||||
"@microsoft/api-extractor": "^7.18.7",
|
||||
"@microsoft/api-extractor-model": "^7.13.5",
|
||||
"@microsoft/api-documenter": "^7.13.77",
|
||||
"@microsoft/api-extractor": "^7.19.2",
|
||||
"@microsoft/api-extractor-model": "^7.15.1",
|
||||
"@microsoft/tsdoc": "^0.13.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -78,11 +78,12 @@
|
||||
"prettier": "^2.2.1",
|
||||
"shx": "^0.3.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "~4.3.5",
|
||||
"yarn-lock-check": "^1.0.5"
|
||||
},
|
||||
"prettier": "@spotify/prettier-config",
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"*.{js,jsx,ts,tsx,mjs,cjs}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
|
||||
@@ -34,6 +34,8 @@ import {
|
||||
OneLoginAuth,
|
||||
UnhandledErrorForwarder,
|
||||
AtlassianAuth,
|
||||
createFetchApi,
|
||||
FetchMiddlewares,
|
||||
} from '@backstage/core-app-api';
|
||||
|
||||
import {
|
||||
@@ -42,6 +44,8 @@ import {
|
||||
analyticsApiRef,
|
||||
errorApiRef,
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
identityApiRef,
|
||||
oauthRequestApiRef,
|
||||
googleAuthApiRef,
|
||||
githubAuthApiRef,
|
||||
@@ -92,6 +96,27 @@ export const apis = [
|
||||
deps: { errorApi: errorApiRef },
|
||||
factory: ({ errorApi }) => WebStorage.create({ errorApi }),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: fetchApiRef,
|
||||
deps: {
|
||||
configApi: configApiRef,
|
||||
identityApi: identityApiRef,
|
||||
discoveryApi: discoveryApiRef,
|
||||
},
|
||||
factory: ({ configApi, identityApi, discoveryApi }) => {
|
||||
return createFetchApi({
|
||||
middleware: [
|
||||
FetchMiddlewares.resolvePluginProtocol({
|
||||
discoveryApi,
|
||||
}),
|
||||
FetchMiddlewares.injectIdentityAuth({
|
||||
identityApi,
|
||||
config: configApi,
|
||||
}),
|
||||
],
|
||||
});
|
||||
},
|
||||
}),
|
||||
createApiFactory({
|
||||
api: oauthRequestApiRef,
|
||||
deps: {},
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# example-app
|
||||
|
||||
## 0.2.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-github-actions@0.4.27
|
||||
- @backstage/core-app-api@0.2.1
|
||||
- @backstage/plugin-kubernetes@0.5.1
|
||||
- @backstage/core-plugin-api@0.3.1
|
||||
- @backstage/core-components@0.8.1
|
||||
- @backstage/plugin-org@0.3.31
|
||||
- @backstage/plugin-azure-devops@0.1.7
|
||||
- @backstage/cli@0.10.2
|
||||
- @backstage/catalog-model@0.9.8
|
||||
- @backstage/plugin-techdocs@0.12.10
|
||||
- @backstage/plugin-catalog-react@0.6.7
|
||||
- @backstage/plugin-apache-airflow@0.1.0
|
||||
|
||||
## 0.2.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
+13
-13
@@ -1,39 +1,39 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.2.56",
|
||||
"version": "0.2.57",
|
||||
"private": true,
|
||||
"bundled": true,
|
||||
"dependencies": {
|
||||
"@backstage/app-defaults": "^0.1.2",
|
||||
"@backstage/catalog-model": "^0.9.7",
|
||||
"@backstage/cli": "^0.10.1",
|
||||
"@backstage/core-app-api": "^0.2.0",
|
||||
"@backstage/core-components": "^0.8.0",
|
||||
"@backstage/core-plugin-api": "^0.3.0",
|
||||
"@backstage/catalog-model": "^0.9.8",
|
||||
"@backstage/cli": "^0.10.2",
|
||||
"@backstage/core-app-api": "^0.2.1",
|
||||
"@backstage/core-components": "^0.8.1",
|
||||
"@backstage/core-plugin-api": "^0.3.1",
|
||||
"@backstage/integration-react": "^0.1.15",
|
||||
"@backstage/plugin-api-docs": "^0.6.18",
|
||||
"@backstage/plugin-azure-devops": "^0.1.6",
|
||||
"@backstage/plugin-apache-airflow": "^0.0.0",
|
||||
"@backstage/plugin-azure-devops": "^0.1.7",
|
||||
"@backstage/plugin-apache-airflow": "^0.1.0",
|
||||
"@backstage/plugin-badges": "^0.2.16",
|
||||
"@backstage/plugin-catalog": "^0.7.4",
|
||||
"@backstage/plugin-catalog-graph": "^0.2.3",
|
||||
"@backstage/plugin-catalog-import": "^0.7.5",
|
||||
"@backstage/plugin-catalog-react": "^0.6.5",
|
||||
"@backstage/plugin-catalog-react": "^0.6.7",
|
||||
"@backstage/plugin-circleci": "^0.2.31",
|
||||
"@backstage/plugin-cloudbuild": "^0.2.29",
|
||||
"@backstage/plugin-code-coverage": "^0.1.19",
|
||||
"@backstage/plugin-cost-insights": "^0.11.13",
|
||||
"@backstage/plugin-explore": "^0.3.22",
|
||||
"@backstage/plugin-gcp-projects": "^0.3.10",
|
||||
"@backstage/plugin-github-actions": "^0.4.26",
|
||||
"@backstage/plugin-github-actions": "^0.4.27",
|
||||
"@backstage/plugin-graphiql": "^0.2.24",
|
||||
"@backstage/plugin-home": "^0.4.7",
|
||||
"@backstage/plugin-jenkins": "^0.5.14",
|
||||
"@backstage/plugin-kafka": "^0.2.22",
|
||||
"@backstage/plugin-kubernetes": "^0.5.0",
|
||||
"@backstage/plugin-kubernetes": "^0.5.1",
|
||||
"@backstage/plugin-lighthouse": "^0.2.31",
|
||||
"@backstage/plugin-newrelic": "^0.3.10",
|
||||
"@backstage/plugin-org": "^0.3.30",
|
||||
"@backstage/plugin-org": "^0.3.31",
|
||||
"@backstage/plugin-pagerduty": "0.3.19",
|
||||
"@backstage/plugin-rollbar": "^0.3.20",
|
||||
"@backstage/plugin-scaffolder": "^0.11.14",
|
||||
@@ -41,7 +41,7 @@
|
||||
"@backstage/plugin-sentry": "^0.3.30",
|
||||
"@backstage/plugin-shortcuts": "^0.1.15",
|
||||
"@backstage/plugin-tech-radar": "^0.4.13",
|
||||
"@backstage/plugin-techdocs": "^0.12.9",
|
||||
"@backstage/plugin-techdocs": "^0.12.10",
|
||||
"@backstage/plugin-todo": "^0.1.16",
|
||||
"@backstage/plugin-user-settings": "^0.3.13",
|
||||
"@backstage/search-common": "^0.2.0",
|
||||
|
||||
@@ -126,6 +126,8 @@ import {
|
||||
} from '@roadiehq/backstage-plugin-travis-ci';
|
||||
import React, { ReactNode, useMemo, useState } from 'react';
|
||||
|
||||
const customEntityFilterKind = ['Component', 'API', 'System'];
|
||||
|
||||
const EntityLayoutWrapper = (props: { children?: ReactNode }) => {
|
||||
const [badgesDialogOpen, setBadgesDialogOpen] = useState(false);
|
||||
|
||||
@@ -523,7 +525,10 @@ const userPage = (
|
||||
<EntityUserProfileCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityOwnershipCard variant="gridItem" />
|
||||
<EntityOwnershipCard
|
||||
variant="gridItem"
|
||||
entityFilterKind={customEntityFilterKind}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</EntityLayout.Route>
|
||||
@@ -539,7 +544,10 @@ const groupPage = (
|
||||
<EntityGroupProfileCard variant="gridItem" />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<EntityOwnershipCard variant="gridItem" />
|
||||
<EntityOwnershipCard
|
||||
variant="gridItem"
|
||||
entityFilterKind={customEntityFilterKind}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<EntityMembersListCard />
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @backstage/backend-common
|
||||
|
||||
## 0.9.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- fe24bc9a32: Each plugin now saves to a separate sqlite database file when `connection.filename` is provided in the sqlite config.
|
||||
Any existing sqlite database files will be ignored.
|
||||
|
||||
## 0.9.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -21,6 +21,7 @@ import { GitLabIntegration } from '@backstage/integration';
|
||||
import { isChildPath } from '@backstage/cli-common';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { Knex } from 'knex';
|
||||
import { LoadConfigOptionsRemote } from '@backstage/config-loader';
|
||||
import { Logger as Logger_2 } from 'winston';
|
||||
import { MergeResult } from 'isomorphic-git';
|
||||
import { PushResult } from 'isomorphic-git';
|
||||
@@ -338,6 +339,7 @@ export function isDatabaseConflictError(e: unknown): boolean;
|
||||
// @public
|
||||
export function loadBackendConfig(options: {
|
||||
logger: Logger_2;
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
argv: string[];
|
||||
}): Promise<Config>;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/backend-common",
|
||||
"description": "Common functionality library for Backstage backends",
|
||||
"version": "0.9.13",
|
||||
"version": "0.9.14",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"private": false,
|
||||
@@ -81,7 +81,7 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.10.1",
|
||||
"@backstage/cli": "^0.10.2",
|
||||
"@backstage/test-utils": "^0.1.24",
|
||||
"@types/archiver": "^5.1.0",
|
||||
"@types/compression": "^1.7.0",
|
||||
@@ -94,7 +94,7 @@
|
||||
"@types/recursive-readdir": "^2.2.0",
|
||||
"@types/stoppable": "^1.1.0",
|
||||
"@types/supertest": "^2.0.8",
|
||||
"@types/tar": "^4.0.3",
|
||||
"@types/tar": "^6.1.1",
|
||||
"@types/unzipper": "^0.10.3",
|
||||
"@types/webpack-env": "^1.15.2",
|
||||
"aws-sdk-mock": "^5.2.1",
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
loadConfig,
|
||||
ConfigSchema,
|
||||
ConfigTarget,
|
||||
LoadConfigOptionsRemote,
|
||||
} from '@backstage/config-loader';
|
||||
import { AppConfig, Config, ConfigReader } from '@backstage/config';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
@@ -178,6 +179,7 @@ let currentCancelFunc: () => void;
|
||||
export async function loadBackendConfig(options: {
|
||||
logger: Logger;
|
||||
// process.argv or any other overrides
|
||||
remote?: LoadConfigOptionsRemote;
|
||||
argv: string[];
|
||||
}): Promise<Config> {
|
||||
const args = parseArgs(options.argv);
|
||||
@@ -202,8 +204,8 @@ export async function loadBackendConfig(options: {
|
||||
const config = new ObservableConfigProxy(options.logger);
|
||||
const { appConfigs } = await loadConfig({
|
||||
configRoot: paths.targetRoot,
|
||||
configPaths: [],
|
||||
configTargets: configTargets,
|
||||
remote: options.remote,
|
||||
watch: {
|
||||
onChange(newConfigs) {
|
||||
options.logger.info(
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { omit } from 'lodash';
|
||||
import path from 'path';
|
||||
import {
|
||||
createDatabaseClient,
|
||||
ensureDatabaseExists,
|
||||
@@ -170,28 +171,6 @@ describe('DatabaseManager', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('uses top level sqlite database filename if plugin config is not present', async () => {
|
||||
const testManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: 'sqlite3',
|
||||
connection: 'some-file-path',
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
await testManager.forPlugin('pluginwithoutconfig').getClient();
|
||||
const mockCalls = mocked(createDatabaseClient).mock.calls.splice(-1);
|
||||
const [_, overrides] = mockCalls[0];
|
||||
|
||||
expect(overrides).toHaveProperty(
|
||||
'connection.filename',
|
||||
expect.stringContaining('some-file-path'),
|
||||
);
|
||||
});
|
||||
|
||||
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({
|
||||
@@ -214,6 +193,110 @@ describe('DatabaseManager', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if top level sqlite filename is provided', async () => {
|
||||
const testManager = DatabaseManager.fromConfig(
|
||||
new ConfigReader({
|
||||
backend: {
|
||||
database: {
|
||||
client: '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: '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: '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: '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();
|
||||
@@ -282,7 +365,10 @@ describe('DatabaseManager', () => {
|
||||
expect(baseConfig.get().client).toEqual('sqlite3');
|
||||
|
||||
// sqlite3 uses 'filename' instead of 'database'
|
||||
expect(overrides).toHaveProperty('connection.filename');
|
||||
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 () => {
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
normalizeConnection,
|
||||
} from './connection';
|
||||
import { PluginDatabaseManager } from './types';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Provides a config lookup path for a plugin's config block.
|
||||
@@ -114,10 +115,18 @@ export class DatabaseManager {
|
||||
const connection = this.getConnectionConfig(pluginId);
|
||||
|
||||
if (this.getClientType(pluginId).client === 'sqlite3') {
|
||||
// sqlite database name should fallback to ':memory:' as a special case
|
||||
return (
|
||||
(connection as Knex.Sqlite3ConnectionConfig)?.filename ?? ':memory:'
|
||||
);
|
||||
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;
|
||||
@@ -205,6 +214,17 @@ export class DatabaseManager {
|
||||
this.config.get('connection'),
|
||||
this.config.getString('client'),
|
||||
);
|
||||
|
||||
if (
|
||||
client === '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
|
||||
|
||||
@@ -73,28 +73,6 @@ describe('sqlite3', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('builds a persistent connection per database', () => {
|
||||
expect(
|
||||
buildSqliteDatabaseConfig(
|
||||
createConfig({
|
||||
filename: path.join('path', 'to', 'foo'),
|
||||
}),
|
||||
{
|
||||
connection: {
|
||||
database: 'my-database',
|
||||
},
|
||||
},
|
||||
),
|
||||
).toEqual({
|
||||
client: 'sqlite3',
|
||||
connection: {
|
||||
filename: path.join('path', 'to', 'foo', 'my-database.sqlite'),
|
||||
database: 'my-database',
|
||||
},
|
||||
useNullAsDefault: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('replaces the connection with an override', () => {
|
||||
expect(
|
||||
buildSqliteDatabaseConfig(createConfig(':memory:'), {
|
||||
|
||||
@@ -86,19 +86,6 @@ export function buildSqliteDatabaseConfig(
|
||||
overrides,
|
||||
);
|
||||
|
||||
// If we don't create an in-memory database, interpret the connection string
|
||||
// as a directory that contains multiple sqlite files based on the database
|
||||
// name.
|
||||
const database = (config.connection as Knex.ConnectionConfig).database;
|
||||
const sqliteConnection = config.connection as Knex.Sqlite3ConnectionConfig;
|
||||
|
||||
if (database && sqliteConnection.filename !== ':memory:') {
|
||||
sqliteConnection.filename = path.join(
|
||||
sqliteConnection.filename,
|
||||
`${database}.sqlite`,
|
||||
);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# example-backend
|
||||
|
||||
## 0.2.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-search-backend-module-elasticsearch@0.0.7
|
||||
- @backstage/plugin-catalog-backend@0.19.2
|
||||
- @backstage/plugin-scaffolder-backend@0.15.17
|
||||
- @backstage/backend-common@0.9.14
|
||||
- @backstage/plugin-azure-devops-backend@0.2.5
|
||||
- @backstage/plugin-auth-backend@0.5.1
|
||||
- @backstage/catalog-model@0.9.8
|
||||
- example-app@0.2.57
|
||||
|
||||
## 0.2.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-backend",
|
||||
"version": "0.2.56",
|
||||
"version": "0.2.57",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -24,16 +24,16 @@
|
||||
"migrate:create": "knex migrate:make -x ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-common": "^0.9.13",
|
||||
"@backstage/backend-common": "^0.9.14",
|
||||
"@backstage/catalog-client": "^0.5.2",
|
||||
"@backstage/catalog-model": "^0.9.7",
|
||||
"@backstage/catalog-model": "^0.9.8",
|
||||
"@backstage/config": "^0.1.10",
|
||||
"@backstage/integration": "^0.6.10",
|
||||
"@backstage/plugin-app-backend": "^0.3.19",
|
||||
"@backstage/plugin-auth-backend": "^0.5.0",
|
||||
"@backstage/plugin-azure-devops-backend": "^0.2.4",
|
||||
"@backstage/plugin-auth-backend": "^0.5.1",
|
||||
"@backstage/plugin-azure-devops-backend": "^0.2.5",
|
||||
"@backstage/plugin-badges-backend": "^0.1.13",
|
||||
"@backstage/plugin-catalog-backend": "^0.19.1",
|
||||
"@backstage/plugin-catalog-backend": "^0.19.2",
|
||||
"@backstage/plugin-code-coverage-backend": "^0.1.16",
|
||||
"@backstage/plugin-graphql-backend": "^0.1.9",
|
||||
"@backstage/plugin-jenkins-backend": "^0.1.9",
|
||||
@@ -41,11 +41,11 @@
|
||||
"@backstage/plugin-kafka-backend": "^0.2.12",
|
||||
"@backstage/plugin-proxy-backend": "^0.2.14",
|
||||
"@backstage/plugin-rollbar-backend": "^0.1.16",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.15.16",
|
||||
"@backstage/plugin-scaffolder-backend": "^0.15.17",
|
||||
"@backstage/plugin-scaffolder-backend-module-rails": "^0.2.0",
|
||||
"@backstage/plugin-search-backend": "^0.2.8",
|
||||
"@backstage/plugin-search-backend-node": "^0.4.2",
|
||||
"@backstage/plugin-search-backend-module-elasticsearch": "^0.0.6",
|
||||
"@backstage/plugin-search-backend-module-elasticsearch": "^0.0.7",
|
||||
"@backstage/plugin-search-backend-module-pg": "^0.2.2",
|
||||
"@backstage/plugin-techdocs-backend": "^0.12.0",
|
||||
"@backstage/plugin-tech-insights-backend": "^0.1.3",
|
||||
@@ -56,7 +56,7 @@
|
||||
"@octokit/rest": "^18.5.3",
|
||||
"azure-devops-node-api": "^11.0.1",
|
||||
"dockerode": "^3.3.1",
|
||||
"example-app": "^0.2.56",
|
||||
"example-app": "^0.2.57",
|
||||
"express": "^4.17.1",
|
||||
"express-promise-router": "^4.1.0",
|
||||
"express-prom-bundle": "^6.3.6",
|
||||
@@ -68,7 +68,7 @@
|
||||
"winston": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.10.1",
|
||||
"@backstage/cli": "^0.10.2",
|
||||
"@types/dockerode": "^3.3.0",
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/express-serve-static-core": "^4.17.5"
|
||||
|
||||
@@ -89,6 +89,7 @@ async function main() {
|
||||
argv: process.argv,
|
||||
logger,
|
||||
});
|
||||
|
||||
const createEnv = makeCreateEnv(config);
|
||||
|
||||
const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck'));
|
||||
|
||||
@@ -71,7 +71,7 @@ export interface CatalogApi {
|
||||
|
||||
// @public
|
||||
export class CatalogClient implements CatalogApi {
|
||||
constructor(options: { discoveryApi: DiscoveryApi });
|
||||
constructor(options: { discoveryApi: DiscoveryApi; fetchApi?: FetchApi });
|
||||
addLocation(
|
||||
{ type, target, dryRun, presence }: AddLocationRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
@@ -121,6 +121,9 @@ export type CatalogEntitiesRequest = {
|
||||
| Record<string, string | symbol | (string | symbol)[]>
|
||||
| undefined;
|
||||
fields?: string[] | undefined;
|
||||
offset?: number;
|
||||
limit?: number;
|
||||
after?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -155,4 +158,9 @@ export type DiscoveryApi = {
|
||||
// @public
|
||||
export const ENTITY_STATUS_CATALOG_PROCESSING_TYPE =
|
||||
'backstage.io/catalog-processing';
|
||||
|
||||
// @public
|
||||
export type FetchApi = {
|
||||
fetch: typeof fetch;
|
||||
};
|
||||
```
|
||||
|
||||
@@ -175,6 +175,24 @@ describe('CatalogClient', () => {
|
||||
{ apiVersion: '2' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('builds paging parameters properly', async () => {
|
||||
expect.assertions(2);
|
||||
|
||||
server.use(
|
||||
rest.get(`${mockBaseUrl}/entities`, (req, res, ctx) => {
|
||||
expect(req.url.search).toBe('?offset=1&limit=2&after=%3D');
|
||||
return res(ctx.json([]));
|
||||
}),
|
||||
);
|
||||
|
||||
const response = await client.getEntities(
|
||||
{ offset: 1, limit: 2, after: '=' },
|
||||
{ token },
|
||||
);
|
||||
|
||||
expect(response.items).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLocationById', () => {
|
||||
|
||||
@@ -25,7 +25,7 @@ import {
|
||||
stringifyLocationReference,
|
||||
} from '@backstage/catalog-model';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
import fetch from 'cross-fetch';
|
||||
import crossFetch from 'cross-fetch';
|
||||
import {
|
||||
CATALOG_FILTER_EXISTS,
|
||||
AddLocationRequest,
|
||||
@@ -38,6 +38,7 @@ import {
|
||||
CatalogEntityAncestorsResponse,
|
||||
} from './types/api';
|
||||
import { DiscoveryApi } from './types/discovery';
|
||||
import { FetchApi } from './types/fetch';
|
||||
|
||||
/**
|
||||
* A frontend and backend compatible client for communicating with the Backstage Catalog.
|
||||
@@ -46,9 +47,11 @@ import { DiscoveryApi } from './types/discovery';
|
||||
* */
|
||||
export class CatalogClient implements CatalogApi {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly fetchApi: FetchApi;
|
||||
|
||||
constructor(options: { discoveryApi: DiscoveryApi }) {
|
||||
constructor(options: { discoveryApi: DiscoveryApi; fetchApi?: FetchApi }) {
|
||||
this.discoveryApi = options.discoveryApi;
|
||||
this.fetchApi = options.fetchApi || { fetch: crossFetch };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,7 +113,7 @@ export class CatalogClient implements CatalogApi {
|
||||
request?: CatalogEntitiesRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<CatalogListResponse<Entity>> {
|
||||
const { filter = [], fields = [] } = request ?? {};
|
||||
const { filter = [], fields = [], offset, limit, after } = request ?? {};
|
||||
const filterItems = [filter].flat();
|
||||
const params: string[] = [];
|
||||
|
||||
@@ -141,6 +144,16 @@ export class CatalogClient implements CatalogApi {
|
||||
params.push(`fields=${fields.map(encodeURIComponent).join(',')}`);
|
||||
}
|
||||
|
||||
if (offset !== undefined) {
|
||||
params.push(`offset=${offset}`);
|
||||
}
|
||||
if (limit !== undefined) {
|
||||
params.push(`limit=${limit}`);
|
||||
}
|
||||
if (after !== undefined) {
|
||||
params.push(`after=${encodeURIComponent(after)}`);
|
||||
}
|
||||
|
||||
const query = params.length ? `?${params.join('&')}` : '';
|
||||
const entities: Entity[] = await this.requestRequired(
|
||||
'GET',
|
||||
@@ -206,7 +219,7 @@ export class CatalogClient implements CatalogApi {
|
||||
* @public
|
||||
*/
|
||||
async refreshEntity(entityRef: string, options?: CatalogRequestOptions) {
|
||||
const response = await fetch(
|
||||
const response = await this.fetchApi.fetch(
|
||||
`${await this.discoveryApi.getBaseUrl('catalog')}/refresh`,
|
||||
{
|
||||
headers: {
|
||||
@@ -237,7 +250,7 @@ export class CatalogClient implements CatalogApi {
|
||||
{ type = 'url', target, dryRun, presence }: AddLocationRequest,
|
||||
options?: CatalogRequestOptions,
|
||||
): Promise<AddLocationResponse> {
|
||||
const response = await fetch(
|
||||
const response = await this.fetchApi.fetch(
|
||||
`${await this.discoveryApi.getBaseUrl('catalog')}/locations${
|
||||
dryRun ? '?dryRun=true' : ''
|
||||
}`,
|
||||
@@ -376,7 +389,7 @@ export class CatalogClient implements CatalogApi {
|
||||
const headers: Record<string, string> = options?.token
|
||||
? { Authorization: `Bearer ${options.token}` }
|
||||
: {};
|
||||
const response = await fetch(url, { method, headers });
|
||||
const response = await this.fetchApi.fetch(url, { method, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
@@ -392,7 +405,7 @@ export class CatalogClient implements CatalogApi {
|
||||
const headers: Record<string, string> = options?.token
|
||||
? { Authorization: `Bearer ${options.token}` }
|
||||
: {};
|
||||
const response = await fetch(url, { method, headers });
|
||||
const response = await this.fetchApi.fetch(url, { method, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
@@ -410,7 +423,7 @@ export class CatalogClient implements CatalogApi {
|
||||
const headers: Record<string, string> = options?.token
|
||||
? { Authorization: `Bearer ${options.token}` }
|
||||
: {};
|
||||
const response = await fetch(url, { method, headers });
|
||||
const response = await this.fetchApi.fetch(url, { method, headers });
|
||||
|
||||
if (!response.ok) {
|
||||
if (response.status === 404) {
|
||||
|
||||
@@ -29,11 +29,81 @@ export const CATALOG_FILTER_EXISTS = Symbol('CATALOG_FILTER_EXISTS');
|
||||
* @public
|
||||
*/
|
||||
export type CatalogEntitiesRequest = {
|
||||
/**
|
||||
* If given, return only entities that match the given patterns.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* If multiple filter sets are given as an array, then there is effectively an
|
||||
* OR between each filter set.
|
||||
*
|
||||
* Within one filter set, there is effectively an AND between the various
|
||||
* keys.
|
||||
*
|
||||
* Within one key, if there are more than one value, then there is effectively
|
||||
* an OR between them.
|
||||
*
|
||||
* Example: For an input of
|
||||
*
|
||||
* ```
|
||||
* [
|
||||
* { kind: ['API', 'Component'] },
|
||||
* { 'metadata.name': 'a', 'metadata.namespace': 'b' }
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
* This effectively means
|
||||
*
|
||||
* ```
|
||||
* (kind = EITHER 'API' OR 'Component')
|
||||
* OR
|
||||
* (metadata.name = 'a' AND metadata.namespace = 'b' )
|
||||
* ```
|
||||
*
|
||||
* Each key is a dot separated path in each object.
|
||||
*
|
||||
* As a value you can also pass in the symbol `CATALOG_FILTER_EXISTS`
|
||||
* (exported from this package), which means that you assert on the existence
|
||||
* of that key, no matter what its value is.
|
||||
*/
|
||||
filter?:
|
||||
| Record<string, string | symbol | (string | symbol)[]>[]
|
||||
| Record<string, string | symbol | (string | symbol)[]>
|
||||
| undefined;
|
||||
/**
|
||||
* If given, return only the parts of each entity that match those dot
|
||||
* separated paths in each object.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Example: For an input of `['kind', 'metadata.annotations']`, then response
|
||||
* objects will be shaped like
|
||||
*
|
||||
* ```
|
||||
* {
|
||||
* "kind": "Component",
|
||||
* "metadata": {
|
||||
* "annotations": {
|
||||
* "foo": "bar"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
fields?: string[] | undefined;
|
||||
/**
|
||||
* If given, skips over the first N items in the result set.
|
||||
*/
|
||||
offset?: number;
|
||||
/**
|
||||
* If given, returns at most N items from the result set.
|
||||
*/
|
||||
limit?: number;
|
||||
/**
|
||||
* If given, skips over all items before that cursor as returned by a previous
|
||||
* request.
|
||||
*/
|
||||
after?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a copy of the core DiscoveryApi, to avoid importing core.
|
||||
* This is a copy of the DiscoveryApi, to avoid importing core-plugin-api.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a copy of FetchApi, to avoid importing core-plugin-api.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type FetchApi = {
|
||||
fetch: typeof fetch;
|
||||
};
|
||||
@@ -25,5 +25,6 @@ export type {
|
||||
CatalogEntityAncestorsResponse,
|
||||
} from './api';
|
||||
export type { DiscoveryApi } from './discovery';
|
||||
export type { FetchApi } from './fetch';
|
||||
export { CATALOG_FILTER_EXISTS } from './api';
|
||||
export { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from './status';
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/catalog-model
|
||||
|
||||
## 0.9.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- ad7338bb48: Added an optional `presence` field to Location spec, which describes whether the target of a location is required to exist or not. It defaults to `'required'`, which is the current behaviour of the catalog.
|
||||
|
||||
## 0.9.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -325,6 +325,7 @@ interface LocationEntityV1alpha1 extends Entity {
|
||||
type?: string;
|
||||
target?: string;
|
||||
targets?: string[];
|
||||
presence?: 'required' | 'optional';
|
||||
};
|
||||
}
|
||||
export { LocationEntityV1alpha1 as LocationEntity };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/catalog-model",
|
||||
"description": "Types and validators that help describe the model of a Backstage Catalog",
|
||||
"version": "0.9.7",
|
||||
"version": "0.9.8",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -42,7 +42,7 @@
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.10.0",
|
||||
"@backstage/cli": "^0.10.2",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/lodash": "^4.14.151",
|
||||
"yaml": "^1.9.2"
|
||||
|
||||
@@ -102,4 +102,23 @@ describe('LocationV1alpha1Validator', () => {
|
||||
(entity as any).spec.targets = 7;
|
||||
await expect(validator.check(entity)).rejects.toThrow(/targets/);
|
||||
});
|
||||
|
||||
it('accepts good presence', async () => {
|
||||
(entity as any).spec.presence = 'required';
|
||||
await expect(validator.check(entity)).resolves.toBe(true);
|
||||
(entity as any).spec.presence = 'optional';
|
||||
await expect(validator.check(entity)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('rejects empty presence', async () => {
|
||||
(entity as any).spec.presence = '';
|
||||
await expect(validator.check(entity)).rejects.toThrow(/presence/);
|
||||
});
|
||||
|
||||
it('rejects wrong presence', async () => {
|
||||
(entity as any).spec.presence = 7;
|
||||
await expect(validator.check(entity)).rejects.toThrow(/presence/);
|
||||
(entity as any).spec.presence = 'nope';
|
||||
await expect(validator.check(entity)).rejects.toThrow(/presence/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface LocationEntityV1alpha1 extends Entity {
|
||||
type?: string;
|
||||
target?: string;
|
||||
targets?: string[];
|
||||
presence?: 'required' | 'optional';
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,13 @@
|
||||
],
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"presence": {
|
||||
"type": "string",
|
||||
"description": "Whether the presence of the location target is required and it should be considered an error if it can not be found",
|
||||
"default": "required",
|
||||
"examples": ["required"],
|
||||
"enum": ["required", "optional"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,4 +67,16 @@ ignore:
|
||||
reason: Prototype pollution is not an effective attack against a CLI as it already executes arbitrary code
|
||||
expires: 2022-03-06T17:18:55.019Z
|
||||
created: 2021-09-06T17:18:55.027Z
|
||||
|
||||
'snyk:lic:npm:rollup-plugin-dts:LGPL-3.0':
|
||||
- '*':
|
||||
reason: Backstage itself does not redistribute this dependency in minified form
|
||||
expires: 2031-09-06T17:18:55.027Z
|
||||
created: 2021-09-06T17:18:55.027Z
|
||||
|
||||
'snyk:lic:npm:axe-core:MPL-2.0':
|
||||
- '*':
|
||||
reason: Backstage itself does not redistribute this dependency in minified form
|
||||
expires: 2031-09-06T17:18:55.027Z
|
||||
created: 2021-09-06T17:18:55.027Z
|
||||
patch: {}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.10.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 25dfc2d483: Add support for `.cjs` and `.mjs` extensions in local and dependency modules.
|
||||
|
||||
## 0.10.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -96,20 +96,22 @@ async function getProjectConfig(targetPath, displayName) {
|
||||
rootDir: path.resolve(targetPath, 'src'),
|
||||
coverageDirectory: path.resolve(targetPath, 'coverage'),
|
||||
coverageProvider: 'v8',
|
||||
collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', '!**/*.d.ts'],
|
||||
collectCoverageFrom: ['**/*.{js,jsx,ts,tsx,mjs,cjs}', '!**/*.d.ts'],
|
||||
moduleNameMapper: {
|
||||
'\\.(css|less|scss|sss|styl)$': require.resolve('jest-css-modules'),
|
||||
},
|
||||
|
||||
transform: {
|
||||
'\\.(js|jsx|ts|tsx)$': require.resolve('./jestSucraseTransform.js'),
|
||||
'\\.(js|jsx|ts|tsx|mjs|cjs)$': require.resolve(
|
||||
'./jestSucraseTransform.js',
|
||||
),
|
||||
'\\.(bmp|gif|jpg|jpeg|png|frag|xml|svg|eot|woff|woff2|ttf)$':
|
||||
require.resolve('./jestFileTransform.js'),
|
||||
'\\.(yaml)$': require.resolve('jest-transform-yaml'),
|
||||
},
|
||||
|
||||
// A bit more opinionated
|
||||
testMatch: ['**/?(*.)test.{js,jsx,mjs,ts,tsx}'],
|
||||
testMatch: ['**/?(*.)test.{js,jsx,ts,tsx,mjs,cjs}'],
|
||||
|
||||
transformIgnorePatterns: [`/node_modules/(?:${transformIgnorePattern})/`],
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"version": "0.10.1",
|
||||
"version": "0.10.2",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
@@ -116,11 +116,11 @@
|
||||
"yn": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/backend-common": "^0.9.13",
|
||||
"@backstage/backend-common": "^0.9.14",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/core-components": "^0.8.0",
|
||||
"@backstage/core-plugin-api": "^0.3.0",
|
||||
"@backstage/core-app-api": "^0.2.0",
|
||||
"@backstage/core-components": "^0.8.1",
|
||||
"@backstage/core-plugin-api": "^0.3.1",
|
||||
"@backstage/core-app-api": "^0.2.1",
|
||||
"@backstage/dev-utils": "^0.2.14",
|
||||
"@backstage/test-utils": "^0.1.24",
|
||||
"@backstage/theme": "^0.2.14",
|
||||
@@ -128,13 +128,13 @@
|
||||
"@types/express": "^4.17.6",
|
||||
"@types/fs-extra": "^9.0.1",
|
||||
"@types/http-proxy": "^1.17.4",
|
||||
"@types/inquirer": "^7.3.1",
|
||||
"@types/inquirer": "^8.1.3",
|
||||
"@types/mock-fs": "^4.13.0",
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/recursive-readdir": "^2.2.0",
|
||||
"@types/rollup-plugin-peer-deps-external": "^2.2.0",
|
||||
"@types/rollup-plugin-postcss": "^2.0.0",
|
||||
"@types/tar": "^4.0.3",
|
||||
"@types/tar": "^6.1.1",
|
||||
"@types/terser-webpack-plugin": "^5.0.4",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@types/webpack-dev-server": "^3.11.5",
|
||||
|
||||
@@ -20,7 +20,7 @@ import { paths } from '../lib/paths';
|
||||
|
||||
export default async (cmd: Command, cmdArgs: string[]) => {
|
||||
const args = [
|
||||
'--ext=js,jsx,ts,tsx',
|
||||
'--ext=js,jsx,ts,tsx,mjs,cjs',
|
||||
'--max-warnings=0',
|
||||
`--format=${cmd.format}`,
|
||||
...(cmdArgs ?? [paths.targetDir]),
|
||||
|
||||
@@ -61,7 +61,7 @@ export const transforms = (options: TransformOptions): Transforms => {
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(jsx?|mjs)$/,
|
||||
test: /\.(jsx?|mjs|cjs)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: require.resolve('@sucrase/webpack-loader'),
|
||||
options: {
|
||||
@@ -71,7 +71,7 @@ export const transforms = (options: TransformOptions): Transforms => {
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.m?js/,
|
||||
test: /\.(js|mjs|cjs)/,
|
||||
resolve: {
|
||||
fullySpecified: false,
|
||||
},
|
||||
|
||||
@@ -59,7 +59,6 @@ export async function loadCliConfig(options: Options) {
|
||||
? async name => process.env[name] || 'x'
|
||||
: undefined,
|
||||
configRoot: paths.targetRoot,
|
||||
configPaths: [],
|
||||
configTargets: configTargets,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
# @backstage/codemods
|
||||
|
||||
## 0.1.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-app-api@0.2.1
|
||||
- @backstage/core-plugin-api@0.3.1
|
||||
- @backstage/core-components@0.8.1
|
||||
|
||||
## 0.1.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/codemods",
|
||||
"description": "A collection of codemods for Backstage projects",
|
||||
"version": "0.1.25",
|
||||
"version": "0.1.26",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
|
||||
@@ -45,7 +45,6 @@ export function loadConfig(
|
||||
// @public
|
||||
export type LoadConfigOptions = {
|
||||
configRoot: string;
|
||||
configPaths: string[];
|
||||
configTargets: ConfigTarget[];
|
||||
env?: string;
|
||||
experimentalEnvFunc?: (name: string) => Promise<string | undefined>;
|
||||
@@ -103,8 +102,4 @@ export type TransformFunc<T extends number | string | boolean> = (
|
||||
visibility: ConfigVisibility;
|
||||
},
|
||||
) => T | undefined;
|
||||
|
||||
// Warnings were encountered during analysis:
|
||||
//
|
||||
// src/loader.d.ts:33:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "@backstage/config-loader" does not have an export "configTargets"
|
||||
```
|
||||
|
||||
@@ -118,7 +118,6 @@ describe('loadConfig', () => {
|
||||
await expect(
|
||||
loadConfig({
|
||||
configRoot: '/root',
|
||||
configPaths: [],
|
||||
configTargets: [],
|
||||
env: 'production',
|
||||
}),
|
||||
@@ -146,7 +145,6 @@ describe('loadConfig', () => {
|
||||
await expect(
|
||||
loadConfig({
|
||||
configRoot: '/root',
|
||||
configPaths: [],
|
||||
configTargets: [{ url: configUrl }],
|
||||
env: 'production',
|
||||
remote: {
|
||||
@@ -173,8 +171,10 @@ describe('loadConfig', () => {
|
||||
await expect(
|
||||
loadConfig({
|
||||
configRoot: '/root',
|
||||
configPaths: ['/root/app-config2.yaml'],
|
||||
configTargets: [{ path: '/root/app-config.yaml' }],
|
||||
configTargets: [
|
||||
{ path: '/root/app-config.yaml' },
|
||||
{ path: '/root/app-config2.yaml' },
|
||||
],
|
||||
env: 'production',
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
@@ -207,7 +207,6 @@ describe('loadConfig', () => {
|
||||
await expect(
|
||||
loadConfig({
|
||||
configRoot: '/root',
|
||||
configPaths: ['/root/app-config.yaml'],
|
||||
configTargets: [{ path: '/root/app-config.yaml' }],
|
||||
env: 'production',
|
||||
}),
|
||||
@@ -231,7 +230,6 @@ describe('loadConfig', () => {
|
||||
await expect(
|
||||
loadConfig({
|
||||
configRoot: '/root',
|
||||
configPaths: [],
|
||||
configTargets: [
|
||||
{ path: '/root/app-config.yaml' },
|
||||
{ path: '/root/app-config.development.yaml' },
|
||||
@@ -274,7 +272,6 @@ describe('loadConfig', () => {
|
||||
await expect(
|
||||
loadConfig({
|
||||
configRoot: '/root',
|
||||
configPaths: [],
|
||||
configTargets: [{ path: '/root/app-config.substitute.yaml' }],
|
||||
env: 'development',
|
||||
}),
|
||||
@@ -302,7 +299,6 @@ describe('loadConfig', () => {
|
||||
await expect(
|
||||
loadConfig({
|
||||
configRoot: '/root',
|
||||
configPaths: [],
|
||||
configTargets: [],
|
||||
watch: {
|
||||
onChange: onChange.resolve,
|
||||
@@ -353,7 +349,6 @@ describe('loadConfig', () => {
|
||||
await expect(
|
||||
loadConfig({
|
||||
configRoot: '/root',
|
||||
configPaths: [],
|
||||
configTargets: [{ url: configUrl }],
|
||||
watch: {
|
||||
onChange: onChange.resolve,
|
||||
@@ -401,7 +396,6 @@ describe('loadConfig', () => {
|
||||
|
||||
await loadConfig({
|
||||
configRoot: '/root',
|
||||
configPaths: [],
|
||||
configTargets: [],
|
||||
watch: {
|
||||
onChange: () => {
|
||||
|
||||
@@ -45,7 +45,7 @@ export type LoadConfigOptionsWatch = {
|
||||
|
||||
export type LoadConfigOptionsRemote = {
|
||||
/**
|
||||
* An optional remote config reloading period, in seconds
|
||||
* A remote config reloading period, in seconds
|
||||
*/
|
||||
reloadIntervalSeconds: number;
|
||||
};
|
||||
@@ -59,11 +59,6 @@ export type LoadConfigOptions = {
|
||||
// The root directory of the config loading context. Used to find default configs.
|
||||
configRoot: string;
|
||||
|
||||
/** Absolute paths to load config files from. Configs from earlier paths have lower priority.
|
||||
* @deprecated Use {@link configTargets} instead.
|
||||
*/
|
||||
configPaths: string[];
|
||||
|
||||
// Paths to load config files from. Configs from earlier paths have lower priority.
|
||||
configTargets: ConfigTarget[];
|
||||
|
||||
@@ -114,20 +109,21 @@ export async function loadConfig(
|
||||
.filter((e): e is { path: string } => e.hasOwnProperty('path'))
|
||||
.map(configTarget => configTarget.path);
|
||||
|
||||
// Append deprecated configPaths to the absolute config paths received via configTargets.
|
||||
options.configPaths.forEach(cp => {
|
||||
if (!configPaths.includes(cp)) {
|
||||
configPaths.push(cp);
|
||||
}
|
||||
});
|
||||
|
||||
const configUrls: string[] = options.configTargets
|
||||
.slice()
|
||||
.filter((e): e is { url: string } => e.hasOwnProperty('url'))
|
||||
.map(configTarget => configTarget.url);
|
||||
|
||||
if (remote === undefined && configUrls.length > 0) {
|
||||
throw new Error(`Remote config detected but this feature is turned off`);
|
||||
if (remote === undefined) {
|
||||
if (configUrls.length > 0) {
|
||||
throw new Error(
|
||||
`Please make sure you are passing the remote option when loading remote configurations. See https://backstage.io/docs/conf/writing#configuration-files for detailed info.`,
|
||||
);
|
||||
}
|
||||
} else if (remote.reloadIntervalSeconds <= 0) {
|
||||
throw new Error(
|
||||
`Remote config must be contain a non zero reloadIntervalSeconds: <seconds> value`,
|
||||
);
|
||||
}
|
||||
|
||||
// If no paths are provided, we default to reading
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/core-app-api
|
||||
|
||||
## 0.2.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c11ce4f552: Deprecated `Auth0Auth`, pointing to using `OAuth2` directly instead.
|
||||
- 9d6503e86c: Switched out usage of deprecated `OAuthRequestApi` types from `@backstage/core-plugin-api`.
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@0.3.1
|
||||
- @backstage/core-components@0.8.1
|
||||
|
||||
## 0.2.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -17,15 +17,14 @@ import { AppTheme } from '@backstage/core-plugin-api';
|
||||
import { AppThemeApi } from '@backstage/core-plugin-api';
|
||||
import { atlassianAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { auth0AuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { AuthProvider } from '@backstage/core-plugin-api';
|
||||
import { AuthRequester } from '@backstage/core-plugin-api';
|
||||
import { AuthRequesterOptions } from '@backstage/core-plugin-api';
|
||||
import { AuthProviderInfo } from '@backstage/core-plugin-api';
|
||||
import { AuthRequestOptions } from '@backstage/core-plugin-api';
|
||||
import { BackstageIdentity } from '@backstage/core-plugin-api';
|
||||
import { BackstageIdentityApi } from '@backstage/core-plugin-api';
|
||||
import { BackstagePlugin } from '@backstage/core-plugin-api';
|
||||
import { bitbucketAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { ComponentType } from 'react';
|
||||
import { Config } from '@backstage/config';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { createApp as createApp_2 } from '@backstage/app-defaults';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
@@ -36,6 +35,7 @@ import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { FeatureFlag } from '@backstage/core-plugin-api';
|
||||
import { FeatureFlagsApi } from '@backstage/core-plugin-api';
|
||||
import { FeatureFlagsSaveOptions } from '@backstage/core-plugin-api';
|
||||
import { FetchApi } from '@backstage/core-plugin-api';
|
||||
import { gitlabAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { googleAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { IconComponent } from '@backstage/core-plugin-api';
|
||||
@@ -43,11 +43,13 @@ import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { microsoftAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { OAuthApi } from '@backstage/core-plugin-api';
|
||||
import { OAuthRequestApi } from '@backstage/core-plugin-api';
|
||||
import { OAuthRequester } from '@backstage/core-plugin-api';
|
||||
import { OAuthRequesterOptions } from '@backstage/core-plugin-api';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { oktaAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { oneloginAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { OpenIdConnectApi } from '@backstage/core-plugin-api';
|
||||
import { PendingAuthRequest } from '@backstage/core-plugin-api';
|
||||
import { PendingOAuthRequest } from '@backstage/core-plugin-api';
|
||||
import { PluginOutput } from '@backstage/core-plugin-api';
|
||||
import { ProfileInfo } from '@backstage/core-plugin-api';
|
||||
import { ProfileInfoApi } from '@backstage/core-plugin-api';
|
||||
@@ -254,7 +256,7 @@ export class AtlassianAuth {
|
||||
static create(options: OAuthApiCreateOptions): typeof atlassianAuthApiRef.T;
|
||||
}
|
||||
|
||||
// @public
|
||||
// @public @deprecated
|
||||
export class Auth0Auth {
|
||||
// (undocumented)
|
||||
static create(options: OAuthApiCreateOptions): typeof auth0AuthApiRef.T;
|
||||
@@ -264,9 +266,7 @@ export class Auth0Auth {
|
||||
export type AuthApiCreateOptions = {
|
||||
discoveryApi: DiscoveryApi;
|
||||
environment?: string;
|
||||
provider?: AuthProvider & {
|
||||
id: string;
|
||||
};
|
||||
provider?: AuthProviderInfo;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -320,6 +320,12 @@ export function createApp(
|
||||
options?: Parameters<typeof createApp_2>[0],
|
||||
): BackstageApp & AppContext;
|
||||
|
||||
// @public
|
||||
export function createFetchApi(options: {
|
||||
baseImplementation?: typeof fetch | undefined;
|
||||
middleware?: FetchMiddleware | FetchMiddleware[] | undefined;
|
||||
}): FetchApi;
|
||||
|
||||
// @public
|
||||
export function createSpecializedApp(options: AppOptions): BackstageApp;
|
||||
|
||||
@@ -371,6 +377,27 @@ export type FeatureFlaggedProps = {
|
||||
}
|
||||
);
|
||||
|
||||
// @public
|
||||
export interface FetchMiddleware {
|
||||
apply(next: typeof fetch): typeof fetch;
|
||||
}
|
||||
|
||||
// @public
|
||||
export class FetchMiddlewares {
|
||||
static injectIdentityAuth(options: {
|
||||
identityApi: IdentityApi;
|
||||
config?: Config;
|
||||
urlPrefixAllowlist?: string[];
|
||||
header?: {
|
||||
name: string;
|
||||
value: (backstageToken: string) => string;
|
||||
};
|
||||
}): FetchMiddleware;
|
||||
static resolvePluginProtocol(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
}): FetchMiddleware;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const FlatRoutes: (props: FlatRoutesProps) => JSX.Element | null;
|
||||
|
||||
@@ -515,9 +542,9 @@ export type OAuthApiCreateOptions = AuthApiCreateOptions & {
|
||||
// @public
|
||||
export class OAuthRequestManager implements OAuthRequestApi {
|
||||
// (undocumented)
|
||||
authRequest$(): Observable<PendingAuthRequest[]>;
|
||||
authRequest$(): Observable<PendingOAuthRequest[]>;
|
||||
// (undocumented)
|
||||
createAuthRequester<T>(options: AuthRequesterOptions<T>): AuthRequester<T>;
|
||||
createAuthRequester<T>(options: OAuthRequesterOptions<T>): OAuthRequester<T>;
|
||||
}
|
||||
|
||||
// @public
|
||||
@@ -539,9 +566,7 @@ export type OneLoginAuthCreateOptions = {
|
||||
discoveryApi: DiscoveryApi;
|
||||
oauthRequestApi: OAuthRequestApi;
|
||||
environment?: string;
|
||||
provider?: AuthProvider & {
|
||||
id: string;
|
||||
};
|
||||
provider?: AuthProviderInfo;
|
||||
};
|
||||
|
||||
// @public
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-app-api",
|
||||
"description": "Core app API used by Backstage apps",
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -30,9 +30,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/app-defaults": "^0.1.2",
|
||||
"@backstage/core-components": "^0.8.0",
|
||||
"@backstage/core-components": "^0.8.1",
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/core-plugin-api": "^0.3.0",
|
||||
"@backstage/core-plugin-api": "^0.3.1",
|
||||
"@backstage/theme": "^0.2.14",
|
||||
"@backstage/types": "^0.1.1",
|
||||
"@backstage/version-bridge": "^0.1.1",
|
||||
@@ -49,7 +49,7 @@
|
||||
"react": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.10.1",
|
||||
"@backstage/cli": "^0.10.2",
|
||||
"@backstage/test-utils": "^0.1.24",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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 { Config } from '@backstage/config';
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { IdentityAuthInjectorFetchMiddleware } from './IdentityAuthInjectorFetchMiddleware';
|
||||
import { PluginProtocolResolverFetchMiddleware } from './PluginProtocolResolverFetchMiddleware';
|
||||
import { FetchMiddleware } from './types';
|
||||
|
||||
/**
|
||||
* A collection of common middlewares for the FetchApi.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class FetchMiddlewares {
|
||||
/**
|
||||
* Handles translation from `plugin://` URLs to concrete http(s) URLs based on
|
||||
* the discovery API.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* If the request is for `plugin://catalog/entities?filter=x=y`, the discovery
|
||||
* API will be queried for `'catalog'`. If it returned
|
||||
* `https://backstage.example.net/api/catalog`, the resulting query would be
|
||||
* `https://backstage.example.net/api/catalog/entities?filter=x=y`.
|
||||
*
|
||||
* If the incoming URL protocol was not `plugin`, the request is just passed
|
||||
* through verbatim to the underlying implementation.
|
||||
*/
|
||||
static resolvePluginProtocol(options: {
|
||||
discoveryApi: DiscoveryApi;
|
||||
}): FetchMiddleware {
|
||||
return new PluginProtocolResolverFetchMiddleware(options.discoveryApi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects a Backstage token header when the user is signed in.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* Per default, an `Authorization: Bearer <token>` is generated. This can be
|
||||
* customized using the `header` option.
|
||||
*
|
||||
* The header injection only happens on allowlisted URLs. Per default, if the
|
||||
* `config` option is passed in, the `backend.baseUrl` is allowlisted, unless
|
||||
* the `urlPrefixAllowlist` option is passed in, in which case it takes
|
||||
* precedence. If you pass in neither config nor an allowlist, the middleware
|
||||
* will have no effect.
|
||||
*/
|
||||
static injectIdentityAuth(options: {
|
||||
identityApi: IdentityApi;
|
||||
config?: Config;
|
||||
urlPrefixAllowlist?: string[];
|
||||
header?: {
|
||||
name: string;
|
||||
value: (backstageToken: string) => string;
|
||||
};
|
||||
}): FetchMiddleware {
|
||||
return IdentityAuthInjectorFetchMiddleware.create(options);
|
||||
}
|
||||
|
||||
private constructor() {}
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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 { ConfigReader } from '@backstage/config';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { IdentityAuthInjectorFetchMiddleware } from './IdentityAuthInjectorFetchMiddleware';
|
||||
|
||||
describe('IdentityAuthInjectorFetchMiddleware', () => {
|
||||
it('creates using defaults', async () => {
|
||||
const middleware = IdentityAuthInjectorFetchMiddleware.create({
|
||||
identityApi: undefined as any,
|
||||
});
|
||||
expect(middleware.urlPrefixAllowlist).toEqual([]);
|
||||
expect(middleware.headerName).toEqual('authorization');
|
||||
expect(middleware.headerValue('t')).toEqual('Bearer t');
|
||||
});
|
||||
|
||||
it('creates using config', async () => {
|
||||
const middleware = IdentityAuthInjectorFetchMiddleware.create({
|
||||
identityApi: undefined as any,
|
||||
config: new ConfigReader({
|
||||
backend: { baseUrl: 'https://example.com/api' },
|
||||
}),
|
||||
header: { name: 'auth', value: t => `${t}!` },
|
||||
});
|
||||
expect(middleware.urlPrefixAllowlist).toEqual(['https://example.com/api']);
|
||||
expect(middleware.headerName).toEqual('auth');
|
||||
expect(middleware.headerValue('t')).toEqual('t!');
|
||||
});
|
||||
|
||||
it('creates using explicit allowlist', async () => {
|
||||
const middleware = IdentityAuthInjectorFetchMiddleware.create({
|
||||
identityApi: undefined as any,
|
||||
config: new ConfigReader({
|
||||
backend: { baseUrl: 'https://example.com/api' },
|
||||
}),
|
||||
urlPrefixAllowlist: ['https://a.com', 'http://b.com:8080/'],
|
||||
});
|
||||
expect(middleware.urlPrefixAllowlist).toEqual([
|
||||
'https://a.com',
|
||||
'http://b.com:8080',
|
||||
]);
|
||||
});
|
||||
|
||||
it('injects the header only when a token is available', async () => {
|
||||
const tokenFunction = jest.fn();
|
||||
const identityApi = {
|
||||
getCredentials: tokenFunction,
|
||||
} as unknown as IdentityApi;
|
||||
|
||||
const middleware = new IdentityAuthInjectorFetchMiddleware(
|
||||
identityApi,
|
||||
['https://example.com'],
|
||||
'Authorization',
|
||||
token => `Bearer ${token}`,
|
||||
);
|
||||
const inner = jest.fn();
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
// No token available
|
||||
tokenFunction.mockResolvedValueOnce({ token: undefined });
|
||||
await outer(new Request('https://example.com'));
|
||||
expect([...inner.mock.calls[0][0].headers.entries()]).toEqual([]);
|
||||
|
||||
// Supply a token, header gets added
|
||||
tokenFunction.mockResolvedValueOnce({ token: 'token' });
|
||||
await outer(new Request('https://example.com'));
|
||||
expect([...inner.mock.calls[1][0].headers.entries()]).toEqual([
|
||||
['authorization', 'Bearer token'],
|
||||
]);
|
||||
|
||||
// Token no longer available
|
||||
tokenFunction.mockResolvedValueOnce({ token: undefined });
|
||||
await outer(new Request('https://example.com'));
|
||||
expect([...inner.mock.calls[2][0].headers.entries()]).toEqual([]);
|
||||
});
|
||||
|
||||
it('does not overwrite an existing header with the same name', async () => {
|
||||
const identityApi = {
|
||||
getCredentials: () => ({ token: 'token' }),
|
||||
} as unknown as IdentityApi;
|
||||
|
||||
const middleware = new IdentityAuthInjectorFetchMiddleware(
|
||||
identityApi,
|
||||
['https://example.com'],
|
||||
'Authorization',
|
||||
token => `Bearer ${token}`,
|
||||
);
|
||||
const inner = jest.fn();
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
// No token available
|
||||
await outer(new Request('https://example.com'));
|
||||
expect([...inner.mock.calls[0][0].headers.entries()]).toEqual([
|
||||
['authorization', 'Bearer token'],
|
||||
]);
|
||||
|
||||
// Supply a token, header gets added
|
||||
await outer(
|
||||
new Request('https://example.com', {
|
||||
headers: { authorization: 'do-not-clobber' },
|
||||
}),
|
||||
);
|
||||
expect([...inner.mock.calls[1][0].headers.entries()]).toEqual([
|
||||
['authorization', 'do-not-clobber'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not affect requests outside the allowlist', async () => {
|
||||
const identityApi = {
|
||||
getCredentials: () => ({ token: 'token' }),
|
||||
} as unknown as IdentityApi;
|
||||
|
||||
const middleware = new IdentityAuthInjectorFetchMiddleware(
|
||||
identityApi,
|
||||
['https://example.com:8080/root'],
|
||||
'Authorization',
|
||||
token => `Bearer ${token}`,
|
||||
);
|
||||
|
||||
const inner = jest.fn();
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
await outer(new Request('https://example.com:8080/root'));
|
||||
await outer(new Request('https://example.com:8080/root/sub'));
|
||||
await outer(new Request('https://example.com:8080/root2'));
|
||||
await outer(new Request('https://example.com/root'));
|
||||
await outer(new Request('http://example.com:8080/root'));
|
||||
await outer(new Request('https://example.com/root'));
|
||||
|
||||
const no: string[][] = [];
|
||||
const yes: string[][] = [['authorization', 'Bearer token']];
|
||||
expect([...inner.mock.calls[0][0].headers.entries()]).toEqual(yes);
|
||||
expect([...inner.mock.calls[1][0].headers.entries()]).toEqual(yes);
|
||||
expect([...inner.mock.calls[2][0].headers.entries()]).toEqual(no);
|
||||
expect([...inner.mock.calls[3][0].headers.entries()]).toEqual(no);
|
||||
expect([...inner.mock.calls[4][0].headers.entries()]).toEqual(no);
|
||||
expect([...inner.mock.calls[5][0].headers.entries()]).toEqual(no);
|
||||
});
|
||||
});
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 { Config } from '@backstage/config';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { FetchMiddleware } from './types';
|
||||
|
||||
/**
|
||||
* A fetch middleware, which injects a Backstage token header when the user is
|
||||
* signed in.
|
||||
*/
|
||||
export class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {
|
||||
static create(options: {
|
||||
identityApi: IdentityApi;
|
||||
config?: Config;
|
||||
urlPrefixAllowlist?: string[];
|
||||
header?: {
|
||||
name: string;
|
||||
value: (backstageToken: string) => string;
|
||||
};
|
||||
}): IdentityAuthInjectorFetchMiddleware {
|
||||
const allowlist: string[] = [];
|
||||
if (options.urlPrefixAllowlist) {
|
||||
allowlist.push(...options.urlPrefixAllowlist);
|
||||
} else if (options.config) {
|
||||
allowlist.push(options.config.getString('backend.baseUrl'));
|
||||
}
|
||||
|
||||
const headerName = options.header?.name || 'authorization';
|
||||
const headerValue = options.header?.value || (token => `Bearer ${token}`);
|
||||
|
||||
return new IdentityAuthInjectorFetchMiddleware(
|
||||
options.identityApi,
|
||||
allowlist.map(prefix => prefix.replace(/\/$/, '')),
|
||||
headerName,
|
||||
headerValue,
|
||||
);
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly identityApi: IdentityApi,
|
||||
public readonly urlPrefixAllowlist: string[],
|
||||
public readonly headerName: string,
|
||||
public readonly headerValue: (pluginId: string) => string,
|
||||
) {}
|
||||
|
||||
apply(next: typeof fetch): typeof fetch {
|
||||
return async (input, init) => {
|
||||
// Skip this middleware if the header already exists, or if the URL
|
||||
// doesn't match any of the allowlist items, or if there was no token
|
||||
const request = new Request(input, init);
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
if (
|
||||
request.headers.get(this.headerName) ||
|
||||
!this.urlPrefixAllowlist.some(
|
||||
prefix =>
|
||||
request.url === prefix || request.url.startsWith(`${prefix}/`),
|
||||
) ||
|
||||
typeof token !== 'string' ||
|
||||
!token
|
||||
) {
|
||||
return next(input, init);
|
||||
}
|
||||
|
||||
request.headers.set(this.headerName, this.headerValue(token));
|
||||
return next(request);
|
||||
};
|
||||
}
|
||||
}
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { PluginProtocolResolverFetchMiddleware } from './PluginProtocolResolverFetchMiddleware';
|
||||
|
||||
describe('PluginProtocolResolverFetchMiddleware', () => {
|
||||
it.each([['https://passthrough.com/a']])(
|
||||
'passes through regular URLs, %p',
|
||||
async url => {
|
||||
const resolve = jest.fn();
|
||||
const discoveryApi = { getBaseUrl: resolve } as unknown as DiscoveryApi;
|
||||
const middleware = new PluginProtocolResolverFetchMiddleware(
|
||||
discoveryApi,
|
||||
);
|
||||
const inner = jest.fn();
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
await outer(url);
|
||||
expect(inner.mock.calls[0][0]).toBe(url);
|
||||
expect(resolve).not.toBeCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
[
|
||||
'plugin://my-plugin/sub/path',
|
||||
'my-plugin',
|
||||
'https://real.com/base',
|
||||
'https://real.com/base/sub/path',
|
||||
],
|
||||
[
|
||||
'plugin://my-plugin/sub/path/',
|
||||
'my-plugin',
|
||||
'https://real.com/base/',
|
||||
'https://real.com/base/sub/path/',
|
||||
],
|
||||
['plugin://x', 'x', 'http://real.com:8080', 'http://real.com:8080'],
|
||||
[
|
||||
'plugin://x/a/b?c=d&e=f#g',
|
||||
'x',
|
||||
'https://real.com/base',
|
||||
'https://real.com/base/a/b?c=d&e=f#g',
|
||||
],
|
||||
[
|
||||
'plugin://x?c=d&e=f#g',
|
||||
'x',
|
||||
'https://real.com:8080/base',
|
||||
'https://real.com:8080/base?c=d&e=f#g',
|
||||
],
|
||||
[
|
||||
'plugin://username:password@x?c=d&e=f#g',
|
||||
'x',
|
||||
'https://real.com:8080/base',
|
||||
'https://username:password@real.com:8080/base?c=d&e=f#g',
|
||||
],
|
||||
[
|
||||
'plugin://x?c=d&e=f#g',
|
||||
'x',
|
||||
'https://username:password@real.com:8080/base',
|
||||
'https://username:password@real.com:8080/base?c=d&e=f#g',
|
||||
],
|
||||
])(
|
||||
'resolves backstage URLs, %p',
|
||||
async (original, host, resolved, result) => {
|
||||
const resolve = jest.fn();
|
||||
const discoveryApi = { getBaseUrl: resolve } as unknown as DiscoveryApi;
|
||||
const middleware = new PluginProtocolResolverFetchMiddleware(
|
||||
discoveryApi,
|
||||
);
|
||||
const inner = jest.fn();
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
resolve.mockResolvedValueOnce(resolved);
|
||||
await outer(original);
|
||||
expect(inner.mock.calls[0][0]).toBe(result);
|
||||
expect(resolve).toHaveBeenLastCalledWith(host);
|
||||
},
|
||||
);
|
||||
});
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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 { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { FetchMiddleware } from './types';
|
||||
|
||||
function join(left: string, right: string): string {
|
||||
if (!right || right === '/') {
|
||||
return left;
|
||||
}
|
||||
|
||||
return `${left.replace(/\/$/, '')}/${right.replace(/^\//, '')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles translation from plugin://some-plugin-id/<path> to concrete http(s)
|
||||
* URLs.
|
||||
*/
|
||||
export class PluginProtocolResolverFetchMiddleware implements FetchMiddleware {
|
||||
constructor(private readonly discoveryApi: DiscoveryApi) {}
|
||||
|
||||
apply(next: typeof fetch): typeof fetch {
|
||||
return async (input, init) => {
|
||||
const request = new Request(input, init);
|
||||
const prefix = 'plugin://';
|
||||
|
||||
if (!request.url.startsWith(prefix)) {
|
||||
return next(input, init);
|
||||
}
|
||||
|
||||
// Switch to a known protocol, since browser URL parsing misbehaves wildly
|
||||
// on foreign protocols
|
||||
const { hostname, pathname, search, hash, username, password } = new URL(
|
||||
`http://${request.url.substring(prefix.length)}`,
|
||||
);
|
||||
|
||||
let base = await this.discoveryApi.getBaseUrl(hostname);
|
||||
if (username || password) {
|
||||
const baseUrl = new URL(base);
|
||||
const authority = `${username}${password ? `:${password}` : ''}@`;
|
||||
base = `${baseUrl.protocol}//${authority}${baseUrl.host}${baseUrl.pathname}`;
|
||||
}
|
||||
|
||||
const target = `${join(base, pathname)}${search}${hash}`;
|
||||
return next(target, request);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 { FetchApi } from '@backstage/core-plugin-api';
|
||||
import { FetchMiddleware } from './types';
|
||||
|
||||
/**
|
||||
* Builds a fetch API, based on the builtin fetch wrapped by a set of optional
|
||||
* middleware implementations that add behaviors.
|
||||
*
|
||||
* @remarks
|
||||
*
|
||||
* The middleware are applied in reverse order, i.e. the last one will be
|
||||
* "closest" to the base implementation. Passing in `[M1, M2, M3]` effectively
|
||||
* leads to `M1(M2(M3(baseImplementation)))`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function createFetchApi(options: {
|
||||
baseImplementation?: typeof fetch | undefined;
|
||||
middleware?: FetchMiddleware | FetchMiddleware[] | undefined;
|
||||
}): FetchApi {
|
||||
let result = options.baseImplementation || global.fetch;
|
||||
|
||||
const middleware = [options.middleware ?? []].flat().reverse();
|
||||
for (const m of middleware) {
|
||||
result = m.apply(result);
|
||||
}
|
||||
|
||||
return {
|
||||
fetch: result,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { createFetchApi } from './createFetchApi';
|
||||
export { FetchMiddlewares } from './FetchMiddlewares';
|
||||
export type { FetchMiddleware } from './types';
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A middleware that modifies the behavior of an ongoing fetch.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface FetchMiddleware {
|
||||
/**
|
||||
* Applies this middleware to an inner implementation.
|
||||
*
|
||||
* @param next - The next, inner, implementation, that this middleware shall
|
||||
* call out to as part of the request cycle.
|
||||
*/
|
||||
apply(next: typeof fetch): typeof fetch;
|
||||
}
|
||||
@@ -16,14 +16,14 @@
|
||||
|
||||
import {
|
||||
OAuthRequestApi,
|
||||
AuthRequesterOptions,
|
||||
OAuthRequesterOptions,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { OAuthRequestManager } from './OAuthRequestManager';
|
||||
|
||||
export default class MockOAuthApi implements OAuthRequestApi {
|
||||
private readonly real = new OAuthRequestManager();
|
||||
|
||||
createAuthRequester<T>(options: AuthRequesterOptions<T>) {
|
||||
createAuthRequester<T>(options: OAuthRequesterOptions<T>) {
|
||||
return this.real.createAuthRequester(options);
|
||||
}
|
||||
|
||||
|
||||
+15
-9
@@ -16,9 +16,9 @@
|
||||
|
||||
import {
|
||||
OAuthRequestApi,
|
||||
PendingAuthRequest,
|
||||
AuthRequester,
|
||||
AuthRequesterOptions,
|
||||
PendingOAuthRequest,
|
||||
OAuthRequester,
|
||||
OAuthRequesterOptions,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { Observable } from '@backstage/types';
|
||||
import { OAuthPendingRequests, PendingRequest } from './OAuthPendingRequests';
|
||||
@@ -34,11 +34,17 @@ import { BehaviorSubject } from '../../../lib/subjects';
|
||||
* @public
|
||||
*/
|
||||
export class OAuthRequestManager implements OAuthRequestApi {
|
||||
private readonly subject = new BehaviorSubject<PendingAuthRequest[]>([]);
|
||||
private currentRequests: PendingAuthRequest[] = [];
|
||||
private readonly subject = new BehaviorSubject<PendingOAuthRequest[]>([]);
|
||||
private currentRequests: PendingOAuthRequest[] = [];
|
||||
private handlerCount = 0;
|
||||
|
||||
createAuthRequester<T>(options: AuthRequesterOptions<T>): AuthRequester<T> {
|
||||
createAuthRequester<T>(options: OAuthRequesterOptions<T>): OAuthRequester<T> {
|
||||
if (!options.provider.id) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(
|
||||
'DEPRECATION WARNING: Not passing a provider id to createAuthRequester is deprecated, it will be required in the future',
|
||||
);
|
||||
}
|
||||
const handler = new OAuthPendingRequests<T>();
|
||||
|
||||
const index = this.handlerCount;
|
||||
@@ -67,8 +73,8 @@ export class OAuthRequestManager implements OAuthRequestApi {
|
||||
// Converts the pending request and popup options into a popup request that we can forward to subscribers.
|
||||
private makeAuthRequest(
|
||||
request: PendingRequest<any>,
|
||||
options: AuthRequesterOptions<any>,
|
||||
): PendingAuthRequest | undefined {
|
||||
options: OAuthRequesterOptions<any>,
|
||||
): PendingOAuthRequest | undefined {
|
||||
const { scopes } = request;
|
||||
if (!scopes) {
|
||||
return undefined;
|
||||
@@ -88,7 +94,7 @@ export class OAuthRequestManager implements OAuthRequestApi {
|
||||
};
|
||||
}
|
||||
|
||||
authRequest$(): Observable<PendingAuthRequest[]> {
|
||||
authRequest$(): Observable<PendingOAuthRequest[]> {
|
||||
return this.subject;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,23 @@ const DEFAULT_PROVIDER = {
|
||||
* Implements the OAuth flow to Auth0 products.
|
||||
*
|
||||
* @public
|
||||
* @deprecated Use {@link OAuth2} instead
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* ```ts
|
||||
* OAuth2.create({
|
||||
* discoveryApi,
|
||||
* oauthRequestApi,
|
||||
* provider: {
|
||||
* id: 'auth0',
|
||||
* title: 'Auth0',
|
||||
* icon: () => null,
|
||||
* },
|
||||
* defaultScopes: ['openid', 'email', 'profile'],
|
||||
* environment: configApi.getOptionalString('auth.environment'),
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export default class Auth0Auth {
|
||||
static create(options: OAuthApiCreateOptions): typeof auth0AuthApiRef.T {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import {
|
||||
oneloginAuthApiRef,
|
||||
OAuthRequestApi,
|
||||
AuthProvider,
|
||||
AuthProviderInfo,
|
||||
DiscoveryApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { OAuth2 } from '../oauth2';
|
||||
@@ -30,7 +30,7 @@ export type OneLoginAuthCreateOptions = {
|
||||
discoveryApi: DiscoveryApi;
|
||||
oauthRequestApi: OAuthRequestApi;
|
||||
environment?: string;
|
||||
provider?: AuthProvider & { id: string };
|
||||
provider?: AuthProviderInfo;
|
||||
};
|
||||
|
||||
const DEFAULT_PROVIDER = {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
AuthProvider,
|
||||
AuthProviderInfo,
|
||||
DiscoveryApi,
|
||||
OAuthRequestApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
@@ -36,5 +36,5 @@ export type OAuthApiCreateOptions = AuthApiCreateOptions & {
|
||||
export type AuthApiCreateOptions = {
|
||||
discoveryApi: DiscoveryApi;
|
||||
environment?: string;
|
||||
provider?: AuthProvider & { id: string };
|
||||
provider?: AuthProviderInfo;
|
||||
};
|
||||
|
||||
@@ -27,5 +27,6 @@ export * from './ConfigApi';
|
||||
export * from './DiscoveryApi';
|
||||
export * from './ErrorApi';
|
||||
export * from './FeatureFlagsApi';
|
||||
export * from './FetchApi';
|
||||
export * from './OAuthRequestApi';
|
||||
export * from './StorageApi';
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
AuthRequester,
|
||||
OAuthRequester,
|
||||
OAuthRequestApi,
|
||||
AuthProvider,
|
||||
AuthProviderInfo,
|
||||
DiscoveryApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { showLoginPopup } from '../loginPopup';
|
||||
@@ -36,7 +36,7 @@ type Options<AuthSession> = {
|
||||
* Information about the auth provider to be shown to the user.
|
||||
* The ID Must match the backend auth plugin configuration, for example 'google'.
|
||||
*/
|
||||
provider: AuthProvider & { id: string };
|
||||
provider: AuthProviderInfo;
|
||||
/**
|
||||
* API used to instantiate an auth requester.
|
||||
*/
|
||||
@@ -65,9 +65,9 @@ export class DefaultAuthConnector<AuthSession>
|
||||
{
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly environment: string;
|
||||
private readonly provider: AuthProvider & { id: string };
|
||||
private readonly provider: AuthProviderInfo;
|
||||
private readonly joinScopesFunc: (scopes: Set<string>) => string;
|
||||
private readonly authRequester: AuthRequester<AuthSession>;
|
||||
private readonly authRequester: OAuthRequester<AuthSession>;
|
||||
private readonly sessionTransform: (response: any) => Promise<AuthSession>;
|
||||
|
||||
constructor(options: Options<AuthSession>) {
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { AuthProvider, DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { AuthProviderInfo, DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { showLoginPopup } from '../loginPopup';
|
||||
|
||||
type Options = {
|
||||
discoveryApi: DiscoveryApi;
|
||||
environment?: string;
|
||||
provider: AuthProvider & { id: string };
|
||||
provider: AuthProviderInfo;
|
||||
};
|
||||
export class DirectAuthConnector<DirectAuthResponse> {
|
||||
private readonly discoveryApi: DiscoveryApi;
|
||||
private readonly environment: string | undefined;
|
||||
private readonly provider: AuthProvider & { id: string };
|
||||
private readonly provider: AuthProviderInfo;
|
||||
|
||||
constructor(options: Options) {
|
||||
const { discoveryApi, environment, provider } = options;
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/core-components
|
||||
|
||||
## 0.8.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 2c17e5b073: Items in `<SidebarSubmenu>` are now only active when their full path is active (including search parameters).
|
||||
- 9d6503e86c: Switched out usage of deprecated `OAuthRequestApi` types from `@backstage/core-plugin-api`.
|
||||
- 1680a1c5ac: Add Missing Override Components Type for SidebarSpace, SidebarSpacer, and SidebarDivider Components.
|
||||
- Updated dependencies
|
||||
- @backstage/core-plugin-api@0.3.1
|
||||
|
||||
## 0.8.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -814,13 +814,10 @@ export type ResponseErrorPanelClassKey = 'text' | 'divider';
|
||||
export function RoutedTabs(props: { routes: SubRoute_2[] }): JSX.Element;
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "SelectProps" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "SelectComponent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export function Select(props: SelectProps): JSX.Element;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SelectClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type SelectClassKey =
|
||||
| 'formControl'
|
||||
@@ -830,11 +827,18 @@ export type SelectClassKey =
|
||||
| 'checkbox'
|
||||
| 'root';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SelectInputBaseClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type SelectedItems = string | string[] | number | number[];
|
||||
|
||||
// @public (undocumented)
|
||||
export type SelectInputBaseClassKey = 'root' | 'input';
|
||||
|
||||
// @public (undocumented)
|
||||
export type SelectItem = {
|
||||
label: string;
|
||||
value: string | number;
|
||||
};
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts
|
||||
// Warning: (ae-missing-release-tag) "Sidebar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
@@ -1152,6 +1156,11 @@ export const SidebarDivider: React_2.ComponentType<
|
||||
}
|
||||
>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarDividerClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type SidebarDividerClassKey = 'root';
|
||||
|
||||
// @public
|
||||
export const SidebarExpandButton: () => JSX.Element | null;
|
||||
|
||||
@@ -1760,6 +1769,11 @@ export const SidebarSpace: React_2.ComponentType<
|
||||
}
|
||||
>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarSpaceClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type SidebarSpaceClassKey = 'root';
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarSpacer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
@@ -2029,6 +2043,11 @@ export const SidebarSpacer: React_2.ComponentType<
|
||||
}
|
||||
>;
|
||||
|
||||
// Warning: (ae-missing-release-tag) "SidebarSpacerClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
|
||||
//
|
||||
// @public (undocumented)
|
||||
export type SidebarSpacerClassKey = 'root';
|
||||
|
||||
// @public
|
||||
export const SidebarSubmenu: (props: SidebarSubmenuProps) => JSX.Element;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@backstage/core-components",
|
||||
"description": "Core components used by Backstage plugins and apps",
|
||||
"version": "0.8.0",
|
||||
"version": "0.8.1",
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
@@ -30,7 +30,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.11",
|
||||
"@backstage/core-plugin-api": "^0.3.0",
|
||||
"@backstage/core-plugin-api": "^0.3.1",
|
||||
"@backstage/errors": "^0.1.5",
|
||||
"@backstage/theme": "^0.2.14",
|
||||
"@material-table/core": "^3.1.0",
|
||||
@@ -72,8 +72,8 @@
|
||||
"react-dom": "^16.13.1 || ^17.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/core-app-api": "^0.2.0",
|
||||
"@backstage/cli": "^0.10.1",
|
||||
"@backstage/core-app-api": "^0.2.1",
|
||||
"@backstage/cli": "^0.10.2",
|
||||
"@backstage/test-utils": "^0.1.24",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
|
||||
@@ -22,7 +22,7 @@ import Typography from '@material-ui/core/Typography';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import React, { useState } from 'react';
|
||||
import { isError } from '@backstage/errors';
|
||||
import { PendingAuthRequest } from '@backstage/core-plugin-api';
|
||||
import { PendingOAuthRequest } from '@backstage/core-plugin-api';
|
||||
|
||||
export type LoginRequestListItemClassKey = 'root';
|
||||
|
||||
@@ -36,7 +36,7 @@ const useItemStyles = makeStyles<Theme>(
|
||||
);
|
||||
|
||||
type RowProps = {
|
||||
request: PendingAuthRequest;
|
||||
request: PendingOAuthRequest;
|
||||
busy: boolean;
|
||||
setBusy: (busy: boolean) => void;
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user