Merge branch 'backstage:master' into custom_auth_connector

This commit is contained in:
Gasan
2025-01-24 09:58:06 +01:00
committed by GitHub
435 changed files with 8642 additions and 1523 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
---
'@backstage/kubernetes-react': patch
'@backstage/plugin-kubernetes-react': patch
---
Fixed bug in string-to-integer conversion to properly handle decimal values with BigInt.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
Bumped create-app version.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-kubernetes-cluster': patch
'@backstage/plugin-kubernetes-react': patch
---
Improved rendering of Kubernetes resources' metadata.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
Added `nestedValuesAsYaml` option to `StructuredMetadataTable` to render data as yaml.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Ignore benign database conflict errors during stitching, now logged with debug level instead.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': patch
---
Fixed an issue where invalid expressions or non-object values in `step.each` caused an error.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/canon': minor
---
This is the first alpha release for Canon. As part of this release we are introducing 5 layout components and 7 components. All theming is done through CSS variables.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-techdocs': patch
---
Add missing route ref to the `/alpha` entity content extension.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/backend-app-api': patch
---
The log message written when plugins fail to initialize now includes the error that caused the plugin startup to fail.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Fixed the file path pattern of many static assets output as part of the frontend build process, where there was an extra `.` before the extension, leading to names like `image-af7946b..png`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-ldap': patch
---
Make `ldapOrg` config key optional
@@ -0,0 +1,5 @@
---
'@backstage/backend-defaults': minor
---
This change introduces the `auditor` service implementation details.
@@ -0,0 +1,5 @@
---
'@backstage/backend-plugin-api': minor
---
This change introduces the `auditor` service definition.
@@ -0,0 +1,5 @@
---
'@backstage/backend-test-utils': minor
---
This change introduces mocks for the `auditor` service.
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': minor
---
This change integrates the `auditor` service into the Catalog plugin.
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': minor
---
This change integrates the `auditor` service into the Scaffolder plugin.
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-node': minor
---
This change introduces an optional `taskId` property to `TaskContext`.
+41 -1
View File
@@ -196,5 +196,45 @@
"@backstage/plugin-user-settings-backend": "0.2.29",
"@backstage/plugin-user-settings-common": "0.0.1"
},
"changesets": []
"changesets": [
"afraid-experts-explain",
"afraid-kids-jog",
"beige-dingos-destroy",
"bigint-convert",
"clean-squids-build",
"create-app-1737468383",
"curly-humans-prove",
"curvy-ways-play",
"cyan-grapes-confess",
"dry-flies-rhyme",
"dry-horses-report",
"eleven-mice-sleep",
"fair-mangos-sleep",
"few-shrimps-kiss",
"gentle-actors-sleep",
"green-jokes-provide",
"hungry-mirrors-sniff",
"loud-walls-build",
"old-moons-end",
"old-phones-rest",
"olive-boxes-hide-backend-defaults",
"olive-boxes-hide-backend-plugin-api",
"olive-boxes-hide-backend-test-utils",
"olive-boxes-hide-catalog-backend",
"olive-boxes-hide-scaffolder-backend",
"olive-boxes-hide-scaffolder-node",
"orange-brooms-lick",
"proud-dryers-act",
"proud-hornets-cheer",
"quiet-phones-sell",
"sharp-vans-protect",
"sharp-years-drive",
"short-vans-reflect",
"soft-planets-mate",
"tall-scissors-sip",
"tame-hats-shout",
"twelve-eyes-stare",
"warm-masks-ring",
"wise-apes-juggle"
]
}
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-scaffolder': patch
'@backstage/plugin-scaffolder-react': patch
---
Add schema output return type to the `makeFieldSchema` function return
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-plugin-api': patch
---
Allow route references to be installed in multiple app instances as long as their name is the same.
@@ -228,6 +228,7 @@ LocalStack
lockdown
lockfile
lookbehind
lowercased
lunr
Luxon
magiclink
@@ -27,10 +27,10 @@ example below on how to set your light and dark mode.
lang="css"
code={`/** Light theme **/
[data-theme='light'] {
--canon-accent: #1ed760;
--canon-bg-accent: #1ed760;
--canon-bg: #fff;
--canon-surface-1: #f5f5f5;
--canon-surface-2: #000;
--canon-bg-elevated: #f5f5f5;
--canon-bg: #000;
--canon-outline: #666;
--canon-outline-focus: #ccc;
--canon-text-primary: #f0f0f0;
@@ -42,10 +42,10 @@ example below on how to set your light and dark mode.
/** Dark theme **/
[data-theme='dark'] {
--canon-accent: #1ed760;
--canon-bg-accent: #1ed760;
--canon-bg: #000;
--canon-bg-elevated: #f5f5f5;
--canon-bg: #000;
--canon-surface-1: #f5f5f5;
--canon-surface-2: #000;
--canon-outline: #666;
--canon-outline-focus: #ccc;
--canon-text-primary: #fff;
@@ -73,7 +73,7 @@ application to match your brand.
<Table.Body>
<Table.Row>
<Table.Cell>
<Chip head>--canon-accent</Chip>
<Chip head>--canon-bg-accent</Chip>
</Table.Cell>
<Table.Cell>The accent color for the theme.</Table.Cell>
</Table.Row>
@@ -85,13 +85,13 @@ application to match your brand.
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-surface-1</Chip>
<Chip head>--canon-bg-elevated</Chip>
</Table.Cell>
<Table.Cell>The first surface color for the theme.</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
<Chip head>--canon-surface-2</Chip>
<Chip head>--canon-bg</Chip>
</Table.Cell>
<Table.Cell>The second surface color for the theme.</Table.Cell>
</Table.Row>
@@ -30,7 +30,7 @@
.breakpointContent {
height: 100%;
border-radius: 4px;
border: 1px solid var(--canon-border-base);
background-color: var(--canon-background-base);
border: 1px solid var(--canon-border);
background-color: var(--canon-bg);
padding: 16px;
}
+3 -3
View File
@@ -1,7 +1,7 @@
body {
display: flex;
flex-direction: row;
background-color: var(--canon-background);
background-color: var(--canon-bg);
color: var(--canon-text-primary);
transition: background-color 0.2s ease-in-out;
@@ -41,8 +41,8 @@ iframe {
}
.ͼ2 .cm-gutters {
background-color: var(--canon-surface-2);
border-right: 1px solid var(--canon-border-base);
background-color: var(--canon-bg);
border-right: 1px solid var(--canon-border);
transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out;
}
@@ -1,6 +1,6 @@
.codeBlock {
border-radius: 4px;
border: 1px solid var(--canon-border-base);
border: 1px solid var(--canon-border);
position: relative;
background: transparent;
overflow-x: auto;
@@ -15,7 +15,7 @@
}
.title {
border-bottom: 1px solid var(--canon-border-base);
border-bottom: 1px solid var(--canon-border);
padding: 12px 20px;
font-size: 0.875rem;
color: var(--canon-text-secondary);
@@ -11,13 +11,13 @@ import { createTheme } from '@uiw/codemirror-themes';
import { tags as t } from '@lezer/highlight';
const defaultTheme = `:root {
--canon-accent: #000;
--canon-bg-accent: #000;
}`;
const myTheme = createTheme({
theme: 'light',
settings: {
background: 'var(--canon-surface-1)',
background: 'var(--canon-bg-elevated)',
backgroundImage: '',
foreground: '#6182B8',
caret: '#5d00ff',
@@ -4,9 +4,9 @@
right: 16px;
width: 240px;
height: 47px;
background-color: var(--canon-surface-1);
background-color: var(--canon-bg-elevated);
border-radius: 0.375rem;
border: 1px solid var(--canon-border-base);
border: 1px solid var(--canon-border);
display: flex;
flex-direction: column;
overflow: hidden;
@@ -31,8 +31,8 @@
.header {
height: 46px;
flex-shrink: 0;
border-bottom: 1px solid var(--canon-border-base);
background-color: var(--canon-surface-1);
border-bottom: 1px solid var(--canon-border);
background-color: var(--canon-bg-elevated);
display: flex;
justify-content: space-between;
align-items: center;
@@ -65,7 +65,7 @@
height: 28px;
padding: 0 8px;
color: #fff;
background-color: var(--canon-surface-2);
background-color: var(--canon-bg);
color: var(--canon-text-primary);
transition: background-color 0.2s ease-in-out;
border-radius: 0.25rem;
@@ -1,9 +1,9 @@
.container {
display: flex;
background-color: var(--canon-surface-1);
background-color: var(--canon-bg-elevated);
padding: var(--canon-space-6);
border-radius: var(--canon-border-radius-xs);
border: 1px solid var(--canon-border-base);
border-radius: var(--canon-radius-2);
border: 1px solid var(--canon-border);
margin-bottom: var(--canon-space-6);
gap: var(--canon-space-6);
transition: background-color 0.2s ease-in-out;
@@ -29,14 +29,14 @@
align-items: center;
justify-content: center;
cursor: pointer;
background-color: var(--canon-surface-2);
border: 1px solid var(--canon-border-base);
border-radius: var(--canon-border-radius-xs);
background-color: var(--canon-bg);
border: 1px solid var(--canon-border);
border-radius: var(--canon-radius-2);
padding: 0 var(--canon-space-3);
height: 28px;
border-radius: 100px;
margin-top: var(--canon-space-3);
font-size: var(--canon-font-size-body);
font-size: var(--canon-font-size-3);
color: var(--canon-text-primary);
gap: var(--canon-space-2);
}
@@ -19,10 +19,10 @@
height: 80px;
border: 1px solid #d3d3d3;
border-radius: 0.5rem;
background-color: var(--canon-background);
background-color: var(--canon-bg);
transition: background-color 0.2s ease-in-out;
&:hover {
background-color: var(--canon-surface-1);
background-color: var(--canon-bg-elevated);
}
}
@@ -11,8 +11,8 @@
width: 300px;
height: 100vh;
color: var(--canon-text-primary);
background-color: var(--canon-surface-1);
border-right: 1px solid var(--canon-border-base);
background-color: var(--canon-bg-elevated);
border-right: 1px solid var(--canon-border);
padding-left: 20px;
padding-right: 20px;
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
@@ -44,7 +44,7 @@
.sectionTitle {
font-family: var(--docs-font);
font-size: var(--canon-font-size-body);
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-bold);
padding: 12px 0;
}
@@ -63,23 +63,23 @@
transition: background-color 0.2s ease-in-out;
&:hover {
background-color: var(--canon-surface-2);
background-color: var(--canon-bg);
}
}
.line.active {
background-color: var(--canon-surface-2);
background-color: var(--canon-bg);
}
.lineTitle {
font-family: var(--docs-font);
font-size: var(--canon-font-size-body);
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-text-primary);
}
.lineStatus {
font-family: var(--docs-font);
font-size: var(--canon-font-size-body);
font-size: var(--canon-font-size-3);
color: var(--canon-text-secondary);
}
@@ -6,8 +6,8 @@
.preview {
border-radius: 4px;
box-shadow: inset 0 0 0 1px var(--canon-border-base);
background-color: var(--canon-background);
box-shadow: inset 0 0 0 1px var(--canon-border);
background-color: var(--canon-bg);
transition: all 0.2s ease-in-out;
padding: 1px;
position: relative;
@@ -15,7 +15,7 @@
*/
.wrapper {
border: 1px solid var(--canon-border-base);
border: 1px solid var(--canon-border);
border-radius: 4px;
overflow: hidden;
margin-bottom: 1rem;
@@ -33,7 +33,7 @@
padding: 12px 16px !important;
border: none !important;
text-align: left;
background-color: var(--canon-surface-1) !important;
background-color: var(--canon-bg-elevated) !important;
font-size: 16px;
transition: background-color 0.2s ease-in-out;
@@ -43,14 +43,14 @@
}
.tableHeaderCell {
border-bottom: 1px solid var(--canon-border-base) !important;
border-bottom: 1px solid var(--canon-border) !important;
font-weight: 500;
font-size: 14px;
}
.tableRow {
border: none;
border-bottom: 1px solid var(--canon-border-base);
border-bottom: 1px solid var(--canon-border);
&:last-child {
border-bottom: none;
}
@@ -59,7 +59,7 @@
.tableChip {
display: inline-block;
font-size: 14px !important;
border: 1px solid var(--canon-border-base);
border: 1px solid var(--canon-border);
border-radius: 6px;
padding: 0px 6px;
height: 24px;
@@ -5,7 +5,7 @@
.list {
display: flex;
gap: var(--canon-space-6);
border-bottom: 1px solid var(--canon-border-base);
border-bottom: 1px solid var(--canon-border);
position: relative;
margin-bottom: var(--canon-space-6);
}
@@ -12,7 +12,7 @@
.tabsTheme {
width: 142px;
border-radius: 0.375rem;
background-color: var(--canon-surface-2);
background-color: var(--canon-bg);
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
@@ -28,7 +28,7 @@
height: 60px;
font-family: var(--docs-font);
color: var(--canon-text-secondary);
font-size: var(--canon-font-size-body);
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-bold);
cursor: pointer;
transition: color 0.2s ease-in-out;
@@ -53,7 +53,7 @@
position: absolute;
inset: 0.25rem 0;
border-radius: 0.25rem;
outline: 2px solid var(--canon-surface-1);
outline: 2px solid var(--canon-bg-elevated);
outline-offset: -1px;
}
}
@@ -4,8 +4,8 @@
left: 0;
right: 0;
z-index: 10;
background-color: var(--canon-surface-1);
border-bottom: 1px solid var(--canon-border-base);
background-color: var(--canon-bg-elevated);
border-bottom: 1px solid var(--canon-border);
height: 60px;
display: flex;
align-items: center;
@@ -17,7 +17,7 @@
color: var(--color-gray-900);
cursor: pointer;
user-select: none;
background-color: var(--canon-surface-2);
background-color: var(--canon-bg);
transition: background-color 0.2s ease-in-out;
&:focus-visible {
@@ -31,7 +31,7 @@
}
.SelectValue {
font-size: var(--canon-font-size-body);
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-regular);
}
@@ -43,9 +43,9 @@
box-sizing: border-box;
padding-block: 0.25rem;
border-radius: 0.375rem;
background-color: var(--canon-surface-1);
background-color: var(--canon-bg-elevated);
color: var(--color-gray-900);
border: 1px solid var(--canon-border-base);
border: 1px solid var(--canon-border);
padding-inline: 0.25rem;
transform-origin: var(--transform-origin);
transition: transform 150ms, opacity 150ms;
@@ -82,7 +82,7 @@
padding-block: 0.5rem;
padding-left: 0.625rem;
padding-right: 1rem;
font-size: var(--canon-font-size-body);
font-size: var(--canon-font-size-3);
font-weight: var(--canon-font-weight-bold);
display: grid;
gap: 0.5rem;
@@ -104,7 +104,7 @@
z-index: 0;
position: relative;
color: var(--color-gray-50);
background-color: var(--canon-surface-2);
background-color: var(--canon-bg);
}
&[data-highlighted]::before {
@@ -1,14 +1,14 @@
.tabs {
border-radius: 0.375rem;
width: 100%;
background-color: var(--canon-surface-2);
background-color: var(--canon-bg);
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
.tabsTheme {
width: 100px;
border-radius: 0.375rem;
background-color: var(--canon-surface-2);
background-color: var(--canon-bg);
transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;
}
@@ -57,7 +57,7 @@
position: absolute;
inset: 0.25rem 0;
border-radius: 0.25rem;
outline: 2px solid var(--canon-surface-1);
outline: 2px solid var(--canon-bg-elevated);
outline-offset: -1px;
}
}
@@ -76,7 +76,7 @@
width: var(--active-tab-width);
height: 1.5rem;
border-radius: 0.25rem;
background-color: var(--canon-surface-1);
background-color: var(--canon-bg-elevated);
transition-property: translate, width, background-color;
transition-duration: 200ms;
transition-timing-function: ease-in-out;
+2 -2
View File
@@ -77,11 +77,11 @@ export function useMDXComponents(components: MDXComponents): MDXComponents {
<code
style={{
fontFamily: 'var(--canon-font-monospace)',
backgroundColor: 'var(--canon-surface-1)',
backgroundColor: 'var(--canon-bg-elevated)',
padding: '0.2rem 0.375rem',
borderRadius: '0.25rem',
color: 'var(--canon-text-secondary)',
border: '1px solid var(--canon-border-base)',
border: '1px solid var(--canon-border)',
fontSize: '0.875rem',
}}
>
+9 -9
View File
@@ -925,11 +925,11 @@ __metadata:
linkType: hard
"@types/node@npm:^20":
version: 20.17.12
resolution: "@types/node@npm:20.17.12"
version: 20.17.16
resolution: "@types/node@npm:20.17.16"
dependencies:
undici-types: ~6.19.2
checksum: 0c0dbeb4e1480a23071ec38e97bb3d776e0ae9828174bf45f9edcf277caa955945cb10d31d84f7a73c66aaf92ae2e022be6331bd00a4bed1f2ad9639a411d17e
checksum: dd19c04c592b602f20448187b4b6ab0293f22173a8bbe53aa3b559188f078b5261d748a107b84ee0580e2074cda760049b13d6e210f9850039bead01cb666a3f
languageName: node
linkType: hard
@@ -5368,22 +5368,22 @@ __metadata:
linkType: hard
"typescript@npm:^5":
version: 5.7.2
resolution: "typescript@npm:5.7.2"
version: 5.7.3
resolution: "typescript@npm:5.7.3"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: b55300c4cefee8ee380d14fa9359ccb41ff8b54c719f6bc49b424899d662a5ce62ece390ce769568c7f4d14af844085255e63788740084444eb12ef423b13433
checksum: 6c38b1e989918e576f0307e6ee013522ea480dfce5f3ca85c9b2d8adb1edeffd37f4f30cd68de0c38a44563d12ba922bdb7e36aa2dac9c51de5d561e6e9a2e9c
languageName: node
linkType: hard
"typescript@patch:typescript@^5#~builtin<compat/typescript>":
version: 5.7.2
resolution: "typescript@patch:typescript@npm%3A5.7.2#~builtin<compat/typescript>::version=5.7.2&hash=5adc0c"
version: 5.7.3
resolution: "typescript@patch:typescript@npm%3A5.7.3#~builtin<compat/typescript>::version=5.7.3&hash=5adc0c"
bin:
tsc: bin/tsc
tsserver: bin/tsserver
checksum: 803430c6da2ba73c25a21880d8d4f08a56d9d2444e6db2ea949ac4abceeece8e4a442b7b9b585db7d8a0b47ebda2060e45fe8ee8b8aca23e27ec1d4844987ee6
checksum: 633cd749d6cd7bc842c6b6245847173bba99742a60776fae3c0fbcc0d1733cd51a733995e5f4dadd8afb0e64e57d3c7dbbeae953a072ee303940eca69e22f311
languageName: node
linkType: hard
@@ -41,7 +41,7 @@ There are however some ways to get this to work without too much effort.
yarn start
```
The default for `global-agent` is to have a prefix on the variable names, hence the need for specifying it twice.
The default for `global-agent` is to have a prefix on the variable names, hence the need for specifying it twice. For further information about `HTTP(S)_PROXY` and `NO_PROXY` excludes, see [the global-agent documentation](https://github.com/gajus/global-agent) and [undici documentation](https://github.com/nodejs/undici).
## Configuration
@@ -0,0 +1,87 @@
---
id: auditor
title: Auditor Service
sidebar_label: Auditor
description: Documentation for the Auditor service
---
## Overview
This document describes the Auditor Service, a software service designed to record and report on security-relevant events within an application. This service utilizes the `winston` library for logging and provides a flexible way to capture and format audit events.
## Key Features
- Provides a standardized way to capture security events.
- Allows categorization of events by severity level.
- Supports detailed metadata for each event.
- Offers success/failure reporting for events.
- Integrates with authentication and plugin services for enhanced context.
- Uses `winston` for flexible log formatting and transport.
- Provides a service factory for easy integration with Backstage plugins.
- Supports configurable log transports (console, file).
## How it Works
The Auditor Service defines a core class, `Auditor`, which implements the `AuditorService` interface. This class uses `winston` to log audit events with varying levels of severity and associated metadata. It also integrates with authentication and plugin services to capture actor details and plugin context.
The `auditorServiceFactory` creates an `Auditor` instance for the root context and provides a factory function for creating child loggers for individual plugins. This allows each plugin to have its own logger with inherited and additional metadata.
## Usage Guidance
The Auditor Service is designed for recording security-relevant events that require special attention or are subject to compliance regulations. These events often involve actions like:
- User session management
- Data access and modification
- System configuration changes
For general application logging that is not security-critical, you should use the standard `LoggerService` provided by Backstage. This helps to keep your audit logs focused and relevant.
## Using the Service
The Auditor Service can be accessed via dependency injection in your Backstage plugin. Here's an example of how to access the service and create an audit event within an Express route handler:
```typescript
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { auditor } = options;
const router = Router();
router.use(express.json());
router.post('/my-endpoint', async (req, res) => {
const auditorEvent = await auditor.createEvent({
eventId: 'my-endpoint-call',
request: req,
meta: {
// ... metadata about the request
},
});
try {
// ... process the request
await auditorEvent.success();
res.status(200).json({ message: 'Succeeded!' });
} catch (error) {
await auditorEvent.fail({ error });
res.status(500).json({ message: 'Failed!' });
throw error;
}
});
return router;
}
```
In this example, an audit event is created for each request to `/my-endpoint`. The `success` or `fail` methods are called based on the outcome of processing the request.
## Naming Conventions
When defining `eventId` and `subEventId` for your audit events, follow these guidelines:
- Use kebab-case (e.g., `user-login`, `file-download`, `fetch`, `entity-create`, `entity-update`).
- The `eventId` represents a logical group of similar events or operations. For example, "fetch" could be used as an `eventId` encompassing various fetch methods like `by-id` or `by-location`.
- Use `subEventId` to further categorize events within a logical group. For example, if the `eventId` is "fetch", the `subEventId` could be "by-id" or "by-location" to specify the method used for fetching.
- Avoid redundant prefixes related to the plugin ID, as that context is already provided.
- Choose names that clearly and concisely describe the event being audited.
+7 -1
View File
@@ -36,7 +36,13 @@ additional encoding:
The name is always required. Depending on the context, you may be able to leave
out the kind and/or namespace. If you do, it is contextual what values will be
used, and the relevant documentation should specify which rule applies where.
All strings are case insensitive.
Entity ref strings are frequently passed between systems as identifiers of
entities. In those cases the refs should always be complete (have all three
parts). The sender should ensure that the refs are always lowercased in an
`en-US` locale, preferably by using [the `stringifyEntityRef` function](https://backstage.io/docs/reference/catalog-model.stringifyentityref/)
which does this automatically. The receiver should treat incoming refs case
insensitively to avoid problems with senders who do not obey this rule.
```yaml
# Example:
File diff suppressed because it is too large Load Diff
+1
View File
@@ -515,6 +515,7 @@ The Backstage tooling supports [ECMAScript modules (ESM)](https://nodejs.org/doc
- To enable support for native ESM in tests, you need to run the tests with the `--experimental-vm-modules` flag enabled, typically via `NODE_OPTIONS='--experimental-vm-modules'`.
- Declaring a package as `"type": "module"` in `package.json` is supported, but in tests it will cause all local transitive dependencies to also be treated as ESM, regardless of whether they declare `"type": "module"` or not.
- When running tests with coverage enabled the default `babel` coverage provider can mess with the hoisting of named exports. This can be worked around by using the `v8` provider instead by setting `"coverageProvider": "v8"` in the Jest configuration, although note that the `v8` provider is a fair bit slower than the `babel` one.
- Node.js has an [ESM interoperability layer with CommonJS](https://nodejs.org/docs/latest-v22.x/api/esm.html#interoperability-with-commonjs) that allows for imports from ESM to identify named exports in CommonJS packages. This interoperability layer is **only** enabled when importing packages with a `.cts` or `.cjs` extension. This is because the interoperability layer is not fully compatible with the NPM ecosystem, and would break package if it was enabled for `.js` files.
- Dynamic imports of CommonJS packages will vary in shape depending on the runtime, i.e. test vs local development, etc. It is therefore recommended to avoid dynamic imports of CommonJS packages and instead use `require`, or to use the explicit CommonJS extensions as mentioned above. If you do need to dynamically import CommonJS packages, avoid using `default` exports, as the shape of them vary across different environments and you would otherwise need to manually unwrap the import based on the shape of the module object.
@@ -4,7 +4,7 @@ author: digital.ai
authorUrl: https://digital.ai/
category: CI/CD
description: The plugin offers integration with Digital.ai Deploy and backstage components and services. It provide access to deployments and reports.
documentation: https://docs.digital.ai/bundle/devops-deploy-version-v.24.1/page/deploy/concept/xl-deploy-backstage-overview.html
documentation: https://docs.digital.ai/deploy/docs/concept/xl-deploy-backstage-overview
iconUrl: /img/digital.ai-deploy.svg
npmPackageName: '@digital.ai/plugin-dai-deploy'
tags:
@@ -4,7 +4,7 @@ author: digital.ai
authorUrl: https://digital.ai/
category: CI/CD
description: The plugin offers integration with Digital.ai Release and backstage. It provide access to active releases, templates and manage workflow executions.
documentation: https://docs.digital.ai/bundle/devops-release-version-v.24.1/page/release/concept/release-backstage-overview.html
documentation: https://docs.digital.ai/release/docs/concept/release-backstage-overview
iconUrl: /img/digital.ai-release.svg
npmPackageName: '@digital-ai/plugin-dai-release'
tags:
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "root",
"version": "1.35.0",
"version": "1.36.0-next.0",
"private": true,
"repository": {
"type": "git",
@@ -1,5 +1,13 @@
# app-next-example-plugin
## 0.0.20-next.0
### Patch Changes
- Updated dependencies
- @backstage/frontend-plugin-api@0.9.5-next.0
- @backstage/core-components@0.16.3
## 0.0.19
### Patch Changes
@@ -1,6 +1,6 @@
{
"name": "app-next-example-plugin",
"version": "0.0.19",
"version": "0.0.20-next.0",
"description": "Backstage internal example plugin",
"backstage": {
"role": "frontend-plugin",
+45
View File
@@ -1,5 +1,50 @@
# example-app-next
## 0.0.20-next.0
### Patch Changes
- Updated dependencies
- @backstage/cli@0.30.0-next.0
- @backstage/plugin-notifications@0.5.2-next.0
- @backstage/plugin-search-react@1.8.6-next.0
- @backstage/plugin-search@1.4.23-next.0
- @backstage/plugin-techdocs@1.12.2-next.0
- @backstage/plugin-scaffolder@1.27.5-next.0
- @backstage/plugin-scaffolder-react@1.14.4-next.0
- @backstage/frontend-plugin-api@0.9.5-next.0
- @backstage/frontend-app-api@0.10.5-next.0
- @backstage/app-defaults@1.5.16
- @backstage/catalog-model@1.7.3
- @backstage/config@1.3.2
- @backstage/core-app-api@1.15.4
- @backstage/core-compat-api@0.3.6-next.0
- @backstage/core-components@0.16.3
- @backstage/core-plugin-api@1.10.3
- @backstage/frontend-defaults@0.1.6-next.0
- @backstage/integration-react@1.2.3
- @backstage/theme@0.6.3
- @backstage/plugin-api-docs@0.12.4-next.0
- @backstage/plugin-app@0.1.6-next.0
- @backstage/plugin-app-visualizer@0.1.16-next.0
- @backstage/plugin-auth-react@0.1.11
- @backstage/plugin-catalog@1.26.2-next.0
- @backstage/plugin-catalog-common@1.1.3
- @backstage/plugin-catalog-graph@0.4.16-next.0
- @backstage/plugin-catalog-import@0.12.10-next.0
- @backstage/plugin-catalog-react@1.15.2-next.0
- @backstage/plugin-catalog-unprocessed-entities@0.2.13
- @backstage/plugin-home@0.8.5-next.0
- @backstage/plugin-kubernetes@0.12.4-next.0
- @backstage/plugin-kubernetes-cluster@0.0.22-next.0
- @backstage/plugin-org@0.6.36-next.0
- @backstage/plugin-permission-react@0.4.30
- @backstage/plugin-search-common@1.2.17
- @backstage/plugin-signals@0.0.15
- @backstage/plugin-techdocs-module-addons-contrib@1.1.20
- @backstage/plugin-techdocs-react@1.2.13
- @backstage/plugin-user-settings@0.8.19-next.0
## 0.0.19
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-app-next",
"version": "0.0.19",
"version": "0.0.20-next.0",
"backstage": {
"role": "frontend"
},
+41
View File
@@ -1,5 +1,46 @@
# example-app
## 0.2.106-next.0
### Patch Changes
- Updated dependencies
- @backstage/cli@0.30.0-next.0
- @backstage/plugin-notifications@0.5.2-next.0
- @backstage/plugin-search-react@1.8.6-next.0
- @backstage/plugin-search@1.4.23-next.0
- @backstage/plugin-techdocs@1.12.2-next.0
- @backstage/plugin-scaffolder@1.27.5-next.0
- @backstage/plugin-scaffolder-react@1.14.4-next.0
- @backstage/frontend-app-api@0.10.5-next.0
- @backstage/app-defaults@1.5.16
- @backstage/catalog-model@1.7.3
- @backstage/config@1.3.2
- @backstage/core-app-api@1.15.4
- @backstage/core-components@0.16.3
- @backstage/core-plugin-api@1.10.3
- @backstage/integration-react@1.2.3
- @backstage/theme@0.6.3
- @backstage/plugin-api-docs@0.12.4-next.0
- @backstage/plugin-auth-react@0.1.11
- @backstage/plugin-catalog@1.26.2-next.0
- @backstage/plugin-catalog-common@1.1.3
- @backstage/plugin-catalog-graph@0.4.16-next.0
- @backstage/plugin-catalog-import@0.12.10-next.0
- @backstage/plugin-catalog-react@1.15.2-next.0
- @backstage/plugin-catalog-unprocessed-entities@0.2.13
- @backstage/plugin-devtools@0.1.24-next.0
- @backstage/plugin-home@0.8.5-next.0
- @backstage/plugin-kubernetes@0.12.4-next.0
- @backstage/plugin-kubernetes-cluster@0.0.22-next.0
- @backstage/plugin-org@0.6.36-next.0
- @backstage/plugin-permission-react@0.4.30
- @backstage/plugin-search-common@1.2.17
- @backstage/plugin-signals@0.0.15
- @backstage/plugin-techdocs-module-addons-contrib@1.1.20
- @backstage/plugin-techdocs-react@1.2.13
- @backstage/plugin-user-settings@0.8.19-next.0
## 0.2.105
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-app",
"version": "0.2.105",
"version": "0.2.106-next.0",
"backstage": {
"role": "frontend"
},
+14
View File
@@ -1,5 +1,19 @@
# @backstage/backend-app-api
## 1.1.2-next.0
### Patch Changes
- Updated dependencies
- @backstage/plugin-permission-node@0.8.8-next.0
- @backstage/config-loader@1.9.6-next.0
- @backstage/backend-plugin-api@1.2.0-next.0
- @backstage/cli-common@0.1.15
- @backstage/config@1.3.2
- @backstage/errors@1.2.7
- @backstage/types@1.2.1
- @backstage/plugin-auth-node@0.5.7-next.0
## 1.1.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-app-api",
"version": "1.1.1",
"version": "1.1.2-next.0",
"description": "Core API used by Backstage backend apps",
"backstage": {
"role": "node-library"
@@ -34,7 +34,7 @@ import type {
} from '../../../backend-plugin-api/src/wiring/types';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import type { InternalServiceFactory } from '../../../backend-plugin-api/src/services/system/types';
import { ForwardedError, ConflictError } from '@backstage/errors';
import { ForwardedError, ConflictError, assertError } from '@backstage/errors';
import {
instanceMetadataServiceRef,
featureDiscoveryServiceRef,
@@ -405,8 +405,9 @@ export class BackendInitializer {
// Once the plugin and all modules have been initialized, we can signal that the plugin has stared up successfully
const lifecycleService = await this.#getPluginLifecycleImpl(pluginId);
await lifecycleService.startup();
} catch (error) {
initLogger.onPluginFailed(pluginId);
} catch (error: unknown) {
assertError(error);
initLogger.onPluginFailed(pluginId, error);
throw error;
}
}),
@@ -27,7 +27,7 @@ export function createInitializationLogger(
rootLogger?: RootLoggerService,
): {
onPluginStarted(pluginId: string): void;
onPluginFailed(pluginId: string): void;
onPluginFailed(pluginId: string, error: Error): void;
onAllStarted(): void;
} {
const logger = rootLogger?.child({ type: 'initialization' });
@@ -68,14 +68,15 @@ export function createInitializationLogger(
starting.delete(pluginId);
started.add(pluginId);
},
onPluginFailed(pluginId: string) {
onPluginFailed(pluginId: string, error: Error) {
starting.delete(pluginId);
const status =
starting.size > 0
? `, waiting for ${starting.size} other plugins to finish before shutting down the process`
: '';
logger?.error(
`Plugin '${pluginId}' threw an error during startup${status}`,
`Plugin '${pluginId}' threw an error during startup${status}.`,
error,
);
},
onAllStarted() {
+26
View File
@@ -1,5 +1,31 @@
# @backstage/backend-defaults
## 0.8.0-next.0
### Minor Changes
- a4aa244: This change introduces the `auditor` service implementation details.
### Patch Changes
- f866b86: Internal refactor to use explicit `require` for lazy-loading dependency.
- a19cb2b: Added default implementation for the new `PermissionsRegistryService`.
- Updated dependencies
- @backstage/cli-node@0.2.13-next.0
- @backstage/plugin-permission-node@0.8.8-next.0
- @backstage/config-loader@1.9.6-next.0
- @backstage/backend-plugin-api@1.2.0-next.0
- @backstage/backend-app-api@1.1.2-next.0
- @backstage/backend-dev-utils@0.1.5
- @backstage/cli-common@0.1.15
- @backstage/config@1.3.2
- @backstage/errors@1.2.7
- @backstage/integration@1.16.1
- @backstage/integration-aws-node@0.1.15
- @backstage/types@1.2.1
- @backstage/plugin-auth-node@0.5.7-next.0
- @backstage/plugin-events-node@0.4.8-next.0
## 0.7.0
### Minor Changes
+5 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-defaults",
"version": "0.7.0",
"version": "0.8.0-next.0",
"description": "Backend defaults used by Backstage backend apps",
"backstage": {
"role": "node-library"
@@ -20,6 +20,7 @@
"license": "Apache-2.0",
"exports": {
".": "./src/index.ts",
"./auditor": "./src/entrypoints/auditor/index.ts",
"./auth": "./src/entrypoints/auth/index.ts",
"./cache": "./src/entrypoints/cache/index.ts",
"./database": "./src/entrypoints/database/index.ts",
@@ -44,6 +45,9 @@
"types": "src/index.ts",
"typesVersions": {
"*": {
"auditor": [
"src/entrypoints/auditor/index.ts"
],
"auth": [
"src/entrypoints/auth/index.ts"
],
@@ -0,0 +1,111 @@
## API Report File for "@backstage/backend-defaults"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import { AuditorService } from '@backstage/backend-plugin-api';
import type { AuditorServiceCreateEventOptions } from '@backstage/backend-plugin-api';
import type { AuditorServiceEvent } from '@backstage/backend-plugin-api';
import type { AuditorServiceEventSeverityLevel } from '@backstage/backend-plugin-api';
import type { AuthService } from '@backstage/backend-plugin-api';
import type { Format } from 'logform';
import type { HttpAuthService } from '@backstage/backend-plugin-api';
import type { JsonObject } from '@backstage/types';
import type { PluginMetadataService } from '@backstage/backend-plugin-api';
import type { Request as Request_2 } from 'express';
import { ServiceFactory } from '@backstage/backend-plugin-api';
import * as winston from 'winston';
// @public
export type AuditorEvent = {
plugin: string;
eventId: string;
severityLevel: AuditorServiceEventSeverityLevel;
actor: AuditorEventActorDetails;
meta?: JsonObject;
request?: AuditorEventRequest;
} & AuditorEventStatus;
// @public (undocumented)
export type AuditorEventActorDetails = {
actorId?: string;
ip?: string;
hostname?: string;
userAgent?: string;
};
// @public
export type AuditorEventOptions<TMeta extends JsonObject> = {
eventId: string;
severityLevel?: AuditorServiceEventSeverityLevel;
request?: Request_2<any, any, any, any, any>;
meta?: TMeta;
} & AuditorEventStatus;
// @public (undocumented)
export type AuditorEventRequest = {
url: string;
method: string;
};
// @public (undocumented)
export type AuditorEventStatus =
| {
status: 'initiated';
}
| {
status: 'succeeded';
}
| {
status: 'failed';
error: string;
};
// @public
export type AuditorLogFunction = (event: AuditorEvent) => void | Promise<void>;
// @public
export const auditorServiceFactory: ServiceFactory<
AuditorService,
'plugin',
'singleton'
>;
// @public
export class DefaultAuditorService implements AuditorService {
static create(
logFn: AuditorLogFunction,
deps: {
auth: AuthService;
httpAuth: HttpAuthService;
plugin: PluginMetadataService;
},
): DefaultAuditorService;
// (undocumented)
createEvent(
options: AuditorServiceCreateEventOptions,
): Promise<AuditorServiceEvent>;
}
// @public
export class WinstonRootAuditorService {
static create(
options?: WinstonRootAuditorServiceOptions,
): WinstonRootAuditorService;
// (undocumented)
forPlugin(deps: {
auth: AuthService;
httpAuth: HttpAuthService;
plugin: PluginMetadataService;
}): AuditorService;
}
// @public
export type WinstonRootAuditorServiceOptions = {
meta?: JsonObject;
format?: Format;
transports?: winston.transport[];
};
// (No @packageDocumentation comment for this package)
```
@@ -15,6 +15,7 @@
*/
import { Backend, createSpecializedBackend } from '@backstage/backend-app-api';
import { auditorServiceFactory } from '@backstage/backend-defaults/auditor';
import { authServiceFactory } from '@backstage/backend-defaults/auth';
import { cacheServiceFactory } from '@backstage/backend-defaults/cache';
import { databaseServiceFactory } from '@backstage/backend-defaults/database';
@@ -36,6 +37,7 @@ import { userInfoServiceFactory } from '@backstage/backend-defaults/userInfo';
import { eventsServiceFactory } from '@backstage/plugin-events-node';
export const defaultServiceFactories = [
auditorServiceFactory,
authServiceFactory,
cacheServiceFactory,
rootConfigServiceFactory,
@@ -0,0 +1,144 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { mockServices } from '@backstage/backend-test-utils';
import { DefaultAuditorService } from './DefaultAuditorService';
const mockDeps = {
auth: mockServices.auth.mock(),
httpAuth: mockServices.httpAuth.mock(),
plugin: {
getId: () => 'test',
},
};
describe('DefaultAuditorService', () => {
it('creates a auditor instance with default options', () => {
const auditor = DefaultAuditorService.create(jest.fn(), mockDeps);
expect(auditor).toBeInstanceOf(DefaultAuditorService);
});
it('should log a status "initiated" using createEvent', async () => {
const logFn = jest.fn();
const auditor = DefaultAuditorService.create(logFn, mockDeps);
await auditor.createEvent({
eventId: 'test-event',
});
expect(logFn).toHaveBeenCalledWith({
eventId: 'test-event',
status: 'initiated',
plugin: 'test',
severityLevel: 'low',
actor: {},
});
});
it('should log a status "succeeded" using createEvent', async () => {
const logFn = jest.fn();
const auditor = DefaultAuditorService.create(logFn, mockDeps);
const auditorEvent = await auditor.createEvent({
eventId: 'test-event',
});
await auditorEvent.success();
expect(logFn).toHaveBeenCalledTimes(2);
expect(logFn).toHaveBeenLastCalledWith({
eventId: 'test-event',
status: 'succeeded',
plugin: 'test',
severityLevel: 'low',
actor: {},
});
});
it('should log a status "failed"', async () => {
const logFn = jest.fn();
const auditor = DefaultAuditorService.create(logFn, mockDeps);
const auditorEvent = await auditor.createEvent({
eventId: 'test-event',
});
const error = new Error('error');
await auditorEvent.fail({ error });
expect(logFn).toHaveBeenCalledTimes(2);
expect(logFn).toHaveBeenLastCalledWith({
eventId: 'test-event',
status: 'failed',
error: error.toString(),
plugin: 'test',
severityLevel: 'low',
actor: {},
});
});
it('should use root meta', async () => {
const logFn = jest.fn();
const auditor = DefaultAuditorService.create(logFn, mockDeps);
const auditorEvent = await auditor.createEvent({
eventId: 'test-event',
meta: {
initiated: 'test',
},
});
await auditorEvent.success({ meta: { succeeded: 'test' } });
const error = new Error('error');
await auditorEvent.fail({ error, meta: { failed: 'test' } });
expect(logFn).toHaveBeenCalledTimes(3);
expect(logFn).toHaveBeenNthCalledWith(1, {
eventId: 'test-event',
status: 'initiated',
meta: {
initiated: 'test',
},
plugin: 'test',
severityLevel: 'low',
actor: {},
});
expect(logFn).toHaveBeenNthCalledWith(2, {
eventId: 'test-event',
status: 'succeeded',
meta: {
initiated: 'test',
succeeded: 'test',
},
plugin: 'test',
severityLevel: 'low',
actor: {},
});
expect(logFn).toHaveBeenNthCalledWith(3, {
eventId: 'test-event',
status: 'failed',
meta: {
initiated: 'test',
failed: 'test',
},
error: error.toString(),
plugin: 'test',
severityLevel: 'low',
actor: {},
});
});
});
@@ -0,0 +1,230 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type {
AuditorService,
AuditorServiceCreateEventOptions,
AuditorServiceEvent,
AuditorServiceEventSeverityLevel,
AuthService,
BackstageCredentials,
HttpAuthService,
PluginMetadataService,
} from '@backstage/backend-plugin-api';
import { ForwardedError } from '@backstage/errors';
import type { JsonObject } from '@backstage/types';
import type { Request } from 'express';
/** @public */
export type AuditorEventActorDetails = {
actorId?: string;
ip?: string;
hostname?: string;
userAgent?: string;
};
/** @public */
export type AuditorEventRequest = {
url: string;
method: string;
};
/** @public */
export type AuditorEventStatus =
| { status: 'initiated' }
| { status: 'succeeded' }
| {
status: 'failed';
error: string;
};
/**
* Options for creating an auditor event.
*
* @public
*/
export type AuditorEventOptions<TMeta extends JsonObject> = {
/**
* Use kebab-case to name audit events (e.g., "user-login", "file-download").
*
* The `pluginId` already provides plugin/module context, so avoid redundant prefixes in the `eventId`.
*/
eventId: string;
severityLevel?: AuditorServiceEventSeverityLevel;
/** (Optional) The associated HTTP request, if applicable. */
request?: Request<any, any, any, any, any>;
/** (Optional) Additional metadata relevant to the event, structured as a JSON object. */
meta?: TMeta;
} & AuditorEventStatus;
/**
* Common fields of an audit event.
*
* @public
*/
export type AuditorEvent = {
plugin: string;
eventId: string;
severityLevel: AuditorServiceEventSeverityLevel;
actor: AuditorEventActorDetails;
meta?: JsonObject;
request?: AuditorEventRequest;
} & AuditorEventStatus;
/**
* Logging function used by the auditor.
* @public
*/
export type AuditorLogFunction = (event: AuditorEvent) => void | Promise<void>;
/**
* A {@link @backstage/backend-plugin-api#AuditorService} implementation that logs events using a provided callback.
*
* @public
*
* @example
* ```ts
* export const auditorServiceFactory = createServiceFactory({
* service: coreServices.auditor,
* deps: {
* logger: coreServices.logger,
* auth: coreServices.auth,
* httpAuth: coreServices.httpAuth,
* plugin: coreServices.pluginMetadata,
* },
* factory({ logger, plugin, auth, httpAuth }) {
* const auditLogger = logger.child({ isAuditEvent: true });
* return DefaultAuditorService.create(
* event => auditLogger.info(`${event.plugin}.${event.eventId}`, event),
* { plugin, auth, httpAuth },
* );
* },
* });
* ```
*/
export class DefaultAuditorService implements AuditorService {
private readonly logFn: AuditorLogFunction;
private readonly auth: AuthService;
private readonly httpAuth: HttpAuthService;
private readonly plugin: PluginMetadataService;
private constructor(
logFn: AuditorLogFunction,
deps: {
auth: AuthService;
httpAuth: HttpAuthService;
plugin: PluginMetadataService;
},
) {
this.logFn = logFn;
this.auth = deps.auth;
this.httpAuth = deps.httpAuth;
this.plugin = deps.plugin;
}
/**
* Creates a {@link DefaultAuditorService} instance.
*/
static create(
logFn: AuditorLogFunction,
deps: {
auth: AuthService;
httpAuth: HttpAuthService;
plugin: PluginMetadataService;
},
): DefaultAuditorService {
return new DefaultAuditorService(logFn, deps);
}
private async log<TMeta extends JsonObject>(
options: AuditorEventOptions<TMeta>,
): Promise<void> {
const { eventId, severityLevel = 'low', request, meta, ...rest } = options;
await this.logFn({
plugin: this.plugin.getId(),
eventId,
severityLevel,
actor: {
actorId: await this.getActorId(request),
ip: request?.ip,
hostname: request?.hostname,
userAgent: request?.get('user-agent'),
},
request: request
? {
url: request?.originalUrl,
method: request?.method,
}
: undefined,
meta: Object.keys(meta ?? {}).length === 0 ? undefined : meta,
...rest,
});
}
async createEvent(
options: AuditorServiceCreateEventOptions,
): Promise<AuditorServiceEvent> {
await this.log({ ...options, status: 'initiated' });
return {
success: async params => {
await this.log({
...options,
meta: { ...options.meta, ...params?.meta },
status: 'succeeded',
});
},
fail: async params => {
await this.log({
...options,
...params,
error: String(params.error),
meta: { ...options.meta, ...params?.meta },
status: 'failed',
});
},
};
}
private async getActorId(
request?: Request<any, any, any, any, any>,
): Promise<string | undefined> {
let credentials: BackstageCredentials =
await this.auth.getOwnServiceCredentials();
if (request) {
try {
credentials = await this.httpAuth.credentials(request);
} catch (error) {
throw new ForwardedError('Could not resolve credentials', error);
}
}
if (this.auth.isPrincipal(credentials, 'user')) {
return credentials.principal.userEntityRef;
}
if (this.auth.isPrincipal(credentials, 'service')) {
return credentials.principal.subject;
}
return undefined;
}
}
@@ -0,0 +1,169 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { mockServices } from '@backstage/backend-test-utils';
import { WinstonRootAuditorService } from './WinstonRootAuditorService';
import { DefaultAuditorService } from './DefaultAuditorService';
describe('WinstonRootAuditorService', () => {
it('creates a auditor instance with default options', () => {
const auditor = WinstonRootAuditorService.create();
expect(auditor).toBeInstanceOf(WinstonRootAuditorService);
});
it('creates a child logger', () => {
const auditor = WinstonRootAuditorService.create();
const childLogger = auditor.forPlugin({
auth: mockServices.auth.mock(),
httpAuth: mockServices.httpAuth.mock(),
plugin: {
getId: () => 'test-plugin',
},
});
expect(childLogger).toBeInstanceOf(DefaultAuditorService);
});
it('should log a status "initiated" using createEvent', async () => {
const pluginId = 'test-plugin';
const auditor = WinstonRootAuditorService.create().forPlugin({
auth: mockServices.auth.mock(),
httpAuth: mockServices.httpAuth.mock(),
plugin: {
getId: () => pluginId,
},
});
// workaround to spy on private method
const auditorSpy = jest.spyOn(auditor as any, 'log');
await auditor.createEvent({
eventId: 'test-event',
});
expect(auditorSpy).toHaveBeenCalledWith({
eventId: 'test-event',
status: 'initiated',
});
});
it('should log a status "succeeded" using createEvent', async () => {
const pluginId = 'test-plugin';
const auditor = WinstonRootAuditorService.create().forPlugin({
auth: mockServices.auth.mock(),
httpAuth: mockServices.httpAuth.mock(),
plugin: {
getId: () => pluginId,
},
});
// workaround to spy on private method
const auditorSpy = jest.spyOn(auditor as any, 'log');
const auditorEvent = await auditor.createEvent({
eventId: 'test-event',
});
await auditorEvent.success();
expect(auditorSpy).toHaveBeenCalledTimes(2);
expect(auditorSpy).toHaveBeenLastCalledWith({
eventId: 'test-event',
meta: {},
status: 'succeeded',
});
});
it('should log a status "failed"', async () => {
const pluginId = 'test-plugin';
const auditor = WinstonRootAuditorService.create().forPlugin({
auth: mockServices.auth.mock(),
httpAuth: mockServices.httpAuth.mock(),
plugin: {
getId: () => pluginId,
},
});
// workaround to spy on private method
const auditorSpy = jest.spyOn(auditor as any, 'log');
const auditorEvent = await auditor.createEvent({
eventId: 'test-event',
});
const error = new Error('error');
await auditorEvent.fail({ error });
expect(auditorSpy).toHaveBeenCalledTimes(2);
expect(auditorSpy).toHaveBeenLastCalledWith({
eventId: 'test-event',
meta: {},
status: 'failed',
error: error.toString(),
});
});
it('should use root meta', async () => {
const pluginId = 'test-plugin';
const auditor = WinstonRootAuditorService.create().forPlugin({
auth: mockServices.auth.mock(),
httpAuth: mockServices.httpAuth.mock(),
plugin: {
getId: () => pluginId,
},
});
// workaround to spy on private method
const auditorSpy = jest.spyOn(auditor as any, 'log');
const auditorEvent = await auditor.createEvent({
eventId: 'test-event',
meta: {
initiated: 'test',
},
});
await auditorEvent.success({ meta: { succeeded: 'test' } });
const error = new Error('error');
await auditorEvent.fail({ error, meta: { failed: 'test' } });
expect(auditorSpy).toHaveBeenCalledTimes(3);
expect(auditorSpy).toHaveBeenNthCalledWith(1, {
eventId: 'test-event',
status: 'initiated',
meta: {
initiated: 'test',
},
});
expect(auditorSpy).toHaveBeenNthCalledWith(2, {
eventId: 'test-event',
status: 'succeeded',
meta: {
initiated: 'test',
succeeded: 'test',
},
});
expect(auditorSpy).toHaveBeenNthCalledWith(3, {
eventId: 'test-event',
status: 'failed',
meta: {
initiated: 'test',
failed: 'test',
},
error: error.toString(),
});
});
});
@@ -0,0 +1,119 @@
/*
* Copyright 2025 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type {
AuditorService,
AuthService,
HttpAuthService,
PluginMetadataService,
} from '@backstage/backend-plugin-api';
import type { JsonObject } from '@backstage/types';
import type { Format } from 'logform';
import * as winston from 'winston';
import { WinstonLogger } from '../rootLogger';
import { DefaultAuditorService } from './DefaultAuditorService';
/** @public */
export const defaultFormatter = winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json(),
);
/**
* Adds `isAuditEvent` field
*
* @public
*/
export const auditorFieldFormat = winston.format(info => {
return { ...info, isAuditEvent: true };
})();
/**
* Options for creating a {@link WinstonRootAuditorService}.
* @public
*/
export type WinstonRootAuditorServiceOptions = {
meta?: JsonObject;
format?: Format;
transports?: winston.transport[];
};
/**
* An implementation of the {@link @backstage/backend-plugin-api#AuditorService} that logs events using a separate winston logger.
*
* @public
*
* @example
* ```ts
* export const auditorServiceFactory = createServiceFactory({
* service: coreServices.auditor,
* deps: {
* auth: coreServices.auth,
* httpAuth: coreServices.httpAuth,
* plugin: coreServices.pluginMetadata,
* },
* createRootContext() {
* return WinstonRootAuditorService.create();
* },
* factory({ plugin, auth, httpAuth }, root) {
* return root.forPlugin({ plugin, auth, httpAuth });
* },
* });
* ```
*/
export class WinstonRootAuditorService {
private constructor(private readonly winstonLogger: WinstonLogger) {}
/**
* Creates a {@link WinstonRootAuditorService} instance.
*/
static create(
options?: WinstonRootAuditorServiceOptions,
): WinstonRootAuditorService {
let winstonLogger = WinstonLogger.create({
meta: {
service: 'backstage',
},
level: 'info',
format: winston.format.combine(
auditorFieldFormat,
options?.format ?? defaultFormatter,
),
transports: options?.transports,
});
if (options?.meta) {
winstonLogger = winstonLogger.child(options.meta) as WinstonLogger;
}
return new WinstonRootAuditorService(winstonLogger);
}
forPlugin(deps: {
auth: AuthService;
httpAuth: HttpAuthService;
plugin: PluginMetadataService;
}): AuditorService {
return DefaultAuditorService.create(
e => this.winstonLogger.info(`${e.plugin}.${e.eventId}`, e),
deps,
);
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
coreServices,
createServiceFactory,
} from '@backstage/backend-plugin-api';
import { DefaultAuditorService } from './DefaultAuditorService';
/**
* Plugin-level auditing.
*
* See {@link @backstage/code-plugin-api#AuditorService}
* and {@link https://backstage.io/docs/backend-system/core-services/auditor | the service docs}
* for more information.
*
* @public
*/
export const auditorServiceFactory = createServiceFactory({
service: coreServices.auditor,
deps: {
logger: coreServices.logger,
auth: coreServices.auth,
httpAuth: coreServices.httpAuth,
plugin: coreServices.pluginMetadata,
},
factory({ logger, plugin, auth, httpAuth }) {
const auditLogger = logger.child({ isAuditEvent: true });
return DefaultAuditorService.create(
event => auditLogger.info(`${event.plugin}.${event.eventId}`, event),
{ plugin, auth, httpAuth },
);
},
});
@@ -0,0 +1,28 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export type {
AuditorEvent,
AuditorEventActorDetails,
AuditorEventOptions,
AuditorEventRequest,
AuditorEventStatus,
AuditorLogFunction,
} from './DefaultAuditorService';
export { DefaultAuditorService } from './DefaultAuditorService';
export { auditorServiceFactory } from './auditorServiceFactory';
export { WinstonRootAuditorService } from './WinstonRootAuditorService';
export type { WinstonRootAuditorServiceOptions } from './WinstonRootAuditorService';
@@ -1,5 +1,31 @@
# @backstage/backend-dynamic-feature-service
## 0.5.4-next.0
### Patch Changes
- 96c20cd: Make sure changes are successfully tracked before starting up scanner.
- Updated dependencies
- @backstage/cli-node@0.2.13-next.0
- @backstage/plugin-permission-node@0.8.8-next.0
- @backstage/backend-defaults@0.8.0-next.0
- @backstage/config-loader@1.9.6-next.0
- @backstage/plugin-catalog-backend@1.31.0-next.0
- @backstage/backend-plugin-api@1.2.0-next.0
- @backstage/plugin-scaffolder-node@0.7.0-next.0
- @backstage/backend-app-api@1.1.2-next.0
- @backstage/cli-common@0.1.15
- @backstage/config@1.3.2
- @backstage/errors@1.2.7
- @backstage/types@1.2.1
- @backstage/plugin-app-node@0.1.30-next.0
- @backstage/plugin-auth-node@0.5.7-next.0
- @backstage/plugin-events-backend@0.4.2-next.0
- @backstage/plugin-events-node@0.4.8-next.0
- @backstage/plugin-permission-common@0.8.4
- @backstage/plugin-search-backend-node@1.3.8-next.0
- @backstage/plugin-search-common@1.2.17
## 0.5.3
### Patch Changes
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-dynamic-feature-service",
"version": "0.5.3",
"version": "0.5.4-next.0",
"description": "Backstage dynamic feature service",
"backstage": {
"role": "node-library"
+40
View File
@@ -1,5 +1,45 @@
# example-backend-legacy
## 0.2.107-next.0
### Patch Changes
- Updated dependencies
- @backstage/plugin-permission-node@0.8.8-next.0
- @backstage/backend-defaults@0.8.0-next.0
- @backstage/plugin-catalog-backend@1.31.0-next.0
- @backstage/plugin-kubernetes-backend@0.19.3-next.0
- @backstage/plugin-catalog-backend-module-unprocessed@0.5.5-next.0
- @backstage/plugin-catalog-node@1.15.2-next.0
- @backstage/backend-plugin-api@1.2.0-next.0
- @backstage/plugin-scaffolder-backend@1.30.0-next.0
- @backstage/plugin-search-backend-module-catalog@0.3.1-next.0
- @backstage/plugin-permission-backend@0.5.54-next.0
- @backstage/plugin-search-backend@1.8.2-next.0
- @backstage/catalog-client@1.9.1
- @backstage/catalog-model@1.7.3
- @backstage/config@1.3.2
- @backstage/integration@1.16.1
- @backstage/plugin-app-backend@0.4.5-next.0
- @backstage/plugin-auth-backend@0.24.3-next.0
- @backstage/plugin-auth-node@0.5.7-next.0
- @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.5-next.0
- @backstage/plugin-events-backend@0.4.2-next.0
- @backstage/plugin-events-node@0.4.8-next.0
- @backstage/plugin-permission-common@0.8.4
- @backstage/plugin-proxy-backend@0.5.11-next.0
- @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.6-next.0
- @backstage/plugin-scaffolder-backend-module-gitlab@0.7.2-next.0
- @backstage/plugin-scaffolder-backend-module-rails@0.5.6-next.0
- @backstage/plugin-search-backend-module-elasticsearch@1.6.5-next.0
- @backstage/plugin-search-backend-module-explore@0.2.8-next.0
- @backstage/plugin-search-backend-module-pg@0.5.41-next.0
- @backstage/plugin-search-backend-module-techdocs@0.3.6-next.0
- @backstage/plugin-search-backend-node@1.3.8-next.0
- @backstage/plugin-signals-backend@0.3.1-next.0
- @backstage/plugin-signals-node@0.1.17-next.0
- @backstage/plugin-techdocs-backend@1.11.6-next.0
## 0.2.106
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend-legacy",
"version": "0.2.106",
"version": "0.2.107-next.0",
"backstage": {
"role": "backend"
},
@@ -1,5 +1,14 @@
# @backstage/backend-openapi-utils
## 0.4.2-next.0
### Patch Changes
- Updated dependencies
- @backstage/backend-plugin-api@1.2.0-next.0
- @backstage/errors@1.2.7
- @backstage/types@1.2.1
## 0.4.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-openapi-utils",
"version": "0.4.1",
"version": "0.4.2-next.0",
"description": "OpenAPI typescript support.",
"backstage": {
"role": "node-library"
+18
View File
@@ -1,5 +1,23 @@
# @backstage/backend-plugin-api
## 1.2.0-next.0
### Minor Changes
- a4aa244: This change introduces the `auditor` service definition.
### Patch Changes
- 9ddfd94: Added new `PermissionsRegistryService` that is used by plugins to register permissions, resource types, and rules into the permission system. This replaces the existing `createPermissionIntegrationRouter` from `@backstage/plugin-permission-node`.
- Updated dependencies
- @backstage/plugin-permission-node@0.8.8-next.0
- @backstage/cli-common@0.1.15
- @backstage/config@1.3.2
- @backstage/errors@1.2.7
- @backstage/types@1.2.1
- @backstage/plugin-auth-node@0.5.7-next.0
- @backstage/plugin-permission-common@0.8.4
## 1.1.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-plugin-api",
"version": "1.1.1",
"version": "1.2.0-next.0",
"description": "Core API used by Backstage backend plugins",
"backstage": {
"role": "node-library"
+30
View File
@@ -26,6 +26,35 @@ import { Readable } from 'stream';
import type { Request as Request_2 } from 'express';
import type { Response as Response_2 } from 'express';
// @public
export interface AuditorService {
// (undocumented)
createEvent(
options: AuditorServiceCreateEventOptions,
): Promise<AuditorServiceEvent>;
}
// @public (undocumented)
export type AuditorServiceCreateEventOptions = {
eventId: string;
severityLevel?: AuditorServiceEventSeverityLevel;
request?: Request_2<any, any, any, any, any>;
meta?: JsonObject;
};
// @public (undocumented)
export type AuditorServiceEvent = {
success(options?: { meta?: JsonObject }): Promise<void>;
fail(options: { meta?: JsonObject; error: Error }): Promise<void>;
};
// @public
export type AuditorServiceEventSeverityLevel =
| 'low'
| 'medium'
| 'high'
| 'critical';
// @public
export interface AuthService {
authenticate(
@@ -185,6 +214,7 @@ export namespace coreServices {
const httpRouter: ServiceRef<HttpRouterService, 'plugin', 'singleton'>;
const lifecycle: ServiceRef<LifecycleService, 'plugin', 'singleton'>;
const logger: ServiceRef<LoggerService, 'plugin', 'singleton'>;
const auditor: ServiceRef<AuditorService, 'plugin', 'singleton'>;
const permissions: ServiceRef<PermissionsService, 'plugin', 'singleton'>;
const permissionsRegistry: ServiceRef<
PermissionsRegistryService,
@@ -0,0 +1,73 @@
/*
* Copyright 2024 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { JsonObject } from '@backstage/types';
import type { Request } from 'express';
/**
* low (default): normal usage
* medium: accessing write endpoints
* high: non-root permission changes
* critical: root permission changes
* @public
*/
export type AuditorServiceEventSeverityLevel =
| 'low'
| 'medium'
| 'high'
| 'critical';
/** @public */
export type AuditorServiceCreateEventOptions = {
/**
* Use kebab-case to name audit events (e.g., "user-login", "file-download", "fetch"). Represents a logical group of similar events or operations. For example, "fetch" could be used as an eventId encompassing various fetch methods like "by-id" or "by-location".
*
* The `pluginId` already provides plugin/module context, so avoid redundant prefixes in the `eventId`.
*/
eventId: string;
/** (Optional) The severity level for the audit event. */
severityLevel?: AuditorServiceEventSeverityLevel;
/** (Optional) The associated HTTP request, if applicable. */
request?: Request<any, any, any, any, any>;
/**
* (Optional) Additional metadata relevant to the event, structured as a JSON object.
* This could include a `queryType` field, using kebab-case, for variations within the main event (e.g., "by-id", "by-user").
* For example, if the `eventId` is "fetch", the `queryType` in `meta` could be "by-id" or "by-location".
*/
meta?: JsonObject;
};
/** @public */
export type AuditorServiceEvent = {
success(options?: { meta?: JsonObject }): Promise<void>;
fail(options: { meta?: JsonObject; error: Error }): Promise<void>;
};
/**
* A service that provides an auditor facility.
*
* See the {@link https://backstage.io/docs/backend-system/core-services/auditor | service documentation} for more details.
*
* @public
*/
export interface AuditorService {
createEvent(
options: AuditorServiceCreateEventOptions,
): Promise<AuditorServiceEvent>;
}
@@ -161,6 +161,19 @@ export namespace coreServices {
import('./LoggerService').LoggerService
>({ id: 'core.logger' });
/**
* Plugin-level auditing.
*
* See {@link AuditorService}
* and {@link https://backstage.io/docs/backend-system/core-services/auditor | the service docs}
* for more information.
*
* @public
*/
export const auditor = createServiceRef<
import('./AuditorService').AuditorService
>({ id: 'core.auditor' });
/**
* Permission system integration for authorization of user actions.
*
@@ -14,36 +14,39 @@
* limitations under the License.
*/
export { coreServices } from './coreServices';
export type {
AuditorService,
AuditorServiceCreateEventOptions,
AuditorServiceEvent,
AuditorServiceEventSeverityLevel,
} from './AuditorService';
export type {
AuthService,
BackstageCredentials,
BackstageUserPrincipal,
BackstageServicePrincipal,
BackstageNonePrincipal,
BackstagePrincipalAccessRestrictions,
BackstagePrincipalTypes,
BackstageNonePrincipal,
BackstageServicePrincipal,
BackstageUserPrincipal,
} from './AuthService';
export type {
CacheService,
CacheServiceOptions,
CacheServiceSetOptions,
} from './CacheService';
export type { RootConfigService } from './RootConfigService';
export type { DatabaseService } from './DatabaseService';
export type { DiscoveryService } from './DiscoveryService';
export type { RootHealthService } from './RootHealthService';
export type { HttpAuthService } from './HttpAuthService';
export type {
HttpRouterService,
HttpRouterServiceAuthPolicy,
} from './HttpRouterService';
export type { HttpAuthService } from './HttpAuthService';
export type {
LifecycleService,
LifecycleServiceStartupHook,
LifecycleServiceStartupOptions,
LifecycleServiceShutdownHook,
LifecycleServiceShutdownOptions,
LifecycleServiceStartupHook,
LifecycleServiceStartupOptions,
} from './LifecycleService';
export type { LoggerService } from './LoggerService';
export type {
@@ -55,6 +58,8 @@ export type {
PermissionsRegistryServiceAddResourceTypeOptions,
} from './PermissionsRegistryService';
export type { PluginMetadataService } from './PluginMetadataService';
export type { RootConfigService } from './RootConfigService';
export type { RootHealthService } from './RootHealthService';
export type { RootHttpRouterService } from './RootHttpRouterService';
export type { RootLifecycleService } from './RootLifecycleService';
export type { RootLoggerService } from './RootLoggerService';
@@ -69,15 +74,16 @@ export type {
SchedulerServiceTaskScheduleDefinitionConfig,
} from './SchedulerService';
export type {
UrlReaderService,
UrlReaderServiceReadTreeOptions,
UrlReaderServiceReadTreeResponse,
UrlReaderServiceReadTreeResponseDirOptions,
UrlReaderServiceReadTreeResponseFile,
UrlReaderServiceReadUrlResponse,
UrlReaderServiceReadUrlOptions,
UrlReaderServiceReadUrlResponse,
UrlReaderServiceSearchOptions,
UrlReaderServiceSearchResponse,
UrlReaderServiceSearchResponseFile,
UrlReaderService,
} from './UrlReaderService';
export type { BackstageUserInfo, UserInfoService } from './UserInfoService';
export { coreServices } from './coreServices';
+21
View File
@@ -1,5 +1,26 @@
# @backstage/backend-test-utils
## 1.3.0-next.0
### Minor Changes
- dd05a97: Added mocks for the new `PermissionsRegistryService`.
- a4aa244: This change introduces mocks for the `auditor` service.
### Patch Changes
- f866b86: Internal refactor to use explicit `require` for lazy-loading dependency.
- fb051f2: Sync feature installation compatibility logic with `@backstage/backend-app-api`.
- Updated dependencies
- @backstage/backend-defaults@0.8.0-next.0
- @backstage/backend-plugin-api@1.2.0-next.0
- @backstage/backend-app-api@1.1.2-next.0
- @backstage/config@1.3.2
- @backstage/errors@1.2.7
- @backstage/types@1.2.1
- @backstage/plugin-auth-node@0.5.7-next.0
- @backstage/plugin-events-node@0.4.8-next.0
## 1.2.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/backend-test-utils",
"version": "1.2.1",
"version": "1.3.0-next.0",
"description": "Test helpers library for Backstage backends",
"backstage": {
"role": "node-library"
+10
View File
@@ -8,6 +8,7 @@
/// <reference types="node" />
/// <reference types="qs" />
import { AuditorService } from '@backstage/backend-plugin-api';
import { AuthService } from '@backstage/backend-plugin-api';
import { Backend } from '@backstage/backend-app-api';
import { BackendFeature } from '@backstage/backend-plugin-api';
@@ -153,6 +154,15 @@ export function mockErrorHandler(): ErrorRequestHandler<
// @public
export namespace mockServices {
// (undocumented)
export namespace auditor {
const // (undocumented)
factory: () => ServiceFactory<AuditorService, 'plugin', 'singleton'>;
const // (undocumented)
mock: (
partialImpl?: Partial<AuditorService> | undefined,
) => ServiceMock<AuditorService>;
}
// (undocumented)
export function auth(options?: {
pluginId?: string;
@@ -21,6 +21,7 @@ import { httpRouterServiceFactory } from '@backstage/backend-defaults/httpRouter
import { lifecycleServiceFactory } from '@backstage/backend-defaults/lifecycle';
import { loggerServiceFactory } from '@backstage/backend-defaults/logger';
import { permissionsServiceFactory } from '@backstage/backend-defaults/permissions';
import { permissionsRegistryServiceFactory } from '@backstage/backend-defaults/permissionsRegistry';
import { rootHealthServiceFactory } from '@backstage/backend-defaults/rootHealth';
import { rootHttpRouterServiceFactory } from '@backstage/backend-defaults/rootHttpRouter';
import { rootLifecycleServiceFactory } from '@backstage/backend-defaults/rootLifecycle';
@@ -47,13 +48,13 @@ import {
eventsServiceRef,
} from '@backstage/plugin-events-node';
import { JsonObject } from '@backstage/types';
import { Knex } from 'knex';
import { MockAuthService } from './MockAuthService';
import { MockHttpAuthService } from './MockHttpAuthService';
import { MockRootLoggerService } from './MockRootLoggerService';
import { MockUserInfoService } from './MockUserInfoService';
import { mockCredentials } from './mockCredentials';
import { Knex } from 'knex';
import { permissionsRegistryServiceFactory } from '@backstage/backend-defaults/permissionsRegistry';
import { auditorServiceFactory } from '@backstage/backend-defaults/auditor';
/** @internal */
function createLoggerMock() {
@@ -221,6 +222,19 @@ export namespace mockServices {
}));
}
export namespace auditor {
export const factory = () => auditorServiceFactory;
export const mock = simpleMock(coreServices.auditor, () => ({
createEvent: jest.fn(async _ => {
return {
success: jest.fn(),
fail: jest.fn(),
};
}),
}));
}
export function auth(options?: {
pluginId?: string;
disableDefaultAuthPolicy?: boolean;
@@ -67,6 +67,7 @@ export interface TestBackend extends Backend {
export const defaultServiceFactories = [
mockServices.auth.factory(),
mockServices.auditor.factory(),
mockServices.cache.factory(),
mockServices.rootConfig.factory(),
mockServices.database.factory(),
+38
View File
@@ -1,5 +1,43 @@
# example-backend
## 0.0.35-next.0
### Patch Changes
- Updated dependencies
- @backstage/plugin-permission-node@0.8.8-next.0
- @backstage/plugin-notifications-backend@0.5.2-next.0
- @backstage/backend-defaults@0.8.0-next.0
- @backstage/plugin-catalog-backend@1.31.0-next.0
- @backstage/plugin-kubernetes-backend@0.19.3-next.0
- @backstage/plugin-catalog-backend-module-unprocessed@0.5.5-next.0
- @backstage/backend-plugin-api@1.2.0-next.0
- @backstage/plugin-scaffolder-backend@1.30.0-next.0
- @backstage/plugin-catalog-backend-module-openapi@0.2.7-next.0
- @backstage/plugin-search-backend-module-catalog@0.3.1-next.0
- @backstage/plugin-devtools-backend@0.5.2-next.0
- @backstage/plugin-permission-backend@0.5.54-next.0
- @backstage/plugin-permission-backend-module-allow-all-policy@0.2.5-next.0
- @backstage/plugin-search-backend@1.8.2-next.0
- @backstage/catalog-model@1.7.3
- @backstage/plugin-app-backend@0.4.5-next.0
- @backstage/plugin-auth-backend@0.24.3-next.0
- @backstage/plugin-auth-backend-module-github-provider@0.2.5-next.0
- @backstage/plugin-auth-backend-module-guest-provider@0.2.5-next.0
- @backstage/plugin-auth-node@0.5.7-next.0
- @backstage/plugin-catalog-backend-module-backstage-openapi@0.4.5-next.0
- @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.5-next.0
- @backstage/plugin-events-backend@0.4.2-next.0
- @backstage/plugin-permission-common@0.8.4
- @backstage/plugin-proxy-backend@0.5.11-next.0
- @backstage/plugin-scaffolder-backend-module-github@0.5.6-next.0
- @backstage/plugin-scaffolder-backend-module-notifications@0.1.7-next.0
- @backstage/plugin-search-backend-module-explore@0.2.8-next.0
- @backstage/plugin-search-backend-module-techdocs@0.3.6-next.0
- @backstage/plugin-search-backend-node@1.3.8-next.0
- @backstage/plugin-signals-backend@0.3.1-next.0
- @backstage/plugin-techdocs-backend@1.11.6-next.0
## 0.0.34
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-backend",
"version": "0.0.34",
"version": "0.0.35-next.0",
"backstage": {
"role": "backend"
},
+14 -15
View File
@@ -29,43 +29,43 @@ const preview: Preview = {
},
viewport: {
viewports: {
xs: {
name: 'XSmall',
initial: {
name: 'Initial',
styles: {
width: '320px',
height: '100%',
},
},
small: {
name: 'Small',
xs: {
name: 'Extra Small',
styles: {
width: '640px',
height: '100%',
},
},
medium: {
name: 'Medium',
sm: {
name: 'Small',
styles: {
width: '768px',
height: '100%',
},
},
large: {
name: 'Large',
md: {
name: 'Medium',
styles: {
width: '1024px',
height: '100%',
},
},
xlarge: {
name: 'XLarge',
lg: {
name: 'Large',
styles: {
width: '1280px',
height: '100%',
},
},
'2xl': {
name: '2XL',
xl: {
name: 'Extra Large',
styles: {
width: '1536px',
height: '100%',
@@ -85,12 +85,11 @@ const preview: Preview = {
defaultTheme: 'Light',
}),
Story => {
document.body.style.backgroundColor = 'var(--canon-background)';
document.body.style.backgroundColor = 'var(--canon-bg)';
const docsStoryElements = document.getElementsByClassName('docs-story');
Array.from(docsStoryElements).forEach(element => {
(element as HTMLElement).style.backgroundColor =
'var(--canon-background)';
(element as HTMLElement).style.backgroundColor = 'var(--canon-bg)';
});
return (
+14 -14
View File
@@ -54,35 +54,35 @@
[data-theme-name='legacy'][data-theme='light'] {
/* Colors */
--canon-accent: #2e77d0;
--canon-background: #f8f8f8;
--canon-surface-1: #fff;
--canon-surface-2: #f4f4f4;
--canon-bg-accent: #2e77d0;
--canon-bg: #f8f8f8;
--canon-bg-elevated: #fff;
--canon-bg: #f4f4f4;
/* Text colors */
--canon-text-primary: #000;
--canon-text-primary-on-accent: #fff;
--canon-fg-accent: #fff;
--canon-text-secondary: #646464;
}
[data-theme-name='legacy'][data-theme='dark'] {
/* Colors */
--canon-accent: #fff;
--canon-background: #000;
--canon-surface-1: #121212;
--canon-surface-2: #1a1a1a;
--canon-bg-accent: #fff;
--canon-bg: #000;
--canon-bg-elevated: #121212;
--canon-bg: #1a1a1a;
/* Borders */
--canon-border-base: rgba(255, 255, 255, 0.2);
--canon-border: rgba(255, 255, 255, 0.2);
--canon-border-warning: #f50000;
--canon-border-error: #f87503;
--canon-border-selected: #25d262;
--canon-border-danger: #f87503;
--canon-border-focus: #25d262;
/* States - Add more states */
--canon-error: #f50000;
--canon-fg-danger: #f50000;
/* Text colors */
--canon-text-primary: #fff;
--canon-text-primary-on-accent: #000;
--canon-fg-accent: #000;
--canon-text-secondary: #b3b3b3;
}
+7
View File
@@ -0,0 +1,7 @@
# @backstage/canon
## 0.1.0-next.0
### Minor Changes
- 65f4acc: This is the first alpha release for Canon. As part of this release we are introducing 5 layout components and 7 components. All theming is done through CSS variables.
+7 -1
View File
@@ -1,6 +1,6 @@
# @backstage/canon
_This package was created through the Backstage CLI_.
Canon is a UI component library for Backstage.
## Installation
@@ -10,3 +10,9 @@ Install the package via Yarn:
cd <package-dir> # if within a monorepo
yarn add @backstage/canon
```
## Documentation
- [Canon Documentation](https://canon.backstage.io)
- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md)
- [Backstage Documentation](https://backstage.io/docs)
+3 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@backstage/canon",
"version": "0.0.0",
"version": "0.1.0-next.0",
"backstage": {
"role": "web-library"
},
@@ -9,11 +9,10 @@
"main": "dist/index.esm.js",
"types": "dist/index.d.ts"
},
"private": true,
"keywords": [
"backstage"
],
"homepage": "https://backstage.io",
"homepage": "https://canon.backstage.io",
"repository": {
"type": "git",
"url": "https://github.com/backstage/backstage",
@@ -28,9 +27,9 @@
],
"scripts": {
"build": "yarn build:app && yarn build:css",
"build-storybook": "storybook build",
"build:app": "backstage-cli package build",
"build:css": "node scripts/build-css.mjs",
"build-storybook": "storybook build",
"clean": "backstage-cli package clean",
"lint": "backstage-cli package lint",
"prepack": "backstage-cli package prepack",
+1 -7
View File
@@ -68,7 +68,7 @@ export interface BoxProps extends UtilityProps {
}
// @public (undocumented)
export type Breakpoint = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
export type Breakpoint = 'initial' | 'xs' | 'sm' | 'md' | 'lg' | 'xl';
// @public (undocumented)
export const Button: React_2.ForwardRefExoticComponent<
@@ -101,12 +101,6 @@ export interface ButtonProps {
// @public (undocumented)
export interface CanonContextProps {
// (undocumented)
breakpoint: Breakpoint;
// (undocumented)
getResponsiveValue: (
value: string | Partial<Record<Breakpoint, string>>,
) => string;
// (undocumented)
icons: IconMap;
}
@@ -382,7 +382,7 @@ export const BorderRadius: Story = {
export const Border: Story = {
args: {
style: {
background: 'var(--canon-surface-1)',
background: 'var(--canon-bg-elevated)',
color: 'var(--canon-text-primary)',
padding: '4px 8px',
width: '80px',
@@ -114,7 +114,7 @@ export const Responsive: Story = {
args: {
children: 'Button',
variant: {
xs: 'primary',
initial: 'primary',
sm: 'secondary',
md: 'tertiary',
},
@@ -18,9 +18,10 @@
import React, { forwardRef } from 'react';
import { Icon } from '../Icon';
import { ButtonProps } from './types';
import { useCanon } from '../../contexts/canon';
import clsx from 'clsx';
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
import type { ButtonProps } from './types';
/** @public */
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
@@ -37,11 +38,9 @@ export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
...rest
} = props;
const { getResponsiveValue } = useCanon();
// Get the responsive value for the variant
const responsiveSize = getResponsiveValue(size);
const responsiveVariant = getResponsiveValue(variant);
const responsiveSize = useResponsiveValue(size);
const responsiveVariant = useResponsiveValue(variant);
return (
<button
@@ -22,16 +22,16 @@
user-select: none;
font-family: var(--canon-font-regular);
font-weight: var(--canon-font-weight-bold);
font-size: var(--canon-font-size-body);
font-size: var(--canon-font-size-3);
padding: 0;
transition: all 150ms ease;
cursor: pointer;
border-radius: 8px;
border-radius: var(--canon-radius-2);
}
.canon-Button--variant-primary {
background-color: var(--canon-accent);
color: var(--canon-text-primary-on-accent);
background-color: var(--canon-bg-accent);
color: var(--canon-fg-accent);
&:hover {
background-color: transparent;
@@ -42,7 +42,7 @@
.canon-Button--variant-secondary {
background-color: transparent;
box-shadow: inset 0 0 0 1px var(--canon-border-base);
box-shadow: inset 0 0 0 1px var(--canon-border);
color: var(--canon-text-primary);
&:hover {
@@ -5,13 +5,13 @@
justify-content: center;
width: 1rem;
height: 1rem;
box-shadow: inset 0 0 0 1px var(--canon-border-base);
box-shadow: inset 0 0 0 1px var(--canon-border);
cursor: pointer;
border-radius: 2px;
transition: all 0.2s ease-in-out;
&[data-checked] {
background-color: var(--canon-accent);
background-color: var(--canon-bg-accent);
}
}
@@ -20,7 +20,7 @@
flex-direction: row;
align-items: center;
justify-content: center;
gap: var(--canon-spacing-xs);
gap: var(--canon-space-2);
font-size: var(--canon-font-size-xs);
font-family: var(--canon-font-regular);
color: var(--canon-text-primary);
@@ -37,5 +37,5 @@
display: flex;
align-items: center;
justify-content: center;
color: var(--canon-text-primary-on-accent);
color: var(--canon-fg-accent);
}
@@ -22,14 +22,14 @@
}
.canon-FieldLabel {
font-size: var(--canon-font-size-caption);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-text-primary);
margin-bottom: var(--canon-space-1_5);
}
.canon-FieldDescription {
font-size: var(--canon-font-size-caption);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-text-secondary);
margin: 0;
@@ -37,15 +37,15 @@
}
.canon-FieldError {
font-size: var(--canon-font-size-caption);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-error);
color: var(--canon-fg-danger);
margin: 0;
padding-top: var(--canon-space-1_5);
}
.canon-FieldValidity {
font-size: var(--canon-font-size-caption);
font-size: var(--canon-font-size-2);
font-weight: var(--canon-font-weight-regular);
color: var(--canon-text-secondary);
margin: 0;
@@ -17,9 +17,10 @@
'use client';
import React, { forwardRef } from 'react';
import { HeadingProps } from './types';
import { useCanon } from '../../contexts/canon';
import clsx from 'clsx';
import { useResponsiveValue } from '../../hooks/useResponsiveValue';
import type { HeadingProps } from './types';
/** @public */
export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
@@ -31,10 +32,9 @@ export const Heading = forwardRef<HTMLHeadingElement, HeadingProps>(
className,
...restProps
} = props;
const { getResponsiveValue } = useCanon();
// Get the responsive value for the variant
const responsiveVariant = getResponsiveValue(variant);
const responsiveVariant = useResponsiveValue(variant);
// Determine the component to render based on the variant
let Component = as;
@@ -23,22 +23,22 @@
}
.canon-Heading--variant-display {
font-size: var(--canon-font-size-display);
font-size: var(--canon-font-size-10);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title1 {
font-size: var(--canon-font-size-title1);
font-size: var(--canon-font-size-9);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title2 {
font-size: var(--canon-font-size-title2);
font-size: var(--canon-font-size-8);
font-weight: var(--canon-font-weight-bold);
}
.canon-Heading--variant-title3 {
font-size: var(--canon-font-size-title3);
font-size: var(--canon-font-size-7);
font-weight: var(--canon-font-weight-bold);
}
@@ -48,6 +48,6 @@
}
.canon-Heading--variant-title5 {
font-size: var(--canon-font-size-title5);
font-size: var(--canon-font-size-5);
font-weight: var(--canon-font-weight-bold);
}

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