Merge branch 'backstage:master' into feature/bitbucket-server-auth-provider
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-gitlab': patch
|
||||
---
|
||||
|
||||
The config now consistently uses the term 'instance' to refer to a single GitLab API host.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-plugin-api': minor
|
||||
---
|
||||
|
||||
**BREAKING**: Split out the hook for both lifecycle services so that the first parameter of `addShutdownHook` is the hook function, and the second is the options.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-plugin-api': minor
|
||||
---
|
||||
|
||||
**BREAKING**: The plugin ID option passed to `createBackendPlugin` is now `pluginId`, rather than just `id`. This is to make it match `createBackendModule` more closely.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Removed unnecessary `@backstage/cli` dependency.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Replaced several monorepo lint rules with new rules from `@backstage/eslint-plugin`. See the [README](https://github.com/import-js/eslint-plugin-import/blob/main/packages/eslint-plugin/README.md) for a full list of rules.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Added explicit return type signature for `mockServices.config()`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-dev-utils': minor
|
||||
---
|
||||
|
||||
Introduced a new package for backend development utilities. Similar to how `@backstage/dev-utils` is used in the frontend.
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
'@backstage/backend-app-api': patch
|
||||
'@backstage/backend-common': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
---
|
||||
|
||||
Updated usage of the lifecycle service.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/codemods': patch
|
||||
---
|
||||
|
||||
Moved `commander` to being a regular dependency.
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
'@backstage/plugin-tech-radar': patch
|
||||
---
|
||||
|
||||
Deprecate `RadarEntry.url` - use `RadarEntry.links` instead
|
||||
|
||||
```diff
|
||||
- url: 'https://www.javascript.com/',
|
||||
key: 'javascript',
|
||||
id: 'javascript',
|
||||
title: 'JavaScript',
|
||||
quadrant: 'languages',
|
||||
links: [
|
||||
+ {
|
||||
+ url: 'https://www.javascript.com/',
|
||||
+ title: 'Learn more',
|
||||
+ },
|
||||
],
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-search-backend': patch
|
||||
---
|
||||
|
||||
Change the router's response to include the error message instead of its object type in case it fails during a search query because the messages have more info.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-plugin-api': patch
|
||||
---
|
||||
|
||||
Aligned opaque type markers to all use a `$$type` property with namespacing.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
'@backstage/plugin-user-settings-backend': patch
|
||||
'@backstage/plugin-scaffolder-backend': patch
|
||||
'@backstage/plugin-periskop-backend': patch
|
||||
'@backstage/backend-common': patch
|
||||
'@backstage/plugin-catalog-backend': patch
|
||||
'@backstage/plugin-bazaar-backend': patch
|
||||
'@backstage/plugin-events-backend': patch
|
||||
'@backstage/plugin-kafka-backend': patch
|
||||
'@backstage/plugin-proxy-backend': patch
|
||||
'@backstage/plugin-app-backend': patch
|
||||
---
|
||||
|
||||
Updated usage of `createBackendPlugin`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-app-api': patch
|
||||
---
|
||||
|
||||
Updated database factory to pass service deps required for restoring database state during development.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-common': patch
|
||||
---
|
||||
|
||||
The `DatabaseManager.forPlugin` method now accepts additional service dependencies. There is no need to update existing code to pass these dependencies.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/repo-tools': patch
|
||||
---
|
||||
|
||||
Packages without a declared `backstage.role` are now ignored.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-app-api': patch
|
||||
---
|
||||
|
||||
The shutdown signal handlers are now installed as part of the backend instance rather than the lifecycle service, and explicitly cause the process to exit.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-msgraph': patch
|
||||
---
|
||||
|
||||
Fixed a bug reading the `user.select` field expected from the `app-config.yaml` configuration
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-circleci': patch
|
||||
---
|
||||
|
||||
Making workflow a link
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-incremental-ingestion': patch
|
||||
---
|
||||
|
||||
Updated README to include newer API options for incremental entity providers
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Removed the `ref` from all `mockServices`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/eslint-plugin': minor
|
||||
---
|
||||
|
||||
Added a new ESLint plugin with common rules for Backstage projects. See the [README](https://github.com/import-js/eslint-plugin-import/blob/main/packages/eslint-plugin/README.md) for more details.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/cli': patch
|
||||
---
|
||||
|
||||
Added an experimental mode for the `package start` command for backend packages. Enabled by setting `EXPERIMENTAL_BACKEND_START`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Updated `packages/app/cypress/.eslintrc.json` to remove the unnecessary `import/no-extraneous-dependencies` rule.
|
||||
@@ -9,6 +9,7 @@ Airbrake
|
||||
Airbrakes
|
||||
Alaria
|
||||
Alef
|
||||
allowlisted
|
||||
Anddddd
|
||||
Apdex
|
||||
api
|
||||
@@ -23,6 +24,7 @@ Avro
|
||||
backend's
|
||||
backported
|
||||
backporting
|
||||
BEPs
|
||||
Bigtable
|
||||
Billett
|
||||
bitbucket
|
||||
@@ -95,6 +97,7 @@ dockerfiles
|
||||
Dockerize
|
||||
dockerode
|
||||
Docusaurus
|
||||
Dominik
|
||||
DOMPurify
|
||||
don'ts
|
||||
dynatrace
|
||||
@@ -119,6 +122,7 @@ Firekube
|
||||
Firestore
|
||||
Fiverr
|
||||
Francesco
|
||||
Frontside
|
||||
gerrit
|
||||
Gerrit
|
||||
gitbeaker
|
||||
@@ -138,6 +142,7 @@ Hackathons
|
||||
haproxy
|
||||
hardcoded
|
||||
Helidon
|
||||
Henneke
|
||||
Heroku
|
||||
hoc
|
||||
horizontalpodautoscalers
|
||||
@@ -167,12 +172,14 @@ jsonnet
|
||||
jsx
|
||||
JWTs
|
||||
Kaewkasi
|
||||
Kaswell
|
||||
Keyv
|
||||
Knex
|
||||
KPIs
|
||||
Kuang
|
||||
kubectl
|
||||
kubernetes
|
||||
kubernetes
|
||||
Lainfiesta
|
||||
ldap
|
||||
learnings
|
||||
Leasot
|
||||
@@ -336,6 +343,7 @@ subfolders
|
||||
subheader
|
||||
subheaders
|
||||
subkey
|
||||
subpath
|
||||
subroutes
|
||||
subtree
|
||||
superfences
|
||||
@@ -412,10 +420,3 @@ zod
|
||||
Zolotusky
|
||||
zoomable
|
||||
zsh
|
||||
Lainfiesta
|
||||
allowlisted
|
||||
Dominik
|
||||
Henneke
|
||||
Kuang
|
||||
Frontside
|
||||
Kaswell
|
||||
@@ -28,13 +28,13 @@ jobs:
|
||||
ref: 'refs/pull/${{ github.event.pull_request.number }}/merge'
|
||||
- name: fetch base
|
||||
run: git fetch --depth 1 origin ${{ github.base_ref }}
|
||||
- uses: backstage/actions/changeset-feedback@v0.5.13
|
||||
- uses: backstage/actions/changeset-feedback@v0.6.0
|
||||
name: Generate feedback
|
||||
with:
|
||||
diffRef: 'origin/master'
|
||||
diff-ref: 'origin/master'
|
||||
marker: <!-- changeset-feedback -->
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
botUsername: backstage-goalie[bot]
|
||||
bot-username: backstage-goalie[bot]
|
||||
app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }}
|
||||
private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }}
|
||||
installation-id: ${{ secrets.BACKSTAGE_GOALIE_INSTALLATION_ID }}
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
@@ -181,7 +181,7 @@ jobs:
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ jobs:
|
||||
cron:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: backstage/actions/cron@v0.5.13
|
||||
- uses: backstage/actions/cron@v0.6.0
|
||||
with:
|
||||
app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }}
|
||||
private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }}
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
@@ -145,7 +145,7 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
|
||||
@@ -10,4 +10,4 @@ jobs:
|
||||
if: github.repository == 'backstage/backstage'
|
||||
steps:
|
||||
- name: Issue sync
|
||||
uses: backstage/actions/issue-sync@v0.5.13
|
||||
uses: backstage/actions/issue-sync@v0.6.0
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
const prNumber = artifact.name.slice('pr_number-'.length)
|
||||
core.setOutput('pr-number', prNumber);
|
||||
|
||||
- uses: backstage/actions/re-review@v0.5.13
|
||||
- uses: backstage/actions/re-review@v0.6.0
|
||||
with:
|
||||
app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }}
|
||||
private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }}
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
if: github.repository == 'backstage/backstage' && ( github.event.pull_request || github.event.issue.pull_request )
|
||||
steps:
|
||||
- name: PR sync
|
||||
uses: backstage/actions/pr-sync@v0.5.13
|
||||
uses: backstage/actions/pr-sync@v0.6.0
|
||||
with:
|
||||
github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
|
||||
app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }}
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
registry-url: https://registry.npmjs.org/
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: linux-v16
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@v0.5.13
|
||||
uses: backstage/actions/yarn-install@v0.6.0
|
||||
with:
|
||||
cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }}
|
||||
- name: storybook yarn install
|
||||
|
||||
@@ -2,15 +2,6 @@ module.exports = {
|
||||
extends: [require.resolve('@backstage/cli/config/eslint.backend')],
|
||||
rules: {
|
||||
'no-console': 0,
|
||||
'import/no-extraneous-dependencies': [
|
||||
'error',
|
||||
{
|
||||
devDependencies: true,
|
||||
optionalDependencies: false,
|
||||
peerDependencies: false,
|
||||
bundledDependencies: false,
|
||||
},
|
||||
],
|
||||
'jest/valid-expect': 'off',
|
||||
'jest/expect-expect': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
export const examplePlugin = createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -53,7 +53,7 @@ export interface ExamplePluginOptions {
|
||||
|
||||
export const examplePlugin = createBackendPlugin(
|
||||
(options?: ExamplePluginOptions) => ({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
|
||||
@@ -44,7 +44,7 @@ class ActionsExtension implements ScaffolderActionsExtensionPoint {
|
||||
|
||||
export const scaffolderPlugin = createBackendPlugin(
|
||||
{
|
||||
id: 'scaffolder',
|
||||
pluginId: 'scaffolder',
|
||||
register(env) {
|
||||
const actionsExtensions = new ActionsExtension();
|
||||
env.registerExtensionPoint(
|
||||
|
||||
@@ -21,7 +21,7 @@ Example:
|
||||
|
||||
```ts
|
||||
export const catalogPlugin = createBackendPlugin({
|
||||
id: 'catalog',
|
||||
pluginId: 'catalog',
|
||||
...
|
||||
})
|
||||
```
|
||||
@@ -36,7 +36,7 @@ export const catalogPlugin = createBackendPlugin({
|
||||
Example:
|
||||
|
||||
```ts
|
||||
export const catalogModuleGithubEntityProvider = createBackendPlugin({
|
||||
export const catalogModuleGithubEntityProvider = createBackendModule({
|
||||
pluginId: 'catalog',
|
||||
moduleId: 'githubEntityProvider',
|
||||
...
|
||||
|
||||
@@ -36,7 +36,7 @@ import {
|
||||
import { createExampleRouter } from './router';
|
||||
|
||||
export const examplePlugin = createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -183,7 +183,7 @@ that needs to be different across environments.
|
||||
import { coreServices } from '@backstage/backend-plugin-api';
|
||||
|
||||
export const examplePlugin = createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: { config: coreServices.config },
|
||||
@@ -216,7 +216,7 @@ export interface ExampleOptions {
|
||||
|
||||
export const examplePlugin = createBackendPlugin(
|
||||
(options?: ExampleOptions) => ({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
|
||||
@@ -46,7 +46,7 @@ import { Router } from 'express';
|
||||
import { KubernetesBuilder } from './KubernetesBuilder';
|
||||
|
||||
export const kubernetesPlugin = createBackendPlugin({
|
||||
id: 'kubernetes',
|
||||
pluginId: 'kubernetes',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -95,7 +95,7 @@ export interface KubernetesOptions {
|
||||
}
|
||||
|
||||
const kubernetesPlugin = createBackendPlugin((options: KubernetesOptions) => ({
|
||||
id: 'kubernetes',
|
||||
pluginId: 'kubernetes',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -182,7 +182,7 @@ class ClusterSupplier implements KubernetesClusterSupplierExtensionPoint {
|
||||
}
|
||||
|
||||
export const kubernetesPlugin = createBackendPlugin({
|
||||
id: 'kubernetes',
|
||||
pluginId: 'kubernetes',
|
||||
register(env) {
|
||||
const extensionPoint = new ClusterSupplier();
|
||||
// We register the extension point with the backend, which allows modules to
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
import { Router } from 'express';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: { http: coreServices.httpRouter },
|
||||
@@ -86,7 +86,7 @@ import {
|
||||
import { Router } from 'express';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -157,7 +157,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -215,7 +215,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -288,7 +288,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -327,7 +327,7 @@ import {
|
||||
import { resolvePackagePath } from '@backstage/backend-common';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -366,7 +366,7 @@ import {
|
||||
import { fetch } from 'node-fetch';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -397,7 +397,7 @@ import {
|
||||
import { Router } from 'express';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -465,7 +465,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -479,10 +479,7 @@ createBackendPlugin({
|
||||
// do some other stuff.
|
||||
}, 1000);
|
||||
|
||||
lifecycle.addShutdownHook({
|
||||
fn: () => clearInterval(interval),
|
||||
logger,
|
||||
});
|
||||
lifecycle.addShutdownHook(() => clearInterval(interval));
|
||||
},
|
||||
});
|
||||
},
|
||||
@@ -499,17 +496,19 @@ The following example shows how to override the default implementation of the li
|
||||
|
||||
```ts
|
||||
class MyCustomLifecycleService implements RootLifecycleService {
|
||||
constructor(private readonly logger: LoggerService) {
|
||||
['SIGKILL', 'SIGTERM'].map(signal =>
|
||||
process.on(signal, () => this.shutdown()),
|
||||
);
|
||||
}
|
||||
constructor(private readonly logger: LoggerService) {}
|
||||
|
||||
#isCalled = false;
|
||||
#shutdownTasks: Array<LifecycleServiceShutdownHook> = [];
|
||||
#shutdownTasks: Array<{
|
||||
hook: LifecycleServiceShutdownHook;
|
||||
options?: LifecycleServiceShutdownOptions;
|
||||
}> = [];
|
||||
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void {
|
||||
this.#shutdownTasks.push(options);
|
||||
addShutdownHook(
|
||||
hook: LifecycleServiceShutdownHook,
|
||||
options?: LifecycleServiceShutdownOptions,
|
||||
): void {
|
||||
this.#shutdownTasks.push({ hook, options });
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
@@ -520,10 +519,10 @@ class MyCustomLifecycleService implements RootLifecycleService {
|
||||
|
||||
this.logger.info(`Running ${this.#shutdownTasks.length} shutdown tasks...`);
|
||||
await Promise.all(
|
||||
this.#shutdownTasks.map(async hook => {
|
||||
const { logger = this.logger } = hook;
|
||||
this.#shutdownTasks.map(async ({ hook, options }) => {
|
||||
const logger = options?.logger ?? this.logger;
|
||||
try {
|
||||
await hook.fn();
|
||||
await hook();
|
||||
logger.info(`Shutdown hook succeeded`);
|
||||
} catch (error) {
|
||||
logger.error(`Shutdown hook failed, ${error}`);
|
||||
@@ -564,7 +563,7 @@ import {
|
||||
import { Router } from 'express';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -613,7 +612,7 @@ import {
|
||||
import { fetch } from 'node-fetch';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
@@ -652,7 +651,7 @@ import {
|
||||
import os from 'os';
|
||||
|
||||
createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
|
||||
@@ -28,3 +28,5 @@ The configuration is a structure with two elements:
|
||||
|
||||
- `host`: The DevOps host; only `dev.azure.com` is supported.
|
||||
- `token` (optional): A personal access token as expected by Azure DevOps.
|
||||
|
||||
> Note: The token should just be provided as the raw token generated by Azure DevOps using the format `raw_token` with no base64 encoding. Formatting and base64'ing is handled by dependent libraries handling the Azure DevOps API
|
||||
|
||||
@@ -22,7 +22,7 @@ catalog:
|
||||
yourProviderId:
|
||||
host: gitlab-host # Identifies one of the hosts set up in the integrations
|
||||
branch: main # Optional. Uses `master` as default
|
||||
group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole project will be scanned
|
||||
group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole instance will be scanned
|
||||
entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml`
|
||||
projectPattern: /[\s\S]*/ # Optional. Filters found projects based on provided patter. Defaults to `/[\s\S]*/`, what means to not filter anything
|
||||
schedule: # optional; same options as in TaskScheduleDefinition
|
||||
|
||||
@@ -48,7 +48,7 @@ The source code is available here:
|
||||
|
||||
Create a new `packages/backend/src/plugins/todolist.ts` with the following content:
|
||||
|
||||
```javascript
|
||||
```typescript
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { createRouter } from '@internal/plugin-todo-list-backend';
|
||||
import { Router } from 'express';
|
||||
|
||||
@@ -146,7 +146,7 @@ In order to test the logic above, the integrators of your backstage instance nee
|
||||
- async handle(): Promise<PolicyDecision> {
|
||||
+ async handle(
|
||||
+ request: PolicyQuery,
|
||||
+ user?: BackstageIdentityResponse,
|
||||
+ _user?: BackstageIdentityResponse,
|
||||
+ ): Promise<PolicyDecision> {
|
||||
+ if (isPermission(request.permission, todoListCreatePermission)) {
|
||||
+ return {
|
||||
@@ -204,7 +204,7 @@ First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`:
|
||||
const router = await createRouter({
|
||||
logger: getVoidLogger(),
|
||||
identity: {} as DefaultIdentityClient,
|
||||
+ permissions: toPermissionEvaluator,
|
||||
+ permissions: permissionEvaluator,
|
||||
});
|
||||
app = express().use(router);
|
||||
});
|
||||
@@ -235,7 +235,7 @@ Then we want to update the `plugins/todo-list-backend/src/service/standaloneServ
|
||||
+ ServerTokenManager,
|
||||
} from '@backstage/backend-common';
|
||||
import { DefaultIdentityClient } from '@backstage/plugin-auth-node';
|
||||
import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
+ import { ServerPermissionClient } from '@backstage/plugin-permission-node';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
import { createRouter } from './router';
|
||||
@@ -285,4 +285,44 @@ Then we want to update the `plugins/todo-list-backend/src/service/standaloneServ
|
||||
module.hot?.accept();
|
||||
```
|
||||
|
||||
Finally, we need to update `plugins/todo-list-backend/src/plugin.ts`:
|
||||
|
||||
```diff
|
||||
import { loggerToWinstonLogger } from '@backstage/backend-common';
|
||||
import {
|
||||
coreServices,
|
||||
createBackendPlugin,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { createRouter } from './service/router';
|
||||
|
||||
/**
|
||||
* The example TODO list backend plugin.
|
||||
*
|
||||
* @alpha
|
||||
*/
|
||||
export const exampleTodoListPlugin = createBackendPlugin({
|
||||
pluginId: 'exampleTodoList',
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: {
|
||||
identity: coreServices.identity,
|
||||
logger: coreServices.logger,
|
||||
httpRouter: coreServices.httpRouter,
|
||||
+ permissions: coreServices.permissions,
|
||||
},
|
||||
- async init({ identity, logger, httpRouter }) {
|
||||
+ async init({ identity, logger, httpRouter, permissions }) {
|
||||
httpRouter.use(
|
||||
await createRouter({
|
||||
identity,
|
||||
logger: loggerToWinstonLogger(logger),
|
||||
permissions,
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Now when you run `yarn tsc` you should have no more errors.
|
||||
|
||||
@@ -76,7 +76,7 @@ This enables decisions based on characteristics of the resource, but it's import
|
||||
Install the missing module:
|
||||
|
||||
```
|
||||
$ yarn workspace @internal/plugin-todo-list-backend add @backstage/plugin-permission-node
|
||||
$ yarn workspace @internal/plugin-todo-list-backend add @backstage/plugin-permission-node zod
|
||||
```
|
||||
|
||||
Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code:
|
||||
@@ -89,7 +89,8 @@ import { Todo, TodoFilter } from './todos';
|
||||
|
||||
export const createTodoListPermissionRule = makeCreatePermissionRule<
|
||||
Todo,
|
||||
TodoFilter
|
||||
TodoFilter,
|
||||
typeof TODO_LIST_RESOURCE_TYPE
|
||||
>();
|
||||
|
||||
export const isOwner = createTodoListPermissionRule({
|
||||
@@ -97,8 +98,8 @@ export const isOwner = createTodoListPermissionRule({
|
||||
description: 'Should allow only if the todo belongs to the user',
|
||||
resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
paramsSchema: z.object({
|
||||
userId: z.string().describe('User ID to match on the resource')
|
||||
})
|
||||
userId: z.string().describe('User ID to match on the resource'),
|
||||
}),
|
||||
apply: (resource: Todo, { userId }) => {
|
||||
return resource.author === userId;
|
||||
},
|
||||
@@ -187,6 +188,7 @@ Make sure `todoListConditions` and `createTodoListConditionalDecision` are expor
|
||||
```diff
|
||||
export * from './service/router';
|
||||
+ export * from './conditionExports';
|
||||
export { exampleTodoListPlugin } from './plugin';
|
||||
```
|
||||
|
||||
## Test the authorized update endpoint
|
||||
@@ -209,7 +211,6 @@ Let's go back to the permission policy's handle function and try to authorize ou
|
||||
+ import {
|
||||
+ todoListCreatePermission,
|
||||
+ todoListUpdatePermission,
|
||||
+ TODO_LIST_RESOURCE_TYPE,
|
||||
+ } from '@internal/plugin-todo-list-common';
|
||||
+ import {
|
||||
+ todoListConditions,
|
||||
@@ -217,7 +218,11 @@ Let's go back to the permission policy's handle function and try to authorize ou
|
||||
+ } from '@internal/plugin-todo-list-backend';
|
||||
|
||||
...
|
||||
|
||||
async handle(
|
||||
request: PolicyQuery,
|
||||
- _user?: BackstageIdentityResponse,
|
||||
+ user?: BackstageIdentityResponse,
|
||||
): Promise<PolicyDecision> {
|
||||
if (isPermission(request.permission, todoListCreatePermission)) {
|
||||
return {
|
||||
result: AuthorizeResult.ALLOW,
|
||||
@@ -236,6 +241,7 @@ Let's go back to the permission policy's handle function and try to authorize ou
|
||||
return {
|
||||
result: AuthorizeResult.ALLOW,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
For any incoming update requests, we now return a _Conditional Decision_. We are saying:
|
||||
|
||||
@@ -15,7 +15,7 @@ One possible solution may leverage the batching functionality to authorize all o
|
||||
- res.json(getAll())
|
||||
+ const items = getAll();
|
||||
+ const decisions = await permissions.authorize(
|
||||
+ items.map(({ id }) => ({ permission: todosListRead, resourceRef: id })),
|
||||
+ items.map(({ id }) => ({ permission: todoListReadPermission, resourceRef: id })),
|
||||
+ );
|
||||
|
||||
+ const filteredItems = decisions.filter(
|
||||
@@ -53,7 +53,7 @@ Let's add another permission to the plugin.
|
||||
resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
});
|
||||
+
|
||||
+ export const todosListRead = createPermission({
|
||||
+ export const todoListReadPermission = createPermission({
|
||||
+ name: 'todos.list.read',
|
||||
+ attributes: { action: 'read' },
|
||||
+ resourceType: TODO_LIST_RESOURCE_TYPE,
|
||||
@@ -81,10 +81,9 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
|
||||
import {
|
||||
todosListCreate,
|
||||
todosListUpdate,
|
||||
+ todosListRead,
|
||||
+ todoListReadPermission,
|
||||
TODO_LIST_RESOURCE_TYPE,
|
||||
} from './permissions';
|
||||
+ import { rules } from './rules';
|
||||
|
||||
+ const transformConditions: ConditionTransformer<TodoFilter> = createConditionTransformer(Object.values(rules));
|
||||
|
||||
@@ -95,7 +94,7 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
|
||||
+ );
|
||||
+
|
||||
+ const decision = (
|
||||
+ await permissions.authorizeConditional([{ permission: todosListRead }], {
|
||||
+ await permissions.authorizeConditional([{ permission: todoListReadPermission }], {
|
||||
+ token,
|
||||
+ })
|
||||
+ )[0];
|
||||
@@ -110,7 +109,6 @@ So far we've only used the `PermissionEvaluator.authorize` method, which will ev
|
||||
+ } else {
|
||||
+ res.json(getAll());
|
||||
+ }
|
||||
+ }
|
||||
- res.json(getAll());
|
||||
});
|
||||
```
|
||||
@@ -121,7 +119,7 @@ Since `TodoFilter` used in our plugin matches the structure of the conditions ob
|
||||
|
||||
## Test the authorized read endpoint
|
||||
|
||||
Let's update our permission policy to return a conditional result whenever a `todosListRead` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission.
|
||||
Let's update our permission policy to return a conditional result whenever a `todoListReadPermission` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission.
|
||||
|
||||
```diff
|
||||
// packages/backend/src/plugins/permission.ts
|
||||
@@ -132,7 +130,6 @@ import {
|
||||
todoListCreatePermission,
|
||||
todoListUpdatePermission,
|
||||
+ todoListReadPermission,
|
||||
TODO_LIST_RESOURCE_TYPE,
|
||||
} from '@internal/plugin-todo-list-common';
|
||||
|
||||
...
|
||||
|
||||
@@ -56,7 +56,11 @@ Let's make the following changes in `plugins/todo-list/src/components/TodoListPa
|
||||
- Add
|
||||
- </Button>
|
||||
+ {!loadingPermission && (
|
||||
+ <Button disabled={!canAddTodo} variant="contained" onClick={handleAdd}>
|
||||
+ <Button
|
||||
+ disabled={!canAddTodo}
|
||||
+ variant="contained"
|
||||
+ onClick={() => onAdd(title.current)}
|
||||
+ >
|
||||
+ Add
|
||||
+ </Button>
|
||||
+ )}
|
||||
@@ -118,7 +122,10 @@ Providing a disabled state can be a helpful signal to users, but there may be ca
|
||||
- <Grid item>
|
||||
- <AddTodo onAdd={handleAdd} />
|
||||
- </Grid>
|
||||
+ <RequirePermission permission={todoListCreatePermission} errorPage={<></>}>
|
||||
+ <RequirePermission
|
||||
+ permission={todoListCreatePermission}
|
||||
+ errorPage={<></>}
|
||||
+ >
|
||||
+ <Grid item>
|
||||
+ <AddTodo onAdd={handleAdd} />
|
||||
+ </Grid>
|
||||
@@ -149,11 +156,15 @@ Providing a disabled state can be a helpful signal to users, but there may be ca
|
||||
onChange={e => (title.current = e.target.value)}
|
||||
/>
|
||||
- {!loadingPermission && (
|
||||
- <Button disabled={!canAddTodo} variant="contained" onClick={handleAdd}>
|
||||
- <Button
|
||||
- disabled={!canAddTodo}
|
||||
- variant="contained"
|
||||
- onClick={() => onAdd(title.current)}
|
||||
- >
|
||||
- Add
|
||||
- </Button>
|
||||
- )}
|
||||
+ <Button variant="contained" onClick={handleAdd}>
|
||||
+ <Button variant="contained" onClick={() => onAdd(title.current)}>
|
||||
+ Add
|
||||
+ </Button>
|
||||
</Box>
|
||||
|
||||
@@ -84,7 +84,7 @@ import {
|
||||
// export type ExamplePluginOptions = { exampleOption: boolean };
|
||||
export const examplePlugin = createBackendPlugin({
|
||||
// unique id for the plugin
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
// It's possible to provide options to the plugin
|
||||
// register(env, options: ExamplePluginOptions) {
|
||||
register(env) {
|
||||
@@ -111,7 +111,7 @@ If we wanted our plugin to accept options as well, we'd accept the options as th
|
||||
|
||||
```ts
|
||||
export const examplePlugin = createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(env, options?: { silent?: boolean }) {
|
||||
env.registerInit({
|
||||
deps: { logger: coreServices.logger },
|
||||
|
||||
@@ -8361,11 +8361,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"prettier@npm:^2.6.2":
|
||||
version: 2.8.1
|
||||
resolution: "prettier@npm:2.8.1"
|
||||
version: 2.8.2
|
||||
resolution: "prettier@npm:2.8.2"
|
||||
bin:
|
||||
prettier: bin-prettier.js
|
||||
checksum: 4f21a0f1269f76fb36f54e9a8a1ea4c11e27478958bf860661fb4b6d7ac69aac1581f8724fa98ea3585e56d42a2ea317a17ff6e3324f40cb11ff9e20b73785cc
|
||||
checksum: 740c56c2128d587d656ea1dde9bc9c3503dfc94db4f3ac387259215eeb2e216680bdad9d18a0c9feecc6b42cfa188d6fa777df4c36c1d00cedd4199074fbfbd2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ author: Paul Cowan & Taras Mankovski
|
||||
authorURL: https://frontside.com/
|
||||
---
|
||||
|
||||
# Scaling Backstage Ingestion with Incremental Entity Providers
|
||||
|
||||
At the heart of [Backstage](https://backstage.io/) is the [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview), which is a data store that allows an organization to centralize and visualize its many software services and components. Backstage inspects and transforms an organization's disparate software services and parts into a centralized data store.
|
||||
|
||||

|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: What’s Ahead in 2023?
|
||||
author: Lee Mills
|
||||
authorURL: https://github.com/leemills83
|
||||
---
|
||||
|
||||
# What’s Ahead in 2023?
|
||||
|
||||

|
||||
|
||||
Okay, everyone. Time to move on from all the 2022 reflections, Top 10 lists, and Wrapped(s), and turn our gaze in the other direction — towards 2023. As the Backstage team moves into the new year, we find ourselves asking those perennial questions: How can we continue to grow and enhance the Backstage community? How can we improve the experience for those adopting Backstage? How can we give the best support to Backstage contributors?
|
||||
|
||||
Well, we don’t have all the answers, but we do have some. So we wanted to share more on three ongoing initiatives aimed at moving the needle forward for these three distinct, yet interrelated, questions.
|
||||
|
||||
- Grow your influence by climbing the contribution ladder
|
||||
- Come join our SIGs (and push for new ones)
|
||||
- Work with us to drive a better experience for Backstage adopters
|
||||
|
||||
# Climb the contribution ladder
|
||||
|
||||
Truth be told, we’ve been wanting to grow the maintainer team for some time, and have had multiple requests from contributors and contributing organizations wanting to become maintainers. But, so far, we just haven’t managed to get anything off the ground. What we see is that becoming a maintainer is too hard and the current ownership model doesn’t work. In short, we need to update Backstage governance.
|
||||
|
||||
To address this, we are proposing (RFC links below) the following three initiatives:
|
||||
|
||||
- Introduce more steps in between first-time contributor and maintainer in the form of a contributor ladder (loosely based on the [CNCF Project Template](https://github.com/cncf/project-template/blob/main/CONTRIBUTOR_LADDER.md)). The ladder might look as follows: Contributor, Org Member, Reviewer, Maintainer, Project Maintainer.
|
||||
- Change the ownership model, for example, divide the project up into several project areas, each covering particular parts of the project.
|
||||
- Introduce a new process for large contributions in the form of Backstage Enhancement Proposals (BEPs).
|
||||
|
||||
Read more and join the already rich discussion in [RFC: Updated Backstage Governance](https://github.com/backstage/backstage/issues/15317) and [RFC: Backstage Enhancement Proposals](https://github.com/backstage/backstage/issues/15318). The RFC will be open until 13/02/2023.
|
||||
|
||||
# Come join our SIGs (and push for new ones)
|
||||
|
||||
As many of you know, SIG stands for Special Interest Group. The purpose of a SIG is to bring together maintainers and interested contributors within a certain specialist area to discuss, debate, spread knowledge, brainstorm - and drive the overall project forward. They’re also a lot of fun!
|
||||
|
||||
We currently have two SIGs up and running:
|
||||
|
||||
- [Catalog](https://github.com/backstage/community/tree/main/sigs/sig-catalog?utm_campaign=Newsletters%20Sent&utm_medium=email&_hsmi=241130993&_hsenc=p2ANqtz-9fx-nTmQga2j12NqzZaL-lYPq-xQjDoXcWKe_JUoa2RGlK65R9j0CIygfYILr0tGEQSvRVN9xzhiJ6LmVxBthKnStfSA&utm_content=241130993&utm_source=hs_email) started up last June and covers all aspects of the systems and components related to the Software Catalog. The group runs every second Tuesday between 16:00 and 17:00 CET. Just show up or hop into [#catalog](https://discord.com/channels/687207715902193673/923144214580191282) (Discord), if you want to talk to us first. Here are the [notes from our sessions so far](https://docs.google.com/document/d/1xg57CDfX2R5jTGbbWANzifm2szEb664kuTwfEEvKgZU/edit#heading=h.iw0hjv6xfb9e).
|
||||
- [Adoption](https://github.com/backstage/community/tree/main/sigs/sig-adoption?utm_campaign=Newsletters%20Sent&utm_medium=email&_hsmi=241130993&_hsenc=p2ANqtz-9CAxtT8JSeJZPh41o3ZnDBi0VBFpbL6fEqXInr9HHOFhU3JAo8HekKg1JzE5rhGP5zOaHcsYDd1YxJWNHw-eSk5mLWkA&utm_content=241130993&utm_source=hs_email) kicked off in December and covers all aspects of the Backstage adoption journey including documentation and tutorials. In the first two meetups, we discussed adoption challenges based on user persona, collecting data about those challenges, and bootstrapping the Catalog to improve metadata quality for adopters. This one runs every second Thursday between 16:00 and 17:00 CET. Again just show up or, if you want to talk to us first, you can do so in [#adoption](https://discord.com/channels/687207715902193673/1045031039061479536) (Discord). Here are the [notes from our sessions](https://docs.google.com/document/d/1zeYSzXYgh897bxPQmwy2btOwCFlEjlUDLf00riySLRc/edit?_hsmi=241130993&_hsenc=p2ANqtz-_nPuE5hW4fnbhJ5-uK8r5HisSTU7rkslKirvqu0g-vdynbitPVYF2AVLAatos2dLoxb2Bgx_g4LBgH02kXoq7_Acd43Q#heading=h.iw0hjv6xfb9e) so far.
|
||||
|
||||
So that’s SIGs. Get involved and join the ones we have up and running. And/or feel free to propose new ones.
|
||||
|
||||
# Help drive a better experience for all adopters
|
||||
|
||||
Throughout all stages of adopting Backstage in an organization — initial discovery, evaluation and POC, and scaling to the wider org — we have identified opportunities where we want to make the journey for Backstage adopters faster and smoother.
|
||||
|
||||
We’ll go step-by-step, but initial plans are to:
|
||||
|
||||
- Make it easier to install, customize, and maintain Backstage
|
||||
- Provide Golden Paths for adoption
|
||||
- Improve the experience and the content on [backstage.io](https://backstage.io/docs/overview/what-is-backstage)
|
||||
|
||||
As we move forward, we’d love you to be part of it. Here are some ways that you can contribute to this initiative:
|
||||
|
||||
- Simply give us feedback. [Tell us about your adoption journey](https://spotify.co1.qualtrics.com/jfe/form/SV_cuxhYPB6oQDu7gW).
|
||||
- Contribute to the pages and documentation on [backstage.io](https://backstage.io/docs/overview/what-is-backstage)
|
||||
- Pay it forward by making code contributions even during your adoption journey (if you are on one)
|
||||
- Join the [Adoption](https://github.com/backstage/community/tree/main/sigs/sig-adoption?utm_campaign=Newsletters%20Sent&utm_medium=email&_hsmi=241130993&_hsenc=p2ANqtz-9CAxtT8JSeJZPh41o3ZnDBi0VBFpbL6fEqXInr9HHOFhU3JAo8HekKg1JzE5rhGP5zOaHcsYDd1YxJWNHw-eSk5mLWkA&utm_content=241130993&utm_source=hs_email) SIG
|
||||
- Chat with us on [#adoption](https://discord.com/channels/687207715902193673/1045031039061479536) (Discord)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
@@ -3,7 +3,7 @@ title: Vault
|
||||
author: Spread Group
|
||||
authorUrl: https://github.com/ivangonzalezacuna
|
||||
category: Vault
|
||||
description: Visualize a list secrets stored in your vault instance.
|
||||
description: Visualize a list of the secrets stored in your HashiCorp Vault instance.
|
||||
documentation: https://github.com/backstage/backstage/tree/master/plugins/vault
|
||||
iconUrl: img/vault.png
|
||||
npmPackageName: '@backstage/plugin-vault'
|
||||
|
||||
+3
-3
@@ -7369,11 +7369,11 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"prettier@npm:^2.6.2":
|
||||
version: 2.8.1
|
||||
resolution: "prettier@npm:2.8.1"
|
||||
version: 2.8.2
|
||||
resolution: "prettier@npm:2.8.2"
|
||||
bin:
|
||||
prettier: bin-prettier.js
|
||||
checksum: 4f21a0f1269f76fb36f54e9a8a1ea4c11e27478958bf860661fb4b6d7ac69aac1581f8724fa98ea3585e56d42a2ea317a17ff6e3324f40cb11ff9e20b73785cc
|
||||
checksum: 740c56c2128d587d656ea1dde9bc9c3503dfc94db4f3ac387259215eeb2e216680bdad9d18a0c9feecc6b42cfa188d6fa777df4c36c1d00cedd4199074fbfbd2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
@@ -7,15 +7,6 @@
|
||||
{
|
||||
"assertFunctionNames": ["expect", "cy.contains"]
|
||||
}
|
||||
],
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": true,
|
||||
"optionalDependencies": true,
|
||||
"peerDependencies": true,
|
||||
"bundledDependencies": true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ export const databaseFactory = createServiceFactory({
|
||||
service: coreServices.database,
|
||||
deps: {
|
||||
config: coreServices.config,
|
||||
plugin: coreServices.pluginMetadata,
|
||||
lifecycle: coreServices.lifecycle,
|
||||
pluginMetadata: coreServices.pluginMetadata,
|
||||
},
|
||||
async createRootContext({ config }) {
|
||||
return config.getOptional('backend.database')
|
||||
@@ -39,7 +40,10 @@ export const databaseFactory = createServiceFactory({
|
||||
}),
|
||||
);
|
||||
},
|
||||
async factory({ plugin }, databaseManager) {
|
||||
return databaseManager.forPlugin(plugin.getId());
|
||||
async factory({ pluginMetadata, lifecycle }, databaseManager) {
|
||||
return databaseManager.forPlugin(pluginMetadata.getId(), {
|
||||
pluginMetadata,
|
||||
lifecycle,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
createServiceFactory,
|
||||
coreServices,
|
||||
LifecycleServiceShutdownHook,
|
||||
LifecycleServiceShutdownOptions,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
/**
|
||||
@@ -33,11 +34,12 @@ export const lifecycleFactory = createServiceFactory({
|
||||
async factory({ rootLifecycle, logger, pluginMetadata }) {
|
||||
const plugin = pluginMetadata.getId();
|
||||
return {
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void {
|
||||
rootLifecycle.addShutdownHook({
|
||||
...options,
|
||||
|
||||
logger: options.logger?.child({ plugin }) ?? logger,
|
||||
addShutdownHook(
|
||||
hook: LifecycleServiceShutdownHook,
|
||||
options?: LifecycleServiceShutdownOptions,
|
||||
): void {
|
||||
rootLifecycle.addShutdownHook(hook, {
|
||||
logger: options?.logger?.child({ plugin }) ?? logger,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
+1
-6
@@ -104,12 +104,7 @@ export const rootHttpRouterFactory = createServiceFactory({
|
||||
{ logger },
|
||||
);
|
||||
|
||||
lifecycle.addShutdownHook({
|
||||
async fn() {
|
||||
await server.stop();
|
||||
},
|
||||
logger,
|
||||
});
|
||||
lifecycle.addShutdownHook(() => server.stop());
|
||||
|
||||
await server.start();
|
||||
|
||||
|
||||
+11
-9
@@ -21,11 +21,7 @@ describe('lifecycleService', () => {
|
||||
it('should execute registered shutdown hook', async () => {
|
||||
const service = new BackendLifecycleImpl(getVoidLogger());
|
||||
const hook = jest.fn();
|
||||
service.addShutdownHook({
|
||||
fn: async () => {
|
||||
hook();
|
||||
},
|
||||
});
|
||||
service.addShutdownHook(() => hook());
|
||||
// should not execute the hook more than once.
|
||||
await service.shutdown();
|
||||
await service.shutdown();
|
||||
@@ -35,10 +31,16 @@ describe('lifecycleService', () => {
|
||||
|
||||
it('should not throw errors', async () => {
|
||||
const service = new BackendLifecycleImpl(getVoidLogger());
|
||||
service.addShutdownHook({
|
||||
fn: async () => {
|
||||
throw new Error('oh no');
|
||||
},
|
||||
service.addShutdownHook(() => {
|
||||
throw new Error('oh no');
|
||||
});
|
||||
await expect(service.shutdown()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('should not throw async errors', async () => {
|
||||
const service = new BackendLifecycleImpl(getVoidLogger());
|
||||
service.addShutdownHook(async () => {
|
||||
throw new Error('oh no');
|
||||
});
|
||||
await expect(service.shutdown()).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
+14
-10
@@ -18,21 +18,25 @@ import {
|
||||
createServiceFactory,
|
||||
coreServices,
|
||||
LifecycleServiceShutdownHook,
|
||||
LifecycleServiceShutdownOptions,
|
||||
RootLifecycleService,
|
||||
LoggerService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
const CALLBACKS = ['SIGTERM', 'SIGINT', 'beforeExit'];
|
||||
export class BackendLifecycleImpl implements RootLifecycleService {
|
||||
constructor(private readonly logger: LoggerService) {
|
||||
CALLBACKS.map(signal => process.on(signal, () => this.shutdown()));
|
||||
}
|
||||
constructor(private readonly logger: LoggerService) {}
|
||||
|
||||
#isCalled = false;
|
||||
#shutdownTasks: Array<LifecycleServiceShutdownHook> = [];
|
||||
#shutdownTasks: Array<{
|
||||
hook: LifecycleServiceShutdownHook;
|
||||
options?: LifecycleServiceShutdownOptions;
|
||||
}> = [];
|
||||
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void {
|
||||
this.#shutdownTasks.push(options);
|
||||
addShutdownHook(
|
||||
hook: LifecycleServiceShutdownHook,
|
||||
options?: LifecycleServiceShutdownOptions,
|
||||
): void {
|
||||
this.#shutdownTasks.push({ hook, options });
|
||||
}
|
||||
|
||||
async shutdown(): Promise<void> {
|
||||
@@ -43,10 +47,10 @@ export class BackendLifecycleImpl implements RootLifecycleService {
|
||||
|
||||
this.logger.info(`Running ${this.#shutdownTasks.length} shutdown tasks...`);
|
||||
await Promise.all(
|
||||
this.#shutdownTasks.map(async hook => {
|
||||
const { logger = this.logger } = hook;
|
||||
this.#shutdownTasks.map(async ({ hook, options }) => {
|
||||
const logger = options?.logger ?? this.logger;
|
||||
try {
|
||||
await hook.fn();
|
||||
await hook();
|
||||
logger.info(`Shutdown hook succeeded`);
|
||||
} catch (error) {
|
||||
logger.error(`Shutdown hook failed, ${error}`);
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ describe('schedulerFactory', () => {
|
||||
const subject = schedulerFactory();
|
||||
|
||||
const plugin = createBackendPlugin({
|
||||
id: 'example',
|
||||
pluginId: 'example',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from './types';
|
||||
|
||||
export class BackendInitializer {
|
||||
#started = false;
|
||||
#startPromise?: Promise<void>;
|
||||
#features = new Map<BackendFeature, unknown>();
|
||||
#registerInits = new Array<BackendRegisterInit>();
|
||||
#extensionPoints = new Map<ExtensionPoint<unknown>, unknown>();
|
||||
@@ -75,18 +75,40 @@ export class BackendInitializer {
|
||||
}
|
||||
|
||||
add<TOptions>(feature: BackendFeature, options?: TOptions) {
|
||||
if (this.#started) {
|
||||
if (this.#startPromise) {
|
||||
throw new Error('feature can not be added after the backend has started');
|
||||
}
|
||||
this.#features.set(feature, options);
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
if (this.#started) {
|
||||
if (this.#startPromise) {
|
||||
throw new Error('Backend has already started');
|
||||
}
|
||||
this.#started = true;
|
||||
|
||||
const exitHandler = async () => {
|
||||
process.removeListener('SIGTERM', exitHandler);
|
||||
process.removeListener('SIGINT', exitHandler);
|
||||
process.removeListener('beforeExit', exitHandler);
|
||||
|
||||
try {
|
||||
await this.stop();
|
||||
process.exit(0);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
process.addListener('SIGTERM', exitHandler);
|
||||
process.addListener('SIGINT', exitHandler);
|
||||
process.addListener('beforeExit', exitHandler);
|
||||
|
||||
this.#startPromise = this.#doStart();
|
||||
await this.#startPromise;
|
||||
}
|
||||
|
||||
async #doStart(): Promise<void> {
|
||||
// Initialize all root scoped services
|
||||
for (const ref of this.#serviceHolder.getServiceRefs()) {
|
||||
if (ref.scope === 'root') {
|
||||
@@ -177,9 +199,10 @@ export class BackendInitializer {
|
||||
}
|
||||
|
||||
async stop(): Promise<void> {
|
||||
if (!this.#started) {
|
||||
if (!this.#startPromise) {
|
||||
return;
|
||||
}
|
||||
await this.#startPromise;
|
||||
|
||||
const lifecycleService = await this.#serviceHolder.get(
|
||||
coreServices.rootLifecycle,
|
||||
|
||||
@@ -32,6 +32,7 @@ import { IdentityService } from '@backstage/backend-plugin-api';
|
||||
import { isChildPath } from '@backstage/cli-common';
|
||||
import { Knex } from 'knex';
|
||||
import { KubeConfig } from '@kubernetes/client-node';
|
||||
import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { LoadConfigOptionsRemote } from '@backstage/config-loader';
|
||||
import { Logger } from 'winston';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
@@ -40,6 +41,7 @@ import { PermissionsService } from '@backstage/backend-plugin-api';
|
||||
import { CacheService as PluginCacheManager } from '@backstage/backend-plugin-api';
|
||||
import { DatabaseService as PluginDatabaseManager } from '@backstage/backend-plugin-api';
|
||||
import { DiscoveryService as PluginEndpointDiscovery } from '@backstage/backend-plugin-api';
|
||||
import { PluginMetadataService } from '@backstage/backend-plugin-api';
|
||||
import { PushResult } from 'isomorphic-git';
|
||||
import { Readable } from 'stream';
|
||||
import { ReadCommitResult } from 'isomorphic-git';
|
||||
@@ -232,6 +234,10 @@ export class Contexts {
|
||||
export function createDatabaseClient(
|
||||
dbConfig: Config,
|
||||
overrides?: Partial<Knex.Config>,
|
||||
deps?: {
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
): Knex<any, any[]>;
|
||||
|
||||
// @public
|
||||
@@ -252,7 +258,13 @@ export function createStatusCheckRouter(options: {
|
||||
|
||||
// @public
|
||||
export class DatabaseManager {
|
||||
forPlugin(pluginId: string): PluginDatabaseManager;
|
||||
forPlugin(
|
||||
pluginId: string,
|
||||
deps?: {
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
): PluginDatabaseManager;
|
||||
static fromConfig(
|
||||
config: Config,
|
||||
options?: DatabaseManagerOptions,
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/backend-app-api": "workspace:^",
|
||||
"@backstage/backend-dev-utils": "workspace:^",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/cli-common": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
|
||||
@@ -29,7 +29,11 @@ import {
|
||||
} from './connection';
|
||||
import { PluginDatabaseManager } from './types';
|
||||
import path from 'path';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import {
|
||||
LifecycleService,
|
||||
LoggerService,
|
||||
PluginMetadataService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { stringifyError } from '@backstage/errors';
|
||||
|
||||
/**
|
||||
@@ -94,12 +98,18 @@ export class DatabaseManager {
|
||||
* should be unique as they are used to look up database config overrides under
|
||||
* `backend.database.plugin`.
|
||||
*/
|
||||
forPlugin(pluginId: string): PluginDatabaseManager {
|
||||
forPlugin(
|
||||
pluginId: string,
|
||||
deps?: {
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
): PluginDatabaseManager {
|
||||
const _this = this;
|
||||
|
||||
return {
|
||||
getClient(): Promise<Knex> {
|
||||
return _this.getDatabase(pluginId);
|
||||
return _this.getDatabase(pluginId, deps);
|
||||
},
|
||||
migrations: {
|
||||
skip: false,
|
||||
@@ -307,7 +317,13 @@ export class DatabaseManager {
|
||||
* @returns Promise which resolves to a scoped Knex database client for a
|
||||
* plugin
|
||||
*/
|
||||
private async getDatabase(pluginId: string): Promise<Knex> {
|
||||
private async getDatabase(
|
||||
pluginId: string,
|
||||
deps?: {
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
): Promise<Knex> {
|
||||
if (this.databaseCache.has(pluginId)) {
|
||||
return this.databaseCache.get(pluginId)!;
|
||||
}
|
||||
@@ -351,6 +367,7 @@ export class DatabaseManager {
|
||||
const client = createDatabaseClient(
|
||||
pluginConfig,
|
||||
databaseClientOverrides,
|
||||
deps,
|
||||
);
|
||||
this.startKeepaliveLoop(pluginId, client);
|
||||
return client;
|
||||
|
||||
@@ -22,6 +22,10 @@ import { mergeDatabaseConfig } from './config';
|
||||
import { DatabaseConnector } from './types';
|
||||
|
||||
import { mysqlConnector, pgConnector, sqlite3Connector } from './connectors';
|
||||
import {
|
||||
LifecycleService,
|
||||
PluginMetadataService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
type DatabaseClient =
|
||||
| 'pg'
|
||||
@@ -55,11 +59,15 @@ const ConnectorMapping: Record<DatabaseClient, DatabaseConnector> = {
|
||||
export function createDatabaseClient(
|
||||
dbConfig: Config,
|
||||
overrides?: Partial<Knex.Config>,
|
||||
deps?: {
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
) {
|
||||
const client: DatabaseClient = dbConfig.getString('client');
|
||||
|
||||
return (
|
||||
ConnectorMapping[client]?.createClient(dbConfig, overrides) ??
|
||||
ConnectorMapping[client]?.createClient(dbConfig, overrides, deps) ??
|
||||
knexFactory(mergeDatabaseConfig(dbConfig.get(), overrides))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,8 +18,13 @@ import { Config } from '@backstage/config';
|
||||
import { ensureDirSync } from 'fs-extra';
|
||||
import knexFactory, { Knex } from 'knex';
|
||||
import path from 'path';
|
||||
import { DevDataStore } from '@backstage/backend-dev-utils';
|
||||
import { mergeDatabaseConfig } from '../config';
|
||||
import { DatabaseConnector } from '../types';
|
||||
import {
|
||||
LifecycleService,
|
||||
PluginMetadataService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
/**
|
||||
* Creates a knex SQLite3 database connection
|
||||
@@ -30,22 +35,63 @@ import { DatabaseConnector } from '../types';
|
||||
export function createSqliteDatabaseClient(
|
||||
dbConfig: Config,
|
||||
overrides?: Knex.Config,
|
||||
deps?: {
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
) {
|
||||
const knexConfig = buildSqliteDatabaseConfig(dbConfig, overrides);
|
||||
const connConfig = knexConfig.connection as Knex.Sqlite3ConnectionConfig;
|
||||
|
||||
const filename = connConfig.filename ?? ':memory:';
|
||||
|
||||
// If storage on disk is used, ensure that the directory exists
|
||||
if (
|
||||
(knexConfig.connection as Knex.Sqlite3ConnectionConfig).filename &&
|
||||
(knexConfig.connection as Knex.Sqlite3ConnectionConfig).filename !==
|
||||
':memory:'
|
||||
) {
|
||||
const { filename } = knexConfig.connection as Knex.Sqlite3ConnectionConfig;
|
||||
if (filename !== ':memory:') {
|
||||
const directory = path.dirname(filename);
|
||||
|
||||
ensureDirSync(directory);
|
||||
}
|
||||
|
||||
const database = knexFactory(knexConfig);
|
||||
let database: Knex;
|
||||
|
||||
if (deps && filename === ':memory:') {
|
||||
// The dev store is used during watch mode to store and restore the database
|
||||
// across reloads. It is only available when running the backend through
|
||||
// `backstage-cli package start`.
|
||||
const devStore = DevDataStore.get();
|
||||
|
||||
if (devStore) {
|
||||
const dataKey = `sqlite3-db-${deps.pluginMetadata.getId()}`;
|
||||
|
||||
const connectionLoader = async () => {
|
||||
// If seed data is available, use it tconnectionLoader restore the database
|
||||
const { data: seedData } = await devStore.load(dataKey);
|
||||
|
||||
return {
|
||||
...(knexConfig.connection as Knex.Sqlite3ConnectionConfig),
|
||||
filename: seedData ?? ':memory:',
|
||||
};
|
||||
};
|
||||
|
||||
database = knexFactory({
|
||||
...knexConfig,
|
||||
connection: Object.assign(connectionLoader, {
|
||||
// This is a workaround for the knex SQLite driver always warning when using a config loader
|
||||
filename: ':memory:',
|
||||
}),
|
||||
});
|
||||
|
||||
// If the dev store is available we save the database state on shutdown
|
||||
deps.lifecycle.addShutdownHook(async () => {
|
||||
const connection = await database.client.acquireConnection();
|
||||
const data = connection.serialize();
|
||||
await devStore.save(dataKey, data);
|
||||
});
|
||||
} else {
|
||||
database = knexFactory(knexConfig);
|
||||
}
|
||||
} else {
|
||||
database = knexFactory(knexConfig);
|
||||
}
|
||||
|
||||
database.client.pool.on('createSuccess', (_eventId: any, resource: any) => {
|
||||
resource.run('PRAGMA foreign_keys = ON', () => {});
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
LifecycleService,
|
||||
PluginMetadataService,
|
||||
} from '@backstage/backend-plugin-api';
|
||||
import { Config } from '@backstage/config';
|
||||
import { Knex } from 'knex';
|
||||
|
||||
@@ -26,7 +30,14 @@ export interface DatabaseConnector {
|
||||
/**
|
||||
* createClient provides an instance of a knex database connector.
|
||||
*/
|
||||
createClient(dbConfig: Config, overrides?: Partial<Knex.Config>): Knex;
|
||||
createClient(
|
||||
dbConfig: Config,
|
||||
overrides?: Partial<Knex.Config>,
|
||||
deps?: {
|
||||
lifecycle: LifecycleService;
|
||||
pluginMetadata: PluginMetadataService;
|
||||
},
|
||||
): Knex;
|
||||
/**
|
||||
* createNameOverride provides a partial knex config sufficient to override a
|
||||
* database name.
|
||||
|
||||
@@ -60,7 +60,7 @@ export function makeLegacyPlugin<
|
||||
}>,
|
||||
) => {
|
||||
const compatPlugin = createBackendPlugin({
|
||||
id: name,
|
||||
pluginId: name,
|
||||
register(env) {
|
||||
env.registerInit({
|
||||
deps: { ...envMapping, _router: coreServices.httpRouter },
|
||||
|
||||
@@ -202,7 +202,7 @@ describe('Git', () => {
|
||||
await git.clone({ url, dir });
|
||||
|
||||
const { onAuth } = (
|
||||
isomorphic.clone as unknown as jest.Mock<typeof isomorphic['clone']>
|
||||
isomorphic.clone as unknown as jest.Mock<(typeof isomorphic)['clone']>
|
||||
).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
@@ -315,7 +315,7 @@ describe('Git', () => {
|
||||
await git.fetch({ remote, dir });
|
||||
|
||||
const { onAuth } = (
|
||||
isomorphic.fetch as unknown as jest.Mock<typeof isomorphic['fetch']>
|
||||
isomorphic.fetch as unknown as jest.Mock<(typeof isomorphic)['fetch']>
|
||||
).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
@@ -486,7 +486,7 @@ describe('Git', () => {
|
||||
await git.push({ remote, dir, remoteRef, force });
|
||||
|
||||
const { onAuth } = (
|
||||
isomorphic.push as unknown as jest.Mock<typeof isomorphic['push']>
|
||||
isomorphic.push as unknown as jest.Mock<(typeof isomorphic)['push']>
|
||||
).mock.calls[0][0]!;
|
||||
|
||||
expect(onAuth()).toEqual(auth);
|
||||
|
||||
@@ -143,7 +143,7 @@ describe('createBackend', () => {
|
||||
expect.assertions(3);
|
||||
backend.add(
|
||||
createBackendPlugin({
|
||||
id: 'test',
|
||||
pluginId: 'test',
|
||||
register(reg) {
|
||||
reg.registerInit({
|
||||
deps: {
|
||||
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
} from '@backstage/backend-plugin-api';
|
||||
|
||||
// Internal import of the type to avoid needing to export this.
|
||||
// eslint-disable-next-line monorepo/no-internal-import
|
||||
// eslint-disable-next-line @backstage/no-forbidden-package-imports
|
||||
import type { InternalSharedBackendEnvironment } from '@backstage/backend-plugin-api/src/wiring/createSharedEnvironment';
|
||||
|
||||
export const defaultServiceFactories = [
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
|
||||
@@ -0,0 +1,5 @@
|
||||
# backend-dev-utils
|
||||
|
||||
Welcome to the backend-dev-utils package!
|
||||
|
||||
_This package is experimental._
|
||||
@@ -0,0 +1,20 @@
|
||||
## API Report File for "@backstage/backend-dev-utils"
|
||||
|
||||
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
|
||||
|
||||
```ts
|
||||
// @public
|
||||
export class DevDataStore {
|
||||
static get(): DevDataStore | undefined;
|
||||
load<T>(key: string): Promise<{
|
||||
loaded: boolean;
|
||||
data: T;
|
||||
}>;
|
||||
save<T>(
|
||||
key: string,
|
||||
data: T,
|
||||
): Promise<{
|
||||
saved: boolean;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@backstage/backend-dev-utils",
|
||||
"version": "0.0.0",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.cjs.js",
|
||||
"types": "dist/index.d.ts"
|
||||
},
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
},
|
||||
"homepage": "https://backstage.io",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/backstage/backstage",
|
||||
"directory": "packages/backend-dev-utils"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
"clean": "backstage-cli package clean",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
"postpack": "backstage-cli package postpack"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Serializable, spawn } from 'child_process';
|
||||
import { DevDataStore } from './DevDataStore';
|
||||
import { BackstageIpcClient } from './ipcClient';
|
||||
|
||||
function applyIpcTransform(value: Serializable): Promise<Serializable> {
|
||||
const child = spawn(
|
||||
'node',
|
||||
[
|
||||
'--eval',
|
||||
`
|
||||
const listener = msg => {
|
||||
process.send(msg);
|
||||
process.removeListener('message', listener);
|
||||
}
|
||||
process.addListener('message', listener);
|
||||
`,
|
||||
],
|
||||
{
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
||||
serialization: 'advanced',
|
||||
},
|
||||
);
|
||||
|
||||
return new Promise(resolve => {
|
||||
child.addListener('message', resolve);
|
||||
child.send(value);
|
||||
});
|
||||
}
|
||||
|
||||
class MockIpcClient implements Pick<BackstageIpcClient, 'request'> {
|
||||
#data = new Map<string, Serializable>();
|
||||
|
||||
async request(method: string, body: any): Promise<any> {
|
||||
if (method === 'DevDataStore.save') {
|
||||
this.#data.set(body.key, body.data);
|
||||
return { saved: true };
|
||||
} else if (method === 'DevDataStore.load') {
|
||||
if (!this.#data.has(body.key)) {
|
||||
return { loaded: false, data: undefined };
|
||||
}
|
||||
const data = this.#data.get(body.key)!;
|
||||
return { loaded: true, data: await applyIpcTransform(data) };
|
||||
}
|
||||
throw new Error('Unknown message');
|
||||
}
|
||||
}
|
||||
|
||||
describe('DevDataStore', () => {
|
||||
it('should save and load data', async () => {
|
||||
const store = DevDataStore.forTest(new MockIpcClient());
|
||||
|
||||
await expect(store.save('test', { foo: 'bar' })).resolves.toEqual({
|
||||
saved: true,
|
||||
});
|
||||
await expect(store.load('test')).resolves.toEqual({
|
||||
loaded: true,
|
||||
data: { foo: 'bar' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should save and load buffers', async () => {
|
||||
const store = DevDataStore.forTest(new MockIpcClient());
|
||||
|
||||
await expect(
|
||||
store.save('test', Buffer.from('abc', 'ascii')),
|
||||
).resolves.toEqual({
|
||||
saved: true,
|
||||
});
|
||||
await expect(store.load('test')).resolves.toEqual({
|
||||
loaded: true,
|
||||
data: Buffer.from('abc', 'ascii'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should save and load array buffers', async () => {
|
||||
const store = DevDataStore.forTest(new MockIpcClient());
|
||||
|
||||
await expect(
|
||||
store.save('test', new Uint8Array(Buffer.from('abc', 'ascii'))),
|
||||
).resolves.toEqual({
|
||||
saved: true,
|
||||
});
|
||||
await expect(store.load('test')).resolves.toEqual({
|
||||
loaded: true,
|
||||
data: new Uint8Array(Buffer.from('abc', 'ascii')),
|
||||
});
|
||||
});
|
||||
|
||||
it('should save and load buffers nested in objects', async () => {
|
||||
const store = DevDataStore.forTest(new MockIpcClient());
|
||||
|
||||
await expect(
|
||||
store.save('test', {
|
||||
x: Buffer.from('x', 'ascii'),
|
||||
y: [Buffer.from('y', 'ascii')],
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
saved: true,
|
||||
});
|
||||
await expect(store.load('test')).resolves.toEqual({
|
||||
loaded: true,
|
||||
data: { x: Buffer.from('x', 'ascii'), y: [Buffer.from('y', 'ascii')] },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { BackstageIpcClient, ipcClient } from './ipcClient';
|
||||
|
||||
interface SaveRequest {
|
||||
key: string;
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
interface SaveResponse {
|
||||
saved: boolean;
|
||||
}
|
||||
|
||||
interface LoadRequest {
|
||||
key: string;
|
||||
}
|
||||
|
||||
interface LoadResponse {
|
||||
loaded: boolean;
|
||||
data: unknown;
|
||||
}
|
||||
|
||||
/**
|
||||
* A data store that can be used to store temporary data during development.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class DevDataStore {
|
||||
static #instance?: DevDataStore;
|
||||
|
||||
/**
|
||||
* Tries to acquire a DevDataStore instance. This will only succeed when the backend
|
||||
* process is being run through the `@backstage/cli` in development mode.
|
||||
*
|
||||
* @returns A DevDataStore instance, or undefined if not available.
|
||||
*/
|
||||
static get(): DevDataStore | undefined {
|
||||
if (ipcClient) {
|
||||
if (!this.#instance) {
|
||||
this.#instance = new DevDataStore(ipcClient);
|
||||
}
|
||||
return this.#instance;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
static forTest(client: Pick<BackstageIpcClient, 'request'>): DevDataStore {
|
||||
return new DevDataStore(client as BackstageIpcClient);
|
||||
}
|
||||
|
||||
#client: BackstageIpcClient;
|
||||
|
||||
private constructor(client: BackstageIpcClient) {
|
||||
this.#client = client;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save data to the data store.
|
||||
*
|
||||
* @param key - The key used to identify the data.
|
||||
* @param data - The data to save. The data will be serialized using advanced IPC serialization.
|
||||
* @returns A promise that resolves to a result object that indicates whether the data was saved.
|
||||
*/
|
||||
async save<T>(key: string, data: T): Promise<{ saved: boolean }> {
|
||||
return this.#client.request<SaveRequest, SaveResponse>(
|
||||
'DevDataStore.save',
|
||||
{ key, data },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads data from the data store.
|
||||
*
|
||||
* @param key - The key used to identify the data.
|
||||
* @returns A promise that resolves to a result object that indicates whether the data was loaded, as well as the data.
|
||||
*/
|
||||
async load<T>(key: string): Promise<{ loaded: boolean; data: T }> {
|
||||
const result = await this.#client.request<LoadRequest, LoadResponse>(
|
||||
'DevDataStore.load',
|
||||
{ key },
|
||||
);
|
||||
return result as { loaded: boolean; data: T };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Local development helpers for backend.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
export { DevDataStore } from './DevDataStore';
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
type SendMessage = Exclude<typeof process.send, undefined>;
|
||||
|
||||
interface Request {
|
||||
id: number;
|
||||
method: string;
|
||||
body: unknown;
|
||||
type: string;
|
||||
}
|
||||
|
||||
type Response =
|
||||
| {
|
||||
type: string;
|
||||
id: number;
|
||||
body: unknown;
|
||||
}
|
||||
| {
|
||||
type: string;
|
||||
id: number;
|
||||
error: Error;
|
||||
};
|
||||
|
||||
const requestType = '@backstage/cli/channel/request';
|
||||
const responseType = '@backstage/cli/channel/response';
|
||||
|
||||
/**
|
||||
* The client side of an IPC communication channel.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export class BackstageIpcClient {
|
||||
#messageId = 0;
|
||||
#sendMessage: SendMessage;
|
||||
|
||||
/**
|
||||
* Creates a new client if we're in a child process with IPC and BACKSTAGE_CLI_CHANNEL is set.
|
||||
*/
|
||||
static create(): BackstageIpcClient | undefined {
|
||||
const sendMessage = process.send?.bind(process);
|
||||
return sendMessage && process.env.BACKSTAGE_CLI_CHANNEL
|
||||
? new BackstageIpcClient(sendMessage)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
constructor(sendMessage: SendMessage) {
|
||||
this.#sendMessage = sendMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request to the parent process and wait for a response.
|
||||
*/
|
||||
async request<TRequestBody, TResponseBody>(
|
||||
method: string,
|
||||
body: TRequestBody,
|
||||
): Promise<TResponseBody> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = this.#messageId++;
|
||||
|
||||
const request: Request = {
|
||||
type: requestType,
|
||||
id,
|
||||
method,
|
||||
body,
|
||||
};
|
||||
|
||||
this.#sendMessage(request, (e: Error) => {
|
||||
if (e) {
|
||||
reject(e);
|
||||
return;
|
||||
}
|
||||
|
||||
let timeout: NodeJS.Timeout | undefined = undefined;
|
||||
|
||||
const messageHandler = (response: Response) => {
|
||||
if (response?.type !== responseType) {
|
||||
return;
|
||||
}
|
||||
if (response.id !== id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ('error' in response) {
|
||||
const error = new Error(response.error.message);
|
||||
if (response.error.name) {
|
||||
error.name = response.error.name;
|
||||
}
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(response.body as TResponseBody);
|
||||
}
|
||||
|
||||
clearTimeout(timeout);
|
||||
process.removeListener('message', messageHandler);
|
||||
};
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
reject(new Error(`IPC request '${method}' with ID ${id} timed out`));
|
||||
process.removeListener('message', messageHandler);
|
||||
}, 5000);
|
||||
timeout.unref();
|
||||
|
||||
process.addListener('message', messageHandler as () => void);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const ipcClient = BackstageIpcClient.create();
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* Copyright 2023 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export {};
|
||||
@@ -18,7 +18,7 @@
|
||||
"backstage"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "backstage-cli package start",
|
||||
"start": "EXPERIMENTAL_BACKEND_START=1 backstage-cli package start",
|
||||
"build": "backstage-cli package build",
|
||||
"lint": "backstage-cli package lint",
|
||||
"test": "backstage-cli package test",
|
||||
|
||||
@@ -47,7 +47,7 @@ export interface BackendModuleRegistrationPoints {
|
||||
|
||||
// @public
|
||||
export interface BackendPluginConfig {
|
||||
id: string;
|
||||
pluginId: string;
|
||||
// (undocumented)
|
||||
register(reg: BackendPluginRegistrationPoints): void;
|
||||
}
|
||||
@@ -275,7 +275,7 @@ export type ExtensionPoint<T> = {
|
||||
id: string;
|
||||
T: T;
|
||||
toString(): string;
|
||||
$$ref: 'extension-point';
|
||||
$$type: '@backstage/ExtensionPoint';
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -294,14 +294,19 @@ export interface IdentityService extends IdentityApi {}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LifecycleService {
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void;
|
||||
addShutdownHook(
|
||||
hook: LifecycleServiceShutdownHook,
|
||||
options?: LifecycleServiceShutdownOptions,
|
||||
): void;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type LifecycleServiceShutdownHook = {
|
||||
fn: () => void | Promise<void>;
|
||||
export type LifecycleServiceShutdownHook = () => void | Promise<void>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LifecycleServiceShutdownOptions {
|
||||
logger?: LoggerService;
|
||||
};
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface LoggerService {
|
||||
@@ -490,7 +495,7 @@ export type ServiceRef<
|
||||
scope: TScope;
|
||||
T: TService;
|
||||
toString(): string;
|
||||
$$ref: 'service';
|
||||
$$type: '@backstage/ServiceRef';
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -508,7 +513,7 @@ export interface ServiceRefConfig<TService, TScope extends 'root' | 'plugin'> {
|
||||
// @public
|
||||
export interface SharedBackendEnvironment {
|
||||
// (undocumented)
|
||||
$$type: 'SharedBackendEnvironment';
|
||||
$$type: '@backstage/SharedBackendEnvironment';
|
||||
}
|
||||
|
||||
// @public
|
||||
|
||||
@@ -19,14 +19,17 @@ import { LoggerService } from './LoggerService';
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type LifecycleServiceShutdownHook = {
|
||||
fn: () => void | Promise<void>;
|
||||
export type LifecycleServiceShutdownHook = () => void | Promise<void>;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export interface LifecycleServiceShutdownOptions {
|
||||
/**
|
||||
* Optional {@link LoggerService} that will be used for logging instead of the default logger.
|
||||
*/
|
||||
logger?: LoggerService;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
@@ -35,5 +38,8 @@ export interface LifecycleService {
|
||||
/**
|
||||
* Register a function to be called when the backend is shutting down.
|
||||
*/
|
||||
addShutdownHook(options: LifecycleServiceShutdownHook): void;
|
||||
addShutdownHook(
|
||||
hook: LifecycleServiceShutdownHook,
|
||||
options?: LifecycleServiceShutdownOptions,
|
||||
): void;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export type { HttpRouterService } from './HttpRouterService';
|
||||
export type {
|
||||
LifecycleService,
|
||||
LifecycleServiceShutdownHook,
|
||||
LifecycleServiceShutdownOptions,
|
||||
} from './LifecycleService';
|
||||
export type { LoggerService, LogMeta } from './LoggerService';
|
||||
export type { PermissionsService } from './PermissionsService';
|
||||
|
||||
@@ -44,7 +44,7 @@ export type ServiceRef<
|
||||
|
||||
toString(): string;
|
||||
|
||||
$$ref: 'service';
|
||||
$$type: '@backstage/ServiceRef';
|
||||
};
|
||||
|
||||
/** @public */
|
||||
@@ -119,7 +119,7 @@ export function createServiceRef<TService>(
|
||||
toString() {
|
||||
return `serviceRef{${config.id}}`;
|
||||
},
|
||||
$$ref: 'service', // TODO: declare
|
||||
$$type: '@backstage/ServiceRef',
|
||||
__defaultFactory: defaultFactory,
|
||||
} as ServiceRef<TService, typeof scope> & {
|
||||
__defaultFactory?: (
|
||||
|
||||
@@ -48,7 +48,7 @@ describe('createSharedEnvironment', () => {
|
||||
expect(env).toBeDefined();
|
||||
const internalEnv = env() as unknown as InternalSharedBackendEnvironment;
|
||||
expect(internalEnv).toEqual({
|
||||
$$type: 'SharedBackendEnvironment',
|
||||
$$type: '@backstage/SharedBackendEnvironment',
|
||||
version: 'v1',
|
||||
services: undefined,
|
||||
});
|
||||
|
||||
@@ -31,7 +31,7 @@ export interface SharedBackendEnvironmentConfig {
|
||||
* @public
|
||||
*/
|
||||
export interface SharedBackendEnvironment {
|
||||
$$type: 'SharedBackendEnvironment';
|
||||
$$type: '@backstage/SharedBackendEnvironment';
|
||||
|
||||
// NOTE: This type is opaque in order to allow for future API evolution without
|
||||
// cluttering the external API. For example we might want to add support
|
||||
@@ -93,7 +93,7 @@ export function createSharedEnvironment<
|
||||
|
||||
// Here to ensure type safety in this internal implementation.
|
||||
const env: SharedBackendEnvironment & InternalSharedBackendEnvironment = {
|
||||
$$type: 'SharedBackendEnvironment',
|
||||
$$type: '@backstage/SharedBackendEnvironment',
|
||||
version: 'v1',
|
||||
services,
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('createExtensionPoint', () => {
|
||||
describe('createBackendPlugin', () => {
|
||||
it('should create a BackendPlugin', () => {
|
||||
const plugin = createBackendPlugin((_options: { a: string }) => ({
|
||||
id: 'x',
|
||||
pluginId: 'x',
|
||||
register() {},
|
||||
}));
|
||||
expect(plugin).toBeDefined();
|
||||
@@ -47,7 +47,7 @@ describe('createBackendPlugin', () => {
|
||||
|
||||
it('should create plugins with optional options', () => {
|
||||
const plugin = createBackendPlugin((_options?: { a: string }) => ({
|
||||
id: 'x',
|
||||
pluginId: 'x',
|
||||
register() {},
|
||||
}));
|
||||
expect(plugin).toBeDefined();
|
||||
@@ -59,7 +59,7 @@ describe('createBackendPlugin', () => {
|
||||
|
||||
it('should create plugins without options', () => {
|
||||
const plugin = createBackendPlugin({
|
||||
id: 'x',
|
||||
pluginId: 'x',
|
||||
register() {},
|
||||
});
|
||||
expect(plugin).toBeDefined();
|
||||
@@ -74,7 +74,7 @@ describe('createBackendPlugin', () => {
|
||||
a: string;
|
||||
}
|
||||
const plugin = createBackendPlugin((_options: TestOptions) => ({
|
||||
id: 'x',
|
||||
pluginId: 'x',
|
||||
register() {},
|
||||
}));
|
||||
expect(plugin).toBeDefined();
|
||||
@@ -91,7 +91,7 @@ describe('createBackendPlugin', () => {
|
||||
a: string;
|
||||
}
|
||||
const plugin = createBackendPlugin((_options?: TestOptions) => ({
|
||||
id: 'x',
|
||||
pluginId: 'x',
|
||||
register() {},
|
||||
}));
|
||||
expect(plugin).toBeDefined();
|
||||
|
||||
@@ -55,7 +55,7 @@ export function createExtensionPoint<T>(
|
||||
toString() {
|
||||
return `extensionPoint{${config.id}}`;
|
||||
},
|
||||
$$ref: 'extension-point', // TODO: declare
|
||||
$$type: '@backstage/ExtensionPoint',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ export interface BackendPluginConfig {
|
||||
*
|
||||
* @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns}
|
||||
*/
|
||||
id: string;
|
||||
pluginId: string;
|
||||
register(reg: BackendPluginRegistrationPoints): void;
|
||||
}
|
||||
|
||||
@@ -87,10 +87,18 @@ export function createBackendPlugin<TOptions extends [options?: object] = []>(
|
||||
config: BackendPluginConfig | ((...params: TOptions) => BackendPluginConfig),
|
||||
): (...params: TOptions) => BackendFeature {
|
||||
if (typeof config === 'function') {
|
||||
return config;
|
||||
return (...options: TOptions) => {
|
||||
const c = config(...options);
|
||||
return {
|
||||
...c,
|
||||
id: c.pluginId,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return () => config;
|
||||
return () => ({
|
||||
...config,
|
||||
id: config.pluginId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -32,7 +32,7 @@ export type ExtensionPoint<T> = {
|
||||
|
||||
toString(): string;
|
||||
|
||||
$$ref: 'extension-point';
|
||||
$$type: '@backstage/ExtensionPoint';
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import { Backend } from '@backstage/backend-app-api';
|
||||
import { BackendFeature } from '@backstage/backend-plugin-api';
|
||||
import { CacheService } from '@backstage/backend-plugin-api';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { ConfigService } from '@backstage/backend-plugin-api';
|
||||
import { DatabaseService } from '@backstage/backend-plugin-api';
|
||||
import { ExtendedHttpServer } from '@backstage/backend-app-api';
|
||||
@@ -20,7 +19,6 @@ import { LifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { LoggerService } from '@backstage/backend-plugin-api';
|
||||
import { PermissionsService } from '@backstage/backend-plugin-api';
|
||||
import { RootLifecycleService } from '@backstage/backend-plugin-api';
|
||||
import { RootLoggerService } from '@backstage/backend-plugin-api';
|
||||
import { SchedulerService } from '@backstage/backend-plugin-api';
|
||||
import { ServiceFactory } from '@backstage/backend-plugin-api';
|
||||
import { ServiceRef } from '@backstage/backend-plugin-api';
|
||||
@@ -34,35 +32,27 @@ export function isDockerDisabledForTests(): boolean;
|
||||
export namespace mockServices {
|
||||
// (undocumented)
|
||||
export namespace cache {
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<CacheService, 'plugin'>;
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<CacheService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export function config(options?: config.Options): ConfigReader;
|
||||
export function config(options?: config.Options): ConfigService;
|
||||
// (undocumented)
|
||||
export namespace config {
|
||||
// (undocumented)
|
||||
export type Options = {
|
||||
data?: JsonObject;
|
||||
};
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<ConfigService, 'root'>;
|
||||
const // (undocumented)
|
||||
factory: (options?: Options | undefined) => ServiceFactory<ConfigService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace database {
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<DatabaseService, 'plugin'>;
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<DatabaseService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace httpRouter {
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<HttpRouterService, 'plugin'>;
|
||||
const // (undocumented)
|
||||
factory: (
|
||||
options?: HttpRouterFactoryOptions | undefined,
|
||||
@@ -72,36 +62,26 @@ export namespace mockServices {
|
||||
export function identity(): IdentityService;
|
||||
// (undocumented)
|
||||
export namespace identity {
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<IdentityService, 'plugin'>;
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<IdentityService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace lifecycle {
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<LifecycleService, 'plugin'>;
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<LifecycleService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace logger {
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<LoggerService, 'plugin'>;
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<LoggerService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace permissions {
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<PermissionsService, 'plugin'>;
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<PermissionsService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace rootLifecycle {
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<RootLifecycleService, 'root'>;
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<RootLifecycleService>;
|
||||
}
|
||||
@@ -120,15 +100,11 @@ export namespace mockServices {
|
||||
debug: boolean;
|
||||
};
|
||||
};
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<RootLoggerService, 'root'>;
|
||||
const // (undocumented)
|
||||
factory: (options?: Options | undefined) => ServiceFactory<LoggerService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace scheduler {
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<SchedulerService, 'plugin'>;
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<SchedulerService>;
|
||||
}
|
||||
@@ -136,15 +112,11 @@ export namespace mockServices {
|
||||
export function tokenManager(): TokenManagerService;
|
||||
// (undocumented)
|
||||
export namespace tokenManager {
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<TokenManagerService, 'plugin'>;
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<TokenManagerService>;
|
||||
}
|
||||
// (undocumented)
|
||||
export namespace urlReader {
|
||||
const // (undocumented)
|
||||
ref: ServiceRef<UrlReaderService, 'plugin'>;
|
||||
const // (undocumented)
|
||||
factory: () => ServiceFactory<UrlReaderService>;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
"@backstage/backend-app-api": "workspace:^",
|
||||
"@backstage/backend-common": "workspace:^",
|
||||
"@backstage/backend-plugin-api": "workspace:^",
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/config": "workspace:^",
|
||||
"@backstage/plugin-auth-node": "workspace:^",
|
||||
"@backstage/types": "workspace:^",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user