Compare commits
88 Commits
9f1dd4ae9b
...
c2e852b3a3
| Author | SHA1 | Date | |
|---|---|---|---|
| c2e852b3a3 | |||
| 372eb5ab6b | |||
| 1f709a386b | |||
| 0d8d90b351 | |||
| b55138e4ba | |||
| 496982782b | |||
| c2f9d8c25d | |||
| 78f6fd3b5b | |||
| a4f2c56110 | |||
| 64c8108166 | |||
| 8f7c02a525 | |||
| 39e0c4f155 | |||
| 854c88cde4 | |||
| 89f08c5304 | |||
| 19c94c49e8 | |||
| 813e7d9ef6 | |||
| d0e64a1603 | |||
| b2117003c4 | |||
| 11f2765e63 | |||
| afe3160dfa | |||
| 6ef44643ee | |||
| ddd22005e9 | |||
| 37b29c240b | |||
| 47fc640b49 | |||
| 4857351bf3 | |||
| 1fc84c8df8 | |||
| bbb9ca105f | |||
| 44f017668b | |||
| 593d011eea | |||
| ce6a326c5c | |||
| 9dfb8948ea | |||
| c630594e7a | |||
| 42a907dfe0 | |||
| 72152935ef | |||
| 5b07b4ef04 | |||
| ce2e763606 | |||
| 8930d77157 | |||
| 3346845548 | |||
| ebcc8b7ca9 | |||
| 3692a346a5 | |||
| 05d4126db0 | |||
| f9cee7bc13 | |||
| 2d181c035d | |||
| 2a85164e49 | |||
| 6fe88aabb3 | |||
| a71ae90198 | |||
| b33bb24b5a | |||
| 58fb313f22 | |||
| 18644ee7a2 | |||
| 5aa867c86f | |||
| 28e5198e23 | |||
| 150f290178 | |||
| de9873e142 | |||
| 378784ebf0 | |||
| 34f21c39a5 | |||
| 70fc9e0370 | |||
| 55009be8a4 | |||
| 826788803e | |||
| 73ac7983a6 | |||
| 635eea3ac7 | |||
| 8d2316f276 | |||
| 74b16ec203 | |||
| d43186ac5a | |||
| 2b9bb3c4d2 | |||
| d2c181f539 | |||
| f56177b47e | |||
| ada73f2ba4 | |||
| 65456c31b6 | |||
| 68db890456 | |||
| b75158b2c1 | |||
| 72db53e9fb | |||
| 8dae41216d | |||
| 843f71caf4 | |||
| a30cf94882 | |||
| 241d359913 | |||
| cd677edc56 | |||
| f2698df4a6 | |||
| e8c67ea82c | |||
| 19b5047c12 | |||
| 92c3435d9e | |||
| 327cc8b09c | |||
| c3e07a5cb3 | |||
| 6b96557718 | |||
| 19a79d3f80 | |||
| dbe93a7aee | |||
| 61e0844bde | |||
| 18430adf40 | |||
| 2259e99a6e |
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Bumped create-app version.
|
||||
@@ -2,4 +2,4 @@
|
||||
'@backstage/eslint-plugin': patch
|
||||
---
|
||||
|
||||
Adds a new `@backstage/no-deprecated-bui-tokens` lint rule that warns when a deprecated `@backstage/ui` CSS token is referenced in a JavaScript or TypeScript file. The rule is included in the `recommended` config, so plugin authors using `plugin:@backstage/recommended` will receive warnings automatically when using tokens that have been superseded by the new semantic color families.
|
||||
Adds a new `@backstage/no-deprecated-bui-tokens` lint rule that warns when a deprecated `@backstage/ui` CSS token is referenced in a JavaScript or TypeScript file (including CSS-in-JS patterns and template literals). The rule is included in the `recommended` config, so plugin authors using `plugin:@backstage/recommended` will receive warnings automatically when using tokens that have been superseded by the new semantic color families. Note that plain CSS and CSS module files are outside ESLint's scope and are not covered by this rule.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Adapted Azure-related tests for the Azure SDK upgrade to ESM-style exports. The `AzureBlobStorageUrlReader` now accepts an optional `createContainerClient` dependency for testability without needing to mock the `@azure/storage-blob` module.
|
||||
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@backstage/catalog-client': patch
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
---
|
||||
|
||||
Moved dependencies that are re-exported in the public API from `devDependencies` to `dependencies`. These were incorrectly demoted in #33936 because the source code only uses type imports, but the types still appear in the published API surface and need to be resolvable by consumers at build time.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-backend-module-msgraph': patch
|
||||
---
|
||||
|
||||
Reverted the server-side `accountEnabled eq true` base filter that was added in v1.51.0, which broke the `userGroupMember` path because the group members endpoint doesn't support `$filter` on that property. Disabled users (`accountEnabled === false`) are now filtered client-side in both the `/users` and group members paths. The mutual exclusivity checks between `userFilter` and `userGroupMemberFilter`/`userGroupMemberSearch` have been restored.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/integration': patch
|
||||
---
|
||||
|
||||
Changed visibility of Bitbucket username as it is not a secret.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-test-utils': patch
|
||||
---
|
||||
|
||||
Increased MySQL connection and pool timeouts to reduce flaky `connect ETIMEDOUT` failures in CI. The test MySQL container now also uses `mysql_native_password` for cheaper connection handshakes and disables binary logging.
|
||||
+32
-1
@@ -227,5 +227,36 @@
|
||||
"@backstage/plugin-user-settings-backend": "0.4.3",
|
||||
"@backstage/plugin-user-settings-common": "0.1.0"
|
||||
},
|
||||
"changesets": []
|
||||
"changesets": [
|
||||
"catalog-backend-totalitems-mode",
|
||||
"catalog-client-totalitems-mode",
|
||||
"catalog-react-split-count",
|
||||
"create-app-1779809101",
|
||||
"dependabot-84d9635",
|
||||
"dependabot-a525941",
|
||||
"disable-experimental-public-entrypoint",
|
||||
"eager-birds-fly",
|
||||
"fast-stars-smile",
|
||||
"fix-azure-blob-storage-entity-provider-import",
|
||||
"fix-azure-blob-storage-integration-class-name",
|
||||
"fix-azure-blob-storage-url-reader-type",
|
||||
"fix-entity-list-triple-fetch",
|
||||
"fix-home-translation-key-typo",
|
||||
"fix-related-entities-card-property-typo",
|
||||
"fix-table-filters-class-key-typo",
|
||||
"fix-test-route-matching",
|
||||
"legal-results-kneel",
|
||||
"notifications-backend-openapi-router",
|
||||
"plenty-worms-happen",
|
||||
"proxied-signin-base64url-token",
|
||||
"rate-limit-ipv6-key-generator",
|
||||
"remove-immediate-stitching",
|
||||
"remove-protobufjs-pins",
|
||||
"renovate-dcee473",
|
||||
"search-extended-statistics",
|
||||
"split-query-entities-count",
|
||||
"stitch-claim-transaction",
|
||||
"stitch-queue-no-overlap",
|
||||
"two-books-sleep"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/core-components': patch
|
||||
---
|
||||
|
||||
Fix autologout not working correctly when closing all tabs
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/backend-defaults': patch
|
||||
---
|
||||
|
||||
Fix gitlabUrlReader issue with retrieving the repository archive tree
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Added a new `NumberField` component for numeric input with support for min, max, step, and keyboard increment/decrement.
|
||||
|
||||
**Affected components:** NumberField
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/ui': patch
|
||||
---
|
||||
|
||||
Fixed Header breadcrumb typography so it remains consistent when component styles are loaded in different orders.
|
||||
|
||||
**Affected components:** Header
|
||||
@@ -10,15 +10,7 @@ The previous tokens remain in place for backward compatibility but are now depre
|
||||
|
||||
**Neutral backgrounds**
|
||||
|
||||
The neutral background scale has been renamed and extended. `--bui-bg-app` is replaced by the new `--bui-bg-neutral-1`, and the old overlay-based `--bui-bg-neutral-1..4` shift up by one to become `--bui-bg-neutral-2..5`:
|
||||
|
||||
| Deprecated | Replacement |
|
||||
| ---------------------------------- | -------------------- |
|
||||
| `--bui-bg-app` | `--bui-bg-neutral-1` |
|
||||
| `--bui-bg-neutral-1` (old overlay) | `--bui-bg-neutral-2` |
|
||||
| `--bui-bg-neutral-2` (old overlay) | `--bui-bg-neutral-3` |
|
||||
| `--bui-bg-neutral-3` (old overlay) | `--bui-bg-neutral-4` |
|
||||
| `--bui-bg-neutral-4` (old overlay) | `--bui-bg-neutral-5` |
|
||||
The neutral background tokens keep their existing names (`--bui-bg-app`, `--bui-bg-neutral-1` through `--bui-bg-neutral-4`) but are updated with new solid-color values for both light and dark themes. No token renaming is required. The `-hover`, `-pressed`, and `-disabled` interaction variants of these tokens are deprecated and should be removed.
|
||||
|
||||
**Foreground**
|
||||
|
||||
@@ -69,5 +61,3 @@ The neutral background scale has been renamed and extended. `--bui-bg-app` is re
|
||||
| `--bui-bg-info` | `--bui-announcement-bg-subdued` |
|
||||
| `--bui-fg-info-on-bg` | `--bui-announcement-fg-subdued` |
|
||||
| `--bui-border-info` | `--bui-announcement-border` |
|
||||
|
||||
**Affected components:** Colors
|
||||
|
||||
@@ -31,6 +31,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
|
||||
@@ -53,6 +53,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
@@ -89,6 +90,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
@@ -262,6 +264,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
|
||||
@@ -35,6 +35,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
|
||||
@@ -64,6 +64,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
@@ -137,6 +138,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
@@ -228,6 +230,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
# Stable docs
|
||||
- name: checkout latest release
|
||||
|
||||
@@ -75,6 +75,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
with:
|
||||
|
||||
@@ -30,6 +30,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
|
||||
@@ -20,6 +20,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
|
||||
@@ -24,6 +24,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
with:
|
||||
|
||||
@@ -36,6 +36,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
|
||||
@@ -23,6 +23,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
|
||||
@@ -23,6 +23,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
with:
|
||||
|
||||
@@ -34,6 +34,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn --immutable
|
||||
|
||||
@@ -38,6 +38,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: Install dependencies
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
|
||||
@@ -71,6 +71,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
with:
|
||||
|
||||
@@ -56,6 +56,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: setup python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
|
||||
@@ -69,6 +69,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
@@ -139,6 +140,7 @@ jobs:
|
||||
with:
|
||||
node-version: 22.x
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
- name: yarn install
|
||||
uses: backstage/actions/yarn-install@2cd6978b476cbdc39fec48346f8b6ca13199dd6a # v0.7.8
|
||||
|
||||
@@ -40,6 +40,7 @@ jobs:
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
registry-url: https://registry.npmjs.org/ # Needed for auth
|
||||
corepack: true
|
||||
|
||||
# Windows file operation slowness means there's no point caching this
|
||||
- name: yarn install
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Fix 406 response for repository/archive retrieval in gitlabUrlReader
|
||||
@@ -0,0 +1 @@
|
||||
Restore runtime dependencies incorrectly demoted to devDependencies
|
||||
@@ -0,0 +1 @@
|
||||
Fix msgraph userGroupMember filter error by filtering disabled users client-side
|
||||
@@ -213,6 +213,7 @@
|
||||
}
|
||||
|
||||
[data-theme-mode='dark'][data-theme-name='spotify'] {
|
||||
--bui-bg-app: var(--bui-black);
|
||||
--bui-ring: rgba(255, 255, 255, 0.2);
|
||||
--bui-accent-bg: #1ed760;
|
||||
--bui-accent-bg-hover: #3be477;
|
||||
@@ -221,7 +222,6 @@
|
||||
--bui-accent-fg-disabled: #62ab7c;
|
||||
|
||||
/* Deprecated tokens */
|
||||
--bui-bg-app: var(--bui-black);
|
||||
--bui-bg-solid: #1ed760;
|
||||
--bui-bg-solid-hover: #3be477;
|
||||
--bui-bg-solid-pressed: #1abc54;
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
'use client';
|
||||
|
||||
import { NumberField } from '../../../../../packages/ui/src/components/NumberField/NumberField';
|
||||
import { Flex } from '../../../../../packages/ui/src/components/Flex/Flex';
|
||||
import { RiTimeLine } from '@remixicon/react';
|
||||
|
||||
export const WithLabel = () => {
|
||||
return (
|
||||
<NumberField
|
||||
name="quantity"
|
||||
placeholder="Enter a number"
|
||||
label="Label"
|
||||
style={{ maxWidth: '300px' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sizes = () => {
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
gap="4"
|
||||
style={{ width: '100%', maxWidth: '300px' }}
|
||||
>
|
||||
<NumberField
|
||||
name="quantity"
|
||||
placeholder="Enter a number"
|
||||
size="small"
|
||||
icon={<RiTimeLine />}
|
||||
/>
|
||||
<NumberField
|
||||
name="quantity"
|
||||
placeholder="Enter a number"
|
||||
size="medium"
|
||||
icon={<RiTimeLine />}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithDescription = () => {
|
||||
return (
|
||||
<NumberField
|
||||
name="quantity"
|
||||
placeholder="Enter a number"
|
||||
label="Label"
|
||||
description="Description"
|
||||
style={{ maxWidth: '300px' }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,70 @@
|
||||
import { PropsTable } from '@/components/PropsTable';
|
||||
import { Snippet } from '@/components/Snippet';
|
||||
import { numberFieldPropDefs } from './props-definition';
|
||||
import {
|
||||
numberFieldUsageSnippet,
|
||||
withLabelSnippet,
|
||||
sizesSnippet,
|
||||
withDescriptionSnippet,
|
||||
} from './snippets';
|
||||
import { WithLabel, Sizes, WithDescription } from './components';
|
||||
import { PageTitle } from '@/components/PageTitle';
|
||||
import { Theming } from '@/components/Theming';
|
||||
import { NumberFieldDefinition } from '../../../utils/definitions';
|
||||
import { ChangelogComponent } from '@/components/ChangelogComponent';
|
||||
import { CodeBlock } from '@/components/CodeBlock';
|
||||
import { ReactAriaLink } from '@/components/ReactAriaLink';
|
||||
|
||||
export const reactAriaUrls = {
|
||||
numberField: 'https://react-aria.adobe.com/NumberField',
|
||||
};
|
||||
|
||||
<PageTitle
|
||||
title="NumberField"
|
||||
description="A numeric input with label, description, icon, and validation support."
|
||||
/>
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
preview={<WithLabel />}
|
||||
code={withLabelSnippet}
|
||||
/>
|
||||
|
||||
## Usage
|
||||
|
||||
<CodeBlock code={numberFieldUsageSnippet} />
|
||||
|
||||
## API reference
|
||||
|
||||
<PropsTable data={numberFieldPropDefs} />
|
||||
|
||||
<ReactAriaLink component="NumberField" href={reactAriaUrls.numberField} />
|
||||
|
||||
## Examples
|
||||
|
||||
### Sizes
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<Sizes />}
|
||||
code={sizesSnippet}
|
||||
layout="side-by-side"
|
||||
/>
|
||||
|
||||
### With description
|
||||
|
||||
<Snippet
|
||||
align="center"
|
||||
py={4}
|
||||
open
|
||||
preview={<WithDescription />}
|
||||
code={withDescriptionSnippet}
|
||||
layout="side-by-side"
|
||||
/>
|
||||
|
||||
<Theming definition={NumberFieldDefinition} />
|
||||
|
||||
<ChangelogComponent component="number-field" />
|
||||
@@ -0,0 +1,100 @@
|
||||
import {
|
||||
classNamePropDefs,
|
||||
stylePropDefs,
|
||||
type PropDef,
|
||||
} from '@/utils/propDefs';
|
||||
import { Chip } from '@/components/Chip';
|
||||
|
||||
export const numberFieldPropDefs: Record<string, PropDef> = {
|
||||
size: {
|
||||
type: 'enum',
|
||||
values: ['small', 'medium'],
|
||||
default: 'small',
|
||||
responsive: true,
|
||||
description: (
|
||||
<>
|
||||
Visual size of the input. Use <Chip>small</Chip> for dense layouts,{' '}
|
||||
<Chip>medium</Chip> for prominent fields.
|
||||
</>
|
||||
),
|
||||
},
|
||||
label: {
|
||||
type: 'string',
|
||||
description: 'Visible label displayed above the input.',
|
||||
},
|
||||
secondaryLabel: {
|
||||
type: 'string',
|
||||
description: (
|
||||
<>
|
||||
Secondary text shown next to the label. If not provided and isRequired
|
||||
is true, displays <Chip>Required</Chip>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Help text displayed below the label.',
|
||||
},
|
||||
icon: {
|
||||
type: 'enum',
|
||||
values: ['ReactNode'],
|
||||
description: 'Icon rendered before the input.',
|
||||
},
|
||||
placeholder: {
|
||||
type: 'string',
|
||||
description: 'Text displayed when the input is empty.',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'Form field name for submission.',
|
||||
},
|
||||
minValue: {
|
||||
type: 'number',
|
||||
description: 'Minimum allowed value.',
|
||||
},
|
||||
maxValue: {
|
||||
type: 'number',
|
||||
description: 'Maximum allowed value.',
|
||||
},
|
||||
step: {
|
||||
type: 'number',
|
||||
description: 'Step increment for arrow key changes.',
|
||||
},
|
||||
formatOptions: {
|
||||
type: 'enum',
|
||||
values: ['Intl.NumberFormatOptions'],
|
||||
description: (
|
||||
<>
|
||||
Number formatting options. Defaults to{' '}
|
||||
<Chip>{'useGrouping: false'}</Chip>.
|
||||
</>
|
||||
),
|
||||
},
|
||||
isRequired: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the field is required for form submission.',
|
||||
},
|
||||
isDisabled: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the input is disabled.',
|
||||
},
|
||||
isReadOnly: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the input is read-only.',
|
||||
},
|
||||
value: {
|
||||
type: 'number',
|
||||
description: 'Controlled value of the input.',
|
||||
},
|
||||
defaultValue: {
|
||||
type: 'number',
|
||||
description: 'Default value for uncontrolled usage.',
|
||||
},
|
||||
onChange: {
|
||||
type: 'enum',
|
||||
values: ['(value: number) => void'],
|
||||
description: 'Handler called when the input value changes.',
|
||||
},
|
||||
...classNamePropDefs,
|
||||
...stylePropDefs,
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
export const numberFieldUsageSnippet = `import { NumberField } from '@backstage/ui';
|
||||
|
||||
<NumberField label="Minutes" minValue={0} maxValue={59} step={1} />`;
|
||||
|
||||
export const withLabelSnippet = `<NumberField
|
||||
name="quantity"
|
||||
placeholder="Enter a number"
|
||||
label="Label"
|
||||
/>`;
|
||||
|
||||
export const sizesSnippet = `<Flex direction="column" gap="4">
|
||||
<NumberField
|
||||
size="small"
|
||||
name="quantity"
|
||||
placeholder="Enter a number"
|
||||
icon={<RiTimeLine />}
|
||||
/>
|
||||
<NumberField
|
||||
size="medium"
|
||||
name="quantity"
|
||||
placeholder="Enter a number"
|
||||
icon={<RiTimeLine />}
|
||||
/>
|
||||
</Flex>`;
|
||||
|
||||
export const withDescriptionSnippet = `<NumberField
|
||||
name="quantity"
|
||||
placeholder="Enter a number"
|
||||
label="Label"
|
||||
description="Description"
|
||||
/>`;
|
||||
@@ -98,6 +98,10 @@ export const components: Page[] = [
|
||||
title: 'Menu',
|
||||
slug: 'menu',
|
||||
},
|
||||
{
|
||||
title: 'NumberField',
|
||||
slug: 'number-field',
|
||||
},
|
||||
{
|
||||
title: 'PasswordField',
|
||||
slug: 'password-field',
|
||||
|
||||
@@ -153,3 +153,90 @@ Below is an example of a more elaborate setup where we have three different back
|
||||
In this example we have split out the Catalog and Search plugins into one backend deployment. The proxy routes all traffic for `/api/catalog/` and `/api/search/` to this instance. With this separation we're able to scale and deploy these two plugins independently, and they are also isolated from both a performance and security perspective. Likewise the TechDocs and Scaffolder plugins are split out as well, and then we route the rest of the traffic to our instance that contains the App, Auth, and Proxy plugins.
|
||||
|
||||
We also see how each of the plugins have their own logical database, but are often set up to share the actual Database Management System (DBMS) instance. This is of course not a requirement, and you can choose to further divide or consolidate the databases as you see fit.
|
||||
|
||||
## Startup Configuration
|
||||
|
||||
The `backend.startup` configuration block lets you control how the backend behaves when plugins or plugin modules fail to boot during startup. By default, any plugin or module boot failure is fatal and causes the backend to abort. This configuration lets you make specific plugins or modules optional, or flip the default so that all are optional unless explicitly required.
|
||||
|
||||
### Plugin Boot Failure Handling
|
||||
|
||||
If a plugin fails to boot, the backend aborts startup by default. You can change this per-plugin using `onPluginBootFailure: continue`:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
startup:
|
||||
plugins:
|
||||
catalog:
|
||||
onPluginBootFailure: continue
|
||||
```
|
||||
|
||||
With this configuration, if the `catalog` plugin crashes during startup, the backend will log the error and continue starting up the remaining plugins. This is useful when troubleshooting data-dependent issues, as it allows you to leave a crashing plugin installed while still allowing the rest of the backend to serve requests.
|
||||
|
||||
### Plugin Module Boot Failure Handling
|
||||
|
||||
You can apply the same control to individual plugin modules using `onPluginModuleBootFailure`:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
startup:
|
||||
plugins:
|
||||
catalog:
|
||||
modules:
|
||||
github: # moduleId as declared in createBackendModule({ moduleId: '...' })
|
||||
onPluginModuleBootFailure: continue
|
||||
```
|
||||
|
||||
This allows the `github` catalog module to fail without bringing down the `catalog` plugin or the rest of the backend. Note that the key under `modules` is the `moduleId` declared in `createBackendModule`, not the plugin or entity provider name.
|
||||
|
||||
### Setting a Global Default
|
||||
|
||||
Instead of opting each plugin into `continue` mode individually, you can flip the global default so that all plugins continue on failure, and only specific ones are required to succeed:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
startup:
|
||||
default:
|
||||
onPluginBootFailure: continue
|
||||
plugins:
|
||||
auth:
|
||||
onPluginBootFailure: abort
|
||||
```
|
||||
|
||||
In this example, all plugins are optional except `auth`, which is explicitly set to `abort` and is therefore required to start successfully.
|
||||
|
||||
The same `default` mechanism works for modules via `onPluginModuleBootFailure`:
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
startup:
|
||||
default:
|
||||
onPluginModuleBootFailure: continue
|
||||
plugins:
|
||||
catalog:
|
||||
modules:
|
||||
github:
|
||||
onPluginModuleBootFailure: abort
|
||||
```
|
||||
|
||||
### Full Configuration Reference
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
startup:
|
||||
# Global defaults applied when not specified per-plugin or per-module
|
||||
default:
|
||||
# Defaults to 'abort'. Set to 'continue' to make all plugins optional by default.
|
||||
onPluginBootFailure: abort # or continue
|
||||
# Defaults to 'abort'. Set to 'continue' to make all plugin modules optional by default.
|
||||
onPluginModuleBootFailure: abort # or continue
|
||||
|
||||
# Per-plugin and per-module overrides
|
||||
plugins:
|
||||
<pluginId>:
|
||||
# Override the default boot failure behavior for this specific plugin.
|
||||
onPluginBootFailure: abort # or continue
|
||||
modules:
|
||||
<moduleId>:
|
||||
# Override the default boot failure behavior for this specific plugin module.
|
||||
onPluginModuleBootFailure: abort # or continue
|
||||
```
|
||||
|
||||
@@ -28,7 +28,7 @@ function useTodos() {
|
||||
|
||||
const data = await response.json();
|
||||
return data.items;
|
||||
}, [fetch]);
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
@@ -37,8 +37,8 @@ Here, we're using Backstage's `fetchApi` which wraps the browser `fetch` and aut
|
||||
1. Injects authentication credentials - you don't need to attach any `Authorization` headers manually.
|
||||
2. Resolves `plugin://<pluginId>` URL schemes to the real plugin URL for your instance.
|
||||
|
||||
The `useAsync` hook from `react-use` runs the async function on mount and
|
||||
returns `{ value, loading, error }`, which the component uses to show a
|
||||
The `useAsync` hook from `@react-hookz/web` runs the async function on mount and
|
||||
returns `[{ status, result, error }, { execute }]`, which the component uses to show a
|
||||
loading spinner, example todo items if the backend request fails, or the
|
||||
fetched todo list.
|
||||
|
||||
|
||||
@@ -26,13 +26,15 @@ such as `axios`.
|
||||
Example:
|
||||
|
||||
```ts title="plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx"
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import { useAsync, useMountEffect } from '@react-hookz/web';
|
||||
|
||||
function AwesomeUsersTable() {
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
const [{ status, result, error }, { execute }] = useAsync(async () => {
|
||||
const response = await fetch('https://api.frobsco.com/v1/list');
|
||||
return response.json();
|
||||
}, []);
|
||||
});
|
||||
|
||||
useMountEffect(execute);
|
||||
|
||||
|
||||
...
|
||||
@@ -94,17 +96,19 @@ import {
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import { useAsync, useMountEffect } from '@react-hookz/web';
|
||||
|
||||
function FrobsAggregator() {
|
||||
const fetchApi = useApi(fetchApiRef);
|
||||
const discoveryApi = useApi(discoveryApiRef);
|
||||
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
const [{ status, result, error }, { execute }] = useAsync(async () => {
|
||||
const baseUrl = await discoveryApi.getBaseUrl('proxy');
|
||||
const response = await fetchApi.fetch(`${baseUrl}/frobs`);
|
||||
return response.json();
|
||||
}, [fetchApi, discoveryApi]);
|
||||
});
|
||||
|
||||
useMountEffect(execute);
|
||||
|
||||
// ...
|
||||
}
|
||||
@@ -171,19 +175,21 @@ import {
|
||||
discoveryApiRef,
|
||||
fetchApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import { useAsync, useMountEffect } from '@react-hookz/web';
|
||||
|
||||
function FrobsAggregator() {
|
||||
const fetchApi = useApi(fetchApiRef);
|
||||
const discoveryApi = useApi(discoveryApiRef);
|
||||
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
const [{ status, result, error }, { execute }] = useAsync(async () => {
|
||||
// highlight-next-line
|
||||
const baseUrl = await discoveryApi.getBaseUrl('frobs-aggregator');
|
||||
// highlight-next-line
|
||||
const response = await fetchApi.fetch(`${baseUrl}/summary`);
|
||||
return response.json();
|
||||
}, [fetchApi, discoveryApi]);
|
||||
});
|
||||
|
||||
useMountEffect(execute);
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,11 +21,12 @@ If your plugin requires access to an API, backstage offers
|
||||
|
||||
- [Setting up the backstage proxy](#setting-up-the-backstage-proxy)
|
||||
- [Calling an API using the backstage proxy](#calling-an-api-using-the-backstage-proxy)
|
||||
- [Option 1: Calling the proxy directly from the frontend plugin](#option-1-calling-the-proxy-directly-from-the-frontend-plugin)
|
||||
- [Option 2: Defining the API client interface](#defining-the-api-client-interface)
|
||||
- [Creating the API client](#creating-the-api-client)
|
||||
- [Bundling your ApiRef with your plugin](#bundling-your-apiref-with-your-plugin)
|
||||
- [Using the API in your components](#using-the-api-in-your-components)
|
||||
- [Option 1: Calling the proxy directly from the frontend plugin](#option-1-calling-the-proxy-directly-from-the-frontend-plugin)
|
||||
- [Option 2: Defining the API client interface](#option-2-defining-the-api-client-interface)
|
||||
- [Defining the API client interface](#defining-the-api-client-interface)
|
||||
- [Creating the API client](#creating-the-api-client)
|
||||
- [Bundling your ApiRef with your plugin](#bundling-your-apiref-with-your-plugin)
|
||||
- [Using the API in your components](#using-the-api-in-your-components)
|
||||
|
||||
## Setting up the backstage proxy
|
||||
|
||||
@@ -71,20 +72,22 @@ import {
|
||||
fetchApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { Progress, Alert } from '@backstage/core-components';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import { useAsync, useMountEffect } from '@react-hookz/web';
|
||||
import { myAwesomeApiRef } from '../../api';
|
||||
|
||||
export const AwesomeUsersTable = () => {
|
||||
const fetchApi = useApi(fetchApiRef);
|
||||
const discoveryApi = useApi(discoveryApiRef);
|
||||
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
const [{ status, result, error }, { execute }] = useAsync(async () => {
|
||||
const baseUrl = await discoveryApi.getBaseUrl('proxy');
|
||||
// As configured previously for the backend proxy
|
||||
const resp = await fetchApi.fetch(`${baseUrl}/<your-proxy-uri>`);
|
||||
if (!resp.ok) throw new Error(resp.statusText);
|
||||
return resp.json();
|
||||
}, [fetchApi, discoveryApi]);
|
||||
});
|
||||
|
||||
useMountEffect(execute);
|
||||
|
||||
// ...
|
||||
};
|
||||
@@ -238,16 +241,18 @@ Now you should be able to access your API using the backstage hook
|
||||
```ts title="plugins/my-awesome-plugin/src/components/AwesomeUsersTable.tsx"
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
import { myAwesomeApiRef } from '../../api';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import { useAsync, useMountEffect } from '@react-hookz/web';
|
||||
|
||||
export const AwesomeUsersTable = () => {
|
||||
const apiClient = useApi(myAwesomeApiRef);
|
||||
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
const [{ status, result, error }, { execute }] = useAsync(async () => {
|
||||
const users = await apiClient.listUsers();
|
||||
return users;
|
||||
}, [apiClient]);
|
||||
|
||||
useMountEffect(execute);
|
||||
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "root",
|
||||
"version": "1.51.0",
|
||||
"version": "1.52.0-next.0",
|
||||
"backstage": {
|
||||
"cli": {
|
||||
"new": {
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @backstage/app-defaults
|
||||
|
||||
## 1.7.9-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.18.11-next.0
|
||||
|
||||
## 1.7.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/app-defaults",
|
||||
"version": "1.7.8",
|
||||
"version": "1.7.9-next.0",
|
||||
"description": "Provides the default wiring of a Backstage App",
|
||||
"backstage": {
|
||||
"role": "web-library"
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# app-example-plugin
|
||||
|
||||
## 0.0.36-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/core-components@0.18.11-next.0
|
||||
|
||||
## 0.0.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "app-example-plugin",
|
||||
"version": "0.0.35",
|
||||
"version": "0.0.36-next.0",
|
||||
"description": "Backstage internal example plugin",
|
||||
"backstage": {
|
||||
"role": "frontend-plugin",
|
||||
|
||||
@@ -1,5 +1,39 @@
|
||||
# example-app-legacy
|
||||
|
||||
## 0.2.122-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-react@3.0.1-next.0
|
||||
- @backstage/plugin-catalog@2.0.6-next.0
|
||||
- @backstage/core-components@0.18.11-next.0
|
||||
- @backstage/plugin-catalog-graph@0.6.5-next.0
|
||||
- @backstage/plugin-org@0.7.5-next.0
|
||||
- @backstage/plugin-scaffolder-react@2.0.1-next.0
|
||||
- @backstage/plugin-scaffolder@1.38.0-next.0
|
||||
- @backstage/plugin-search-react@1.11.5-next.0
|
||||
- @backstage/plugin-search@1.7.5-next.0
|
||||
- @backstage/plugin-catalog-unprocessed-entities@0.2.32-next.0
|
||||
- @backstage/plugin-home@0.9.7-next.0
|
||||
- @backstage/cli@0.36.3-next.0
|
||||
- @backstage/plugin-catalog-import@0.13.14-next.0
|
||||
- @backstage/plugin-techdocs@1.17.7-next.0
|
||||
- @backstage/plugin-api-docs@0.14.2-next.0
|
||||
- @backstage/plugin-kubernetes@0.12.20-next.0
|
||||
- @backstage/plugin-kubernetes-cluster@0.0.38-next.0
|
||||
- @backstage/plugin-techdocs-module-addons-contrib@1.1.37-next.0
|
||||
- @backstage/plugin-user-settings@0.9.4-next.0
|
||||
- @backstage/app-defaults@1.7.9-next.0
|
||||
- @backstage/integration-react@1.2.19-next.0
|
||||
- @backstage/plugin-auth-react@0.1.28-next.0
|
||||
- @backstage/plugin-devtools@0.1.40-next.0
|
||||
- @backstage/plugin-home-react@0.1.39-next.0
|
||||
- @backstage/plugin-notifications@0.5.18-next.0
|
||||
- @backstage/plugin-signals@0.0.32-next.0
|
||||
- @backstage/plugin-techdocs-react@1.3.12-next.0
|
||||
- @backstage/frontend-app-api@0.16.4-next.0
|
||||
|
||||
## 0.2.121
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-app-legacy",
|
||||
"version": "0.2.121",
|
||||
"version": "0.2.122-next.0",
|
||||
"backstage": {
|
||||
"role": "frontend"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
# example-app
|
||||
|
||||
## 0.0.36-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-react@3.0.1-next.0
|
||||
- @backstage/plugin-catalog@2.0.6-next.0
|
||||
- @backstage/core-components@0.18.11-next.0
|
||||
- @backstage/plugin-catalog-graph@0.6.5-next.0
|
||||
- @backstage/plugin-org@0.7.5-next.0
|
||||
- @backstage/plugin-scaffolder-react@2.0.1-next.0
|
||||
- @backstage/plugin-scaffolder@1.38.0-next.0
|
||||
- @backstage/plugin-search-react@1.11.5-next.0
|
||||
- @backstage/plugin-search@1.7.5-next.0
|
||||
- @backstage/plugin-catalog-unprocessed-entities@0.2.32-next.0
|
||||
- @backstage/plugin-home@0.9.7-next.0
|
||||
- @backstage/cli@0.36.3-next.0
|
||||
- @backstage/plugin-catalog-import@0.13.14-next.0
|
||||
- @backstage/plugin-techdocs@1.17.7-next.0
|
||||
- @backstage/core-compat-api@0.5.12-next.0
|
||||
- @backstage/plugin-api-docs@0.14.2-next.0
|
||||
- @backstage/plugin-kubernetes@0.12.20-next.0
|
||||
- @backstage/plugin-kubernetes-cluster@0.0.38-next.0
|
||||
- @backstage/plugin-techdocs-module-addons-contrib@1.1.37-next.0
|
||||
- @backstage/plugin-user-settings@0.9.4-next.0
|
||||
- @backstage/app-defaults@1.7.9-next.0
|
||||
- @backstage/frontend-defaults@0.5.3-next.0
|
||||
- @backstage/integration-react@1.2.19-next.0
|
||||
- @backstage/plugin-app@0.4.7-next.0
|
||||
- @backstage/plugin-app-visualizer@0.2.5-next.0
|
||||
- @backstage/plugin-auth-react@0.1.28-next.0
|
||||
- @backstage/plugin-devtools@0.1.40-next.0
|
||||
- @backstage/plugin-home-react@0.1.39-next.0
|
||||
- @backstage/plugin-notifications@0.5.18-next.0
|
||||
- @backstage/plugin-signals@0.0.32-next.0
|
||||
- @backstage/plugin-techdocs-react@1.3.12-next.0
|
||||
- @backstage/frontend-app-api@0.16.4-next.0
|
||||
|
||||
## 0.0.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-app",
|
||||
"version": "0.0.35",
|
||||
"version": "0.0.36-next.0",
|
||||
"backstage": {
|
||||
"role": "frontend"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @backstage/backend-app-api
|
||||
|
||||
## 1.7.1-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-plugin-api@1.9.2-next.0
|
||||
|
||||
## 1.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-app-api",
|
||||
"version": "1.7.0",
|
||||
"version": "1.7.1-next.0",
|
||||
"description": "Core API used by Backstage backend apps",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @backstage/backend-defaults
|
||||
|
||||
## 0.17.2-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a07e6a3: Updated `AzureBlobStorageUrlReader` to reference the correctly-named `AzureBlobStorageIntegration` type from `@backstage/integration`. The previously-used `AzureBlobStorageIntergation` is now an alias for the new type and remains a valid argument to the constructor.
|
||||
- def82d4: Fixed the built-in rate limiter throwing a validation error and refusing to start when `backend.rateLimit` is enabled. Requests are now keyed using the address normalization helper from `express-rate-limit`, which is required by newer versions of that library and ensures IPv6 clients are grouped by their address block rather than by individual address.
|
||||
- Updated dependencies
|
||||
- @backstage/integration@2.0.3-next.0
|
||||
- @backstage/plugin-auth-node@0.7.2-next.0
|
||||
- @backstage/backend-app-api@1.7.1-next.0
|
||||
- @backstage/plugin-permission-node@0.11.1-next.0
|
||||
- @backstage/backend-plugin-api@1.9.2-next.0
|
||||
- @backstage/plugin-events-node@0.4.23-next.0
|
||||
|
||||
## 0.17.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-defaults",
|
||||
"version": "0.17.1",
|
||||
"version": "0.17.2-next.0",
|
||||
"description": "Backend defaults used by Backstage backend apps",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -12,6 +12,7 @@ import { AzureIntegration } from '@backstage/integration';
|
||||
import { BitbucketCloudIntegration } from '@backstage/integration';
|
||||
import { BitbucketServerIntegration } from '@backstage/integration';
|
||||
import { Config } from '@backstage/config';
|
||||
import { ContainerClient } from '@azure/storage-blob';
|
||||
import { GerritIntegration } from '@backstage/integration';
|
||||
import { GiteaIntegration } from '@backstage/integration';
|
||||
import { GithubCredentialsProvider } from '@backstage/integration';
|
||||
@@ -70,6 +71,9 @@ export class AzureBlobStorageUrlReader implements UrlReaderService {
|
||||
integration: AzureBlobStorageIntegration,
|
||||
deps: {
|
||||
treeResponseFactory: ReadTreeResponseFactory;
|
||||
createContainerClient?: (
|
||||
containerName: string,
|
||||
) => Promise<ContainerClient>;
|
||||
},
|
||||
);
|
||||
// (undocumented)
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
TestDatabases,
|
||||
mockServices,
|
||||
} from '@backstage/backend-test-utils';
|
||||
import waitForExpect from 'wait-for-expect';
|
||||
import { DatabaseKeyStore, TABLE } from './DatabaseKeyStore';
|
||||
|
||||
const testKey = {
|
||||
@@ -104,12 +105,13 @@ describe('DatabaseKeyStore', () => {
|
||||
"Removing expired plugin service keys, 'test-key-2'",
|
||||
);
|
||||
|
||||
// Key deletion happens async, so give it a bit of time to complete
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
await expect(knex(TABLE).select('id')).resolves.toEqual([
|
||||
{ id: testKey.kid },
|
||||
]);
|
||||
// Key deletion happens async — poll until it completes rather than
|
||||
// relying on a fixed sleep that can flake in slow CI environments.
|
||||
await waitForExpect(async () => {
|
||||
await expect(knex(TABLE).select('id')).resolves.toEqual([
|
||||
{ id: testKey.kid },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to insert with invalid date', async () => {
|
||||
|
||||
+62
-106
@@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as AzureStorage from '@azure/storage-blob';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { DefaultReadTreeResponseFactory } from './tree';
|
||||
@@ -27,36 +26,26 @@ import {
|
||||
ScmIntegrations,
|
||||
} from '@backstage/integration';
|
||||
import { UrlReaderPredicateTuple } from './types';
|
||||
import { mockServices } from '@backstage/backend-test-utils';
|
||||
import { Readable } from 'node:stream';
|
||||
import { ContainerClient } from '@azure/storage-blob';
|
||||
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
// Mock Azure Blob Storage SDK
|
||||
const mockBlobDownload = jest.fn();
|
||||
const mockGetBlobClient = jest.fn(() => ({
|
||||
download: mockBlobDownload,
|
||||
}));
|
||||
const mockListBlobsFlat = jest.fn();
|
||||
|
||||
class MockContainerClient {
|
||||
getBlobClient = mockGetBlobClient;
|
||||
listBlobsFlat = mockListBlobsFlat;
|
||||
function createMockContainerClient(): ContainerClient {
|
||||
return {
|
||||
getBlobClient: mockGetBlobClient,
|
||||
listBlobsFlat: mockListBlobsFlat,
|
||||
} as unknown as ContainerClient;
|
||||
}
|
||||
|
||||
class MockBlobServiceClient {
|
||||
getContainerClient = jest.fn(() => new MockContainerClient());
|
||||
}
|
||||
|
||||
jest
|
||||
.spyOn(AzureStorage, 'BlobServiceClient')
|
||||
.mockReturnValue(new MockBlobServiceClient() as any);
|
||||
jest
|
||||
.spyOn(AzureStorage, 'StorageSharedKeyCredential')
|
||||
.mockReturnValue({} as any);
|
||||
|
||||
const treeResponseFactory = DefaultReadTreeResponseFactory.create({
|
||||
config: new ConfigReader({}),
|
||||
});
|
||||
|
||||
describe('parseUrl', () => {
|
||||
it('parses Azure Blob Storage URLs correctly', () => {
|
||||
expect(
|
||||
@@ -96,35 +85,40 @@ describe('parseUrl', () => {
|
||||
|
||||
describe('AzureBlobStorageUrlReader', () => {
|
||||
const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => {
|
||||
return AzureBlobStorageUrlReader.factory({
|
||||
config: new ConfigReader(config),
|
||||
logger: mockServices.logger.mock(),
|
||||
treeResponseFactory,
|
||||
const integrations = ScmIntegrations.fromConfig(new ConfigReader(config));
|
||||
const credsManager =
|
||||
DefaultAzureCredentialsManager.fromIntegrations(integrations);
|
||||
|
||||
return integrations.azureBlobStorage.list().map(integrationConfig => {
|
||||
const reader = new AzureBlobStorageUrlReader(
|
||||
credsManager,
|
||||
integrationConfig,
|
||||
{
|
||||
treeResponseFactory,
|
||||
createContainerClient: async () => createMockContainerClient(),
|
||||
},
|
||||
);
|
||||
const predicate = (url: URL) =>
|
||||
url.host.endsWith(
|
||||
`${integrationConfig.config.accountName}.${integrationConfig.config.host}`,
|
||||
);
|
||||
return { reader, predicate };
|
||||
});
|
||||
};
|
||||
|
||||
it('creates a reader with minimal config', () => {
|
||||
const entries = createReader({
|
||||
integrations: {
|
||||
azureBlobStorage: [
|
||||
{
|
||||
accountName: 'test-account',
|
||||
},
|
||||
],
|
||||
azureBlobStorage: [{ accountName: 'test-account' }],
|
||||
},
|
||||
});
|
||||
|
||||
expect(entries).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('predicates', () => {
|
||||
const readers = createReader({
|
||||
integrations: {
|
||||
azureBlobStorage: [
|
||||
{
|
||||
accountName: 'test-account',
|
||||
},
|
||||
],
|
||||
azureBlobStorage: [{ accountName: 'test-account' }],
|
||||
},
|
||||
});
|
||||
const predicate = readers[0].predicate;
|
||||
@@ -147,14 +141,10 @@ describe('AzureBlobStorageUrlReader', () => {
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
azureBlobStorage: [
|
||||
{
|
||||
accountName: 'test-account',
|
||||
accountKey: 'test-key',
|
||||
},
|
||||
{ accountName: 'test-account', accountKey: 'test-key' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const credsManager =
|
||||
DefaultAzureCredentialsManager.fromIntegrations(integrations);
|
||||
@@ -163,7 +153,6 @@ describe('AzureBlobStorageUrlReader', () => {
|
||||
integrations.azureBlobStorage.list()[0],
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
|
||||
expect(reader.toString()).toBe(
|
||||
'azureBlobStorage{accountName=test-account,authed=true}',
|
||||
);
|
||||
@@ -172,14 +161,9 @@ describe('AzureBlobStorageUrlReader', () => {
|
||||
it('shows authed=false when no account key provided', () => {
|
||||
const config = new ConfigReader({
|
||||
integrations: {
|
||||
azureBlobStorage: [
|
||||
{
|
||||
accountName: 'test-account',
|
||||
},
|
||||
],
|
||||
azureBlobStorage: [{ accountName: 'test-account' }],
|
||||
},
|
||||
});
|
||||
|
||||
const integrations = ScmIntegrations.fromConfig(config);
|
||||
const credsManager =
|
||||
DefaultAzureCredentialsManager.fromIntegrations(integrations);
|
||||
@@ -188,7 +172,6 @@ describe('AzureBlobStorageUrlReader', () => {
|
||||
integrations.azureBlobStorage.list()[0],
|
||||
{ treeResponseFactory },
|
||||
);
|
||||
|
||||
expect(reader.toString()).toBe(
|
||||
'azureBlobStorage{accountName=test-account,authed=false}',
|
||||
);
|
||||
@@ -199,10 +182,7 @@ describe('AzureBlobStorageUrlReader', () => {
|
||||
const [{ reader }] = createReader({
|
||||
integrations: {
|
||||
azureBlobStorage: [
|
||||
{
|
||||
accountName: 'test-account',
|
||||
accountKey: 'test-key',
|
||||
},
|
||||
{ accountName: 'test-account', accountKey: 'test-key' },
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -245,55 +225,38 @@ describe('AzureBlobStorageUrlReader', () => {
|
||||
const [{ reader }] = createReader({
|
||||
integrations: {
|
||||
azureBlobStorage: [
|
||||
{
|
||||
accountName: 'test-account',
|
||||
accountKey: 'test-key',
|
||||
},
|
||||
{ accountName: 'test-account', accountKey: 'test-key' },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
const mockBlobs = [
|
||||
{
|
||||
name: 'prefix/file1.yaml',
|
||||
properties: {
|
||||
lastModified: new Date('2025-01-01T00:00:00Z'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'prefix/subdir/file2.yaml',
|
||||
properties: {
|
||||
lastModified: new Date('2024-01-01T00:00:00Z'),
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
mockBlobDownload.mockResolvedValue({
|
||||
readableStreamBody: Readable.from(
|
||||
Buffer.from('site_name: Test Azure Blob'),
|
||||
),
|
||||
});
|
||||
|
||||
mockListBlobsFlat.mockReturnValue({
|
||||
[Symbol.asyncIterator]: async function* generateBlobs() {
|
||||
for (const blob of mockBlobs) {
|
||||
yield blob;
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('returns contents of blobs in a container', async () => {
|
||||
mockListBlobsFlat.mockReturnValue(
|
||||
(async function* mockIterator() {
|
||||
yield {
|
||||
name: 'prefix/file1.yaml',
|
||||
properties: { lastModified: new Date('2025-01-01T00:00:00Z') },
|
||||
};
|
||||
yield {
|
||||
name: 'prefix/file2.yaml',
|
||||
properties: { lastModified: new Date('2025-01-02T00:00:00Z') },
|
||||
};
|
||||
})(),
|
||||
);
|
||||
|
||||
mockBlobDownload.mockResolvedValue({
|
||||
readableStreamBody: Readable.from(Buffer.from('test content')),
|
||||
});
|
||||
|
||||
const response = await reader.readTree(
|
||||
'https://test-account.blob.core.windows.net/test-container/prefix',
|
||||
'https://test-account.blob.core.windows.net/test-container/prefix/',
|
||||
);
|
||||
const files = await response.files();
|
||||
|
||||
expect(files).toHaveLength(2);
|
||||
const file1Content = await files[0].content();
|
||||
expect(file1Content.toString()).toBe('site_name: Test Azure Blob');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -301,10 +264,7 @@ describe('AzureBlobStorageUrlReader', () => {
|
||||
const [{ reader }] = createReader({
|
||||
integrations: {
|
||||
azureBlobStorage: [
|
||||
{
|
||||
accountName: 'test-account',
|
||||
accountKey: 'test-key',
|
||||
},
|
||||
{ accountName: 'test-account', accountKey: 'test-key' },
|
||||
],
|
||||
},
|
||||
});
|
||||
@@ -313,7 +273,7 @@ describe('AzureBlobStorageUrlReader', () => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return a file when given an exact valid url', async () => {
|
||||
it('returns a file when given an exact valid url', async () => {
|
||||
mockBlobDownload.mockResolvedValue({
|
||||
readableStreamBody: Readable.from(
|
||||
Buffer.from('site_name: Test Azure Blob'),
|
||||
@@ -322,26 +282,22 @@ describe('AzureBlobStorageUrlReader', () => {
|
||||
lastModified: new Date('2025-01-01T00:00:00Z'),
|
||||
});
|
||||
|
||||
const data = await reader.search(
|
||||
'https://test-account.blob.core.windows.net/test-container/test-file.yaml',
|
||||
const { files } = await reader.search(
|
||||
'https://test-account.blob.core.windows.net/test-container/exact-file.yaml',
|
||||
);
|
||||
|
||||
expect(data.etag).toBe('"etag"');
|
||||
expect(data.files.length).toBe(1);
|
||||
expect(data.files[0].url).toBe(
|
||||
'https://test-account.blob.core.windows.net/test-container/test-file.yaml',
|
||||
);
|
||||
expect((await data.files[0].content()).toString()).toEqual(
|
||||
'site_name: Test Azure Blob',
|
||||
expect(files).toHaveLength(1);
|
||||
expect(files[0].url).toBe(
|
||||
'https://test-account.blob.core.windows.net/test-container/exact-file.yaml',
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle Azure SDK errors from readUrl', async () => {
|
||||
it('handles Azure SDK errors from readUrl', async () => {
|
||||
mockBlobDownload.mockRejectedValue(new Error('Blob not found'));
|
||||
|
||||
await expect(
|
||||
reader.search(
|
||||
'https://test-account.blob.core.windows.net/test-container/missing.yaml',
|
||||
'https://test-account.blob.core.windows.net/test-container/nonexistent.yaml',
|
||||
),
|
||||
).rejects.toThrow('Could not retrieve file from Azure Blob Storage');
|
||||
});
|
||||
@@ -349,7 +305,7 @@ describe('AzureBlobStorageUrlReader', () => {
|
||||
it('throws if given URL with wildcard', async () => {
|
||||
await expect(
|
||||
reader.search(
|
||||
'https://test-account.blob.core.windows.net/test-container/test-*.yaml',
|
||||
'https://test-account.blob.core.windows.net/test-container/path/to/*.yaml',
|
||||
),
|
||||
).rejects.toThrow(
|
||||
'Glob search pattern not implemented for AzureBlobStorageUrlReader',
|
||||
|
||||
+18
-14
@@ -85,12 +85,10 @@ export class AzureBlobStorageUrlReader implements UrlReaderService {
|
||||
});
|
||||
};
|
||||
|
||||
// private readonly blobServiceClient: BlobServiceClient;
|
||||
|
||||
private readonly credsManager: AzureCredentialsManager;
|
||||
private readonly integration: AzureBlobStorageIntegration;
|
||||
private readonly deps: {
|
||||
treeResponseFactory: ReadTreeResponseFactory;
|
||||
createContainerClient: (containerName: string) => Promise<ContainerClient>;
|
||||
};
|
||||
|
||||
constructor(
|
||||
@@ -98,18 +96,26 @@ export class AzureBlobStorageUrlReader implements UrlReaderService {
|
||||
integration: AzureBlobStorageIntegration,
|
||||
deps: {
|
||||
treeResponseFactory: ReadTreeResponseFactory;
|
||||
createContainerClient?: (
|
||||
containerName: string,
|
||||
) => Promise<ContainerClient>;
|
||||
},
|
||||
) {
|
||||
this.credsManager = credsManager;
|
||||
this.integration = integration;
|
||||
this.deps = deps;
|
||||
this.deps = {
|
||||
...deps,
|
||||
createContainerClient:
|
||||
deps.createContainerClient ??
|
||||
this.#defaultCreateContainerClient.bind(this, credsManager),
|
||||
};
|
||||
}
|
||||
|
||||
private async createContainerClient(
|
||||
async #defaultCreateContainerClient(
|
||||
credsManager: AzureCredentialsManager,
|
||||
containerName: string,
|
||||
): Promise<ContainerClient> {
|
||||
const accountName = this.integration.config.accountName; // Use the account name from the integration config
|
||||
const accountKey = this.integration.config.accountKey; // Get the account key if it exists
|
||||
const accountName = this.integration.config.accountName;
|
||||
const accountKey = this.integration.config.accountKey;
|
||||
|
||||
if (accountKey && accountName) {
|
||||
const creds = new StorageSharedKeyCredential(accountName, accountKey);
|
||||
@@ -119,10 +125,8 @@ export class AzureBlobStorageUrlReader implements UrlReaderService {
|
||||
);
|
||||
return blobServiceClient.getContainerClient(containerName);
|
||||
}
|
||||
// Use the credentials manager to get the correct credentials
|
||||
const credential = await this.credsManager.getCredentials(
|
||||
accountName as string,
|
||||
);
|
||||
|
||||
const credential = await credsManager.getCredentials(accountName as string);
|
||||
|
||||
let blobServiceClientUrl: string;
|
||||
|
||||
@@ -157,7 +161,7 @@ export class AzureBlobStorageUrlReader implements UrlReaderService {
|
||||
try {
|
||||
const { path, container } = parseUrl(url);
|
||||
|
||||
const containerClient = await this.createContainerClient(container);
|
||||
const containerClient = await this.deps.createContainerClient(container);
|
||||
const blobClient = containerClient.getBlobClient(path);
|
||||
|
||||
const getBlobOptions: BlobDownloadOptions = {
|
||||
@@ -200,7 +204,7 @@ export class AzureBlobStorageUrlReader implements UrlReaderService {
|
||||
try {
|
||||
const { path, container } = parseUrl(url);
|
||||
|
||||
const containerClient = await this.createContainerClient(container);
|
||||
const containerClient = await this.deps.createContainerClient(container);
|
||||
const blobs = containerClient.listBlobsFlat({ prefix: path });
|
||||
|
||||
const responses = [];
|
||||
|
||||
@@ -120,14 +120,14 @@ describe('AzureUrlReader', () => {
|
||||
it.each([
|
||||
{
|
||||
url: 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster',
|
||||
config: createConfig(),
|
||||
config: createConfig('my-pat'),
|
||||
response: expect.objectContaining({
|
||||
url: 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?api-version=6.0&path=my-template.yaml&version=master',
|
||||
}),
|
||||
},
|
||||
{
|
||||
url: 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml',
|
||||
config: createConfig(),
|
||||
config: createConfig('my-pat'),
|
||||
response: expect.objectContaining({
|
||||
url: 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?api-version=6.0&path=my-template.yaml',
|
||||
}),
|
||||
@@ -141,15 +141,6 @@ describe('AzureUrlReader', () => {
|
||||
}),
|
||||
}),
|
||||
},
|
||||
{
|
||||
url: 'https://dev.azure.com/a/b/_git/repo-name?path=my-template.yaml',
|
||||
config: createConfig(undefined),
|
||||
response: expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
authorization: expect.stringMatching(/^Bearer /),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
])('should handle happy path %#', async ({ url, config, response }) => {
|
||||
const [{ reader }] = AzureUrlReader.factory({
|
||||
config,
|
||||
@@ -166,12 +157,12 @@ describe('AzureUrlReader', () => {
|
||||
it.each([
|
||||
{
|
||||
url: 'https://api.com/a/b/blob/master/path/to/c.yaml',
|
||||
config: createConfig(),
|
||||
config: createConfig('my-pat'),
|
||||
error: 'Azure URL must point to a git repository',
|
||||
},
|
||||
{
|
||||
url: 'com/a/b/blob/master/path/to/c.yaml',
|
||||
config: createConfig(),
|
||||
config: createConfig('my-pat'),
|
||||
error: 'Invalid URL',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -211,6 +211,10 @@ export class GitlabUrlReader implements UrlReaderService {
|
||||
)}/repository/archive?${archiveReqParams.toString()}`;
|
||||
const archiveGitLabResponse = await this.integration.fetch(reqUrl, {
|
||||
...getGitLabRequestOptions(this.integration.config, token),
|
||||
// The mode is set to 'same-origin' to overwrite the default 'cors' value.
|
||||
// The repository/archive endpoint marks mode='cors' as a "hotlink" which will return 406 - Not Acceptable as a response
|
||||
// More info on this issue can be found @ https://github.com/backstage/backstage/issues/34395
|
||||
mode: 'same-origin',
|
||||
// TODO(freben): The signal cast is there because pre-3.x versions of
|
||||
// node-fetch have a very slightly deviating AbortSignal type signature.
|
||||
// The difference does not affect us in practice however. The cast can
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# @backstage/backend-dynamic-feature-service
|
||||
|
||||
## 0.8.3-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-backend@3.8.0-next.0
|
||||
- @backstage/backend-defaults@0.17.2-next.0
|
||||
- @backstage/plugin-auth-node@0.7.2-next.0
|
||||
- @backstage/plugin-scaffolder-node@0.13.4-next.0
|
||||
- @backstage/plugin-events-backend@0.6.3-next.0
|
||||
- @backstage/plugin-permission-node@0.11.1-next.0
|
||||
- @backstage/plugin-search-backend-node@1.4.5-next.0
|
||||
- @backstage/backend-plugin-api@1.9.2-next.0
|
||||
- @backstage/plugin-events-node@0.4.23-next.0
|
||||
- @backstage/backend-openapi-utils@0.6.10-next.0
|
||||
- @backstage/plugin-app-node@0.1.46-next.0
|
||||
|
||||
## 0.8.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-dynamic-feature-service",
|
||||
"version": "0.8.2",
|
||||
"version": "0.8.3-next.0",
|
||||
"description": "Backstage dynamic feature service",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @backstage/backend-openapi-utils
|
||||
|
||||
## 0.6.10-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-plugin-api@1.9.2-next.0
|
||||
|
||||
## 0.6.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-openapi-utils",
|
||||
"version": "0.6.9",
|
||||
"version": "0.6.10-next.0",
|
||||
"description": "OpenAPI typescript support.",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/backend-plugin-api
|
||||
|
||||
## 1.9.2-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-auth-node@0.7.2-next.0
|
||||
- @backstage/plugin-permission-node@0.11.1-next.0
|
||||
|
||||
## 1.9.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-plugin-api",
|
||||
"version": "1.9.1",
|
||||
"version": "1.9.2-next.0",
|
||||
"description": "Core API used by Backstage backend plugins",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
# @backstage/backend-test-utils
|
||||
|
||||
## 1.11.4-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/backend-defaults@0.17.2-next.0
|
||||
- @backstage/plugin-auth-node@0.7.2-next.0
|
||||
- @backstage/backend-app-api@1.7.1-next.0
|
||||
- @backstage/backend-plugin-api@1.9.2-next.0
|
||||
- @backstage/plugin-events-node@0.4.23-next.0
|
||||
|
||||
## 1.11.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/backend-test-utils",
|
||||
"version": "1.11.3",
|
||||
"version": "1.11.4-next.0",
|
||||
"description": "Test helpers library for Backstage backends",
|
||||
"backstage": {
|
||||
"role": "node-library"
|
||||
|
||||
@@ -56,6 +56,10 @@ export async function startMysqlContainer(image: string): Promise<{
|
||||
.withExposedPorts(3306)
|
||||
.withEnvironment({ MYSQL_ROOT_PASSWORD: password })
|
||||
.withTmpFs({ '/var/lib/mysql': 'rw' })
|
||||
.withCommand([
|
||||
'--default-authentication-plugin=mysql_native_password',
|
||||
'--skip-log-bin',
|
||||
])
|
||||
.start();
|
||||
|
||||
const host = container.getHost();
|
||||
@@ -176,6 +180,7 @@ export class MysqlEngine implements Engine {
|
||||
connection: {
|
||||
...this.#connection,
|
||||
database: databaseName,
|
||||
connectTimeout: 30_000,
|
||||
},
|
||||
...LARGER_POOL_CONFIG,
|
||||
});
|
||||
|
||||
@@ -129,5 +129,7 @@ export const LARGER_POOL_CONFIG = {
|
||||
pool: {
|
||||
min: 0,
|
||||
max: 50,
|
||||
acquireTimeoutMillis: 30_000,
|
||||
createTimeoutMillis: 30_000,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,47 @@
|
||||
# example-backend
|
||||
|
||||
## 0.0.51-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-backend@3.8.0-next.0
|
||||
- @backstage/plugin-search-backend@2.1.3-next.0
|
||||
- @backstage/plugin-kubernetes-backend@0.21.5-next.0
|
||||
- @backstage/plugin-signals-backend@0.3.16-next.0
|
||||
- @backstage/plugin-app-backend@0.5.15-next.0
|
||||
- @backstage/backend-defaults@0.17.2-next.0
|
||||
- @backstage/plugin-notifications-backend@0.6.6-next.0
|
||||
- @backstage/plugin-catalog-backend-module-logs@0.1.23-next.0
|
||||
- @backstage/plugin-auth-node@0.7.2-next.0
|
||||
- @backstage/plugin-mcp-actions-backend@0.1.14-next.0
|
||||
- @backstage/plugin-search-backend-module-catalog@0.3.16-next.0
|
||||
- @backstage/plugin-search-backend-module-techdocs@0.4.15-next.0
|
||||
- @backstage/plugin-techdocs-backend@2.2.1-next.0
|
||||
- @backstage/plugin-catalog-backend-module-openapi@0.2.23-next.0
|
||||
- @backstage/plugin-scaffolder-backend@4.0.1-next.0
|
||||
- @backstage/plugin-scaffolder-backend-module-github@0.9.10-next.0
|
||||
- @backstage/plugin-auth-backend@0.29.1-next.0
|
||||
- @backstage/plugin-auth-backend-module-github-provider@0.5.4-next.0
|
||||
- @backstage/plugin-auth-backend-module-openshift-provider@0.1.8-next.0
|
||||
- @backstage/plugin-devtools-backend@0.5.18-next.0
|
||||
- @backstage/plugin-events-backend@0.6.3-next.0
|
||||
- @backstage/plugin-events-backend-module-google-pubsub@0.2.4-next.0
|
||||
- @backstage/plugin-permission-backend@0.7.13-next.0
|
||||
- @backstage/plugin-permission-node@0.11.1-next.0
|
||||
- @backstage/plugin-proxy-backend@0.6.14-next.0
|
||||
- @backstage/plugin-search-backend-node@1.4.5-next.0
|
||||
- @backstage/backend-plugin-api@1.9.2-next.0
|
||||
- @backstage/plugin-auth-backend-module-guest-provider@0.2.20-next.0
|
||||
- @backstage/plugin-catalog-backend-module-unprocessed@0.6.13-next.0
|
||||
- @backstage/plugin-permission-backend-module-allow-all-policy@0.2.20-next.0
|
||||
- @backstage/plugin-catalog-backend-module-ai-model@0.1.1-next.0
|
||||
- @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.15-next.0
|
||||
- @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.21-next.0
|
||||
- @backstage/plugin-scaffolder-backend-module-notifications@0.1.23-next.0
|
||||
- @backstage/plugin-search-backend-module-elasticsearch@1.8.4-next.0
|
||||
- @backstage/plugin-search-backend-module-explore@0.3.15-next.0
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-backend",
|
||||
"version": "0.0.50",
|
||||
"version": "0.0.51-next.0",
|
||||
"backstage": {
|
||||
"role": "backend"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/catalog-client
|
||||
|
||||
## 1.16.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 8f20cc2: `CatalogApi.queryEntities` now accepts a `totalItems` option (`'include'` or `'exclude'`, default `'include'`) on initial requests. Pass `'exclude'` to skip the `totalItems` count when the caller doesn't need it.
|
||||
|
||||
## 1.15.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/catalog-client",
|
||||
"version": "1.15.1",
|
||||
"version": "1.16.0-next.0",
|
||||
"description": "An isomorphic client for the catalog backend",
|
||||
"backstage": {
|
||||
"role": "common-library"
|
||||
@@ -51,13 +51,13 @@
|
||||
"@backstage/catalog-model": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/filter-predicates": "workspace:^",
|
||||
"@backstage/plugin-catalog-common": "workspace:^",
|
||||
"cross-fetch": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"uri-template": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/plugin-catalog-common": "workspace:^",
|
||||
"@types/lodash": "^4.14.151",
|
||||
"msw": "^1.0.0"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @backstage/cli-defaults
|
||||
|
||||
## 0.1.3-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/cli-module-build@0.1.4-next.0
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/cli-defaults",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3-next.0",
|
||||
"description": "Default set of CLI modules for the Backstage CLI",
|
||||
"backstage": {
|
||||
"role": "cli-module"
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @backstage/cli-module-build
|
||||
|
||||
## 0.1.4-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- a1971ea: Suppress false-positive `@protobufjs/inquire` "Critical dependency" warning in the bundler. Since `protobufjs` 7.5.9, the dynamic require path in inquire is no longer exercised, but webpack/rspack still flags it during static analysis.
|
||||
- 8007b58: Updated dependency `embedded-postgres` to `18.3.0-beta.17`.
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/cli-module-build",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4-next.0",
|
||||
"description": "CLI module for Backstage CLI",
|
||||
"backstage": {
|
||||
"role": "cli-module"
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/cli
|
||||
|
||||
## 0.36.3-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/cli-module-build@0.1.4-next.0
|
||||
- @backstage/cli-defaults@0.1.3-next.0
|
||||
|
||||
## 0.36.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/cli",
|
||||
"version": "0.36.2",
|
||||
"version": "0.36.3-next.0",
|
||||
"description": "CLI for developing Backstage plugins and apps",
|
||||
"backstage": {
|
||||
"role": "cli"
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# @backstage/core-compat-api
|
||||
|
||||
## 0.5.12-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-react@3.0.1-next.0
|
||||
|
||||
## 0.5.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/core-compat-api",
|
||||
"version": "0.5.11",
|
||||
"version": "0.5.12-next.0",
|
||||
"backstage": {
|
||||
"role": "web-library"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @backstage/core-components
|
||||
|
||||
## 0.18.11-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- e0889a3: chore(deps): bump `qs` from 6.15.1 to 6.15.2
|
||||
- a07e6a3: Added the correctly-spelled `'header'` literal to the `TableFiltersClassKey` union type and deprecated the previous typoed `'heder'` literal. The generated CSS class with the old key is preserved for backwards compatibility; switch to `'header'` to avoid future removal.
|
||||
- 8add9b9: Fixed the proxy-based sign-in page failing to read the session token when the proxy issues a token whose payload is encoded using the URL-safe base64 alphabet. Such tokens are now decoded correctly so sign-in no longer breaks.
|
||||
|
||||
## 0.18.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/core-components",
|
||||
"version": "0.18.10",
|
||||
"version": "0.18.11-next.0",
|
||||
"description": "Core components used by Backstage plugins and apps",
|
||||
"backstage": {
|
||||
"role": "web-library"
|
||||
|
||||
@@ -114,6 +114,7 @@ const ConditionalAutoLogout = ({
|
||||
// Events will be rebound as long as `stopOnMount` is not set.
|
||||
setPromptOpen(false);
|
||||
setRemainingTimeCountdown(0);
|
||||
lastSeenOnlineStore.delete();
|
||||
identityApi.signOut();
|
||||
};
|
||||
|
||||
@@ -231,18 +232,37 @@ const parseConfig = (
|
||||
export const AutoLogout = (props: AutoLogoutProps): JSX.Element | null => {
|
||||
const identityApi = useApi(identityApiRef);
|
||||
const configApi = useApi(configApiRef);
|
||||
const [isLogged, setIsLogged] = useState(false);
|
||||
const [isLogged, setIsLogged] = useState<boolean | null>(null);
|
||||
const lastSeenOnlineStore: TimestampStore = useMemo(
|
||||
() => new DefaultTimestampStore(LAST_SEEN_ONLINE_STORAGE_KEY),
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
// if the user is not logged in, the autologout feature won't affect the app even if enabled
|
||||
async function isLoggedIn(identity: IdentityApi) {
|
||||
if ((await identity.getCredentials()).token) {
|
||||
setIsLogged(true);
|
||||
} else {
|
||||
let cancelled = false;
|
||||
|
||||
async function checkLogin(identity: IdentityApi) {
|
||||
try {
|
||||
const creds = await identity.getCredentials();
|
||||
if (cancelled) return;
|
||||
if (creds?.token) {
|
||||
setIsLogged(true);
|
||||
} else {
|
||||
setIsLogged(false);
|
||||
lastSeenOnlineStore.delete();
|
||||
}
|
||||
} catch (err) {
|
||||
if (cancelled) return;
|
||||
setIsLogged(false);
|
||||
lastSeenOnlineStore.delete();
|
||||
}
|
||||
}
|
||||
isLoggedIn(identityApi);
|
||||
}, [identityApi]);
|
||||
checkLogin(identityApi);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [lastSeenOnlineStore, identityApi]);
|
||||
|
||||
const {
|
||||
enabled,
|
||||
@@ -274,10 +294,6 @@ export const AutoLogout = (props: AutoLogoutProps): JSX.Element | null => {
|
||||
}
|
||||
}, [idleTimeoutMinutes, promptBeforeIdleSeconds]);
|
||||
|
||||
const lastSeenOnlineStore: TimestampStore = useMemo(
|
||||
() => new DefaultTimestampStore(LAST_SEEN_ONLINE_STORAGE_KEY),
|
||||
[],
|
||||
);
|
||||
const [promptOpen, setPromptOpen] = useState<boolean>(false);
|
||||
|
||||
const [remainingTimeCountdown, setRemainingTimeCountdown] =
|
||||
@@ -285,7 +301,8 @@ export const AutoLogout = (props: AutoLogoutProps): JSX.Element | null => {
|
||||
|
||||
useLogoutDisconnectedUserEffect({
|
||||
enableEffect: logoutIfDisconnected,
|
||||
autologoutIsEnabled: enabled && isLogged,
|
||||
autologoutIsEnabled: enabled,
|
||||
isLoggedIn: isLogged,
|
||||
idleTimeoutSeconds: idleTimeoutMinutes * 60,
|
||||
lastSeenOnlineStore,
|
||||
identityApi,
|
||||
|
||||
@@ -35,10 +35,29 @@ const mockTimestampStore = {
|
||||
};
|
||||
|
||||
describe('useLogoutDisconnectedUserEffect', () => {
|
||||
it('should not do anything if effect is not enabled', () => {
|
||||
it('should not do anything if isLoggedIn has not yet resolved', () => {
|
||||
const props: UseLogoutDisconnectedUserEffectProps = {
|
||||
enableEffect: true,
|
||||
autologoutIsEnabled: true,
|
||||
isLoggedIn: null,
|
||||
idleTimeoutSeconds: 300,
|
||||
lastSeenOnlineStore: mockTimestampStore,
|
||||
identityApi: mockIdentityApi,
|
||||
};
|
||||
|
||||
renderHook(() => useLogoutDisconnectedUserEffect(props));
|
||||
|
||||
expect(mockTimestampStore.get).not.toHaveBeenCalled();
|
||||
expect(mockTimestampStore.delete).not.toHaveBeenCalled();
|
||||
expect(mockTimestampStore.save).not.toHaveBeenCalled();
|
||||
expect(mockIdentityApi.signOut).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not do anything if effect is not enabled and isLoggedIn is false', () => {
|
||||
const props: UseLogoutDisconnectedUserEffectProps = {
|
||||
enableEffect: false,
|
||||
autologoutIsEnabled: true,
|
||||
isLoggedIn: false,
|
||||
idleTimeoutSeconds: 300,
|
||||
lastSeenOnlineStore: mockTimestampStore,
|
||||
identityApi: mockIdentityApi,
|
||||
@@ -54,6 +73,7 @@ describe('useLogoutDisconnectedUserEffect', () => {
|
||||
const props: UseLogoutDisconnectedUserEffectProps = {
|
||||
enableEffect: true,
|
||||
autologoutIsEnabled: false,
|
||||
isLoggedIn: true,
|
||||
idleTimeoutSeconds: 300,
|
||||
lastSeenOnlineStore: mockTimestampStore,
|
||||
identityApi: mockIdentityApi,
|
||||
@@ -64,12 +84,34 @@ describe('useLogoutDisconnectedUserEffect', () => {
|
||||
expect(mockTimestampStore.delete).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should delete the store and sign out when idle timeout has passed', () => {
|
||||
const staleStore = {
|
||||
...mockTimestampStore,
|
||||
get: jest.fn().mockReturnValue(new Date(Date.now() - 2000)),
|
||||
};
|
||||
const props: UseLogoutDisconnectedUserEffectProps = {
|
||||
enableEffect: true,
|
||||
autologoutIsEnabled: true,
|
||||
isLoggedIn: true,
|
||||
idleTimeoutSeconds: 1,
|
||||
lastSeenOnlineStore: staleStore,
|
||||
identityApi: mockIdentityApi,
|
||||
};
|
||||
|
||||
renderHook(() => useLogoutDisconnectedUserEffect(props));
|
||||
|
||||
expect(staleStore.delete).toHaveBeenCalled();
|
||||
expect(mockIdentityApi.signOut).toHaveBeenCalled();
|
||||
expect(staleStore.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call signOut if idle timeout passed', () => {
|
||||
jest.useFakeTimers();
|
||||
|
||||
const props: UseLogoutDisconnectedUserEffectProps = {
|
||||
enableEffect: true,
|
||||
autologoutIsEnabled: true,
|
||||
isLoggedIn: true,
|
||||
idleTimeoutSeconds: 1,
|
||||
lastSeenOnlineStore: {
|
||||
...mockTimestampStore,
|
||||
@@ -93,6 +135,7 @@ describe('useLogoutDisconnectedUserEffect', () => {
|
||||
const props: UseLogoutDisconnectedUserEffectProps = {
|
||||
enableEffect: true,
|
||||
autologoutIsEnabled: true,
|
||||
isLoggedIn: true,
|
||||
idleTimeoutSeconds: 300,
|
||||
lastSeenOnlineStore: mockTimestampStore,
|
||||
identityApi: mockIdentityApi,
|
||||
|
||||
@@ -24,6 +24,7 @@ export const LAST_SEEN_ONLINE_STORAGE_KEY =
|
||||
export type UseLogoutDisconnectedUserEffectProps = {
|
||||
enableEffect: boolean;
|
||||
autologoutIsEnabled: boolean;
|
||||
isLoggedIn: boolean | null;
|
||||
idleTimeoutSeconds: number;
|
||||
lastSeenOnlineStore: TimestampStore;
|
||||
identityApi: IdentityApi;
|
||||
@@ -32,6 +33,7 @@ export type UseLogoutDisconnectedUserEffectProps = {
|
||||
export const useLogoutDisconnectedUserEffect = ({
|
||||
enableEffect,
|
||||
autologoutIsEnabled,
|
||||
isLoggedIn,
|
||||
idleTimeoutSeconds,
|
||||
lastSeenOnlineStore,
|
||||
identityApi,
|
||||
@@ -41,30 +43,34 @@ export const useLogoutDisconnectedUserEffect = ({
|
||||
* Considers disconnected users as inactive users.
|
||||
* If all Backstage tabs are closed and idleTimeoutMinutes are passed then logout the user anyway.
|
||||
*/
|
||||
if (autologoutIsEnabled && enableEffect) {
|
||||
const lastSeenOnline = lastSeenOnlineStore.get();
|
||||
if (lastSeenOnline) {
|
||||
const now = new Date();
|
||||
const nowSeconds = Math.ceil(now.getTime() / 1000);
|
||||
const lastSeenOnlineSeconds = Math.ceil(
|
||||
lastSeenOnline.getTime() / 1000,
|
||||
);
|
||||
if (nowSeconds - lastSeenOnlineSeconds > idleTimeoutSeconds) {
|
||||
identityApi.signOut();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* save for the first time when app is loaded, so that
|
||||
* if user logs in and does nothing we still have a
|
||||
* lastSeenOnline value in store
|
||||
*/
|
||||
lastSeenOnlineStore.save(new Date());
|
||||
} else {
|
||||
lastSeenOnlineStore.delete();
|
||||
const shouldCheckDisconnectedUser = autologoutIsEnabled && enableEffect;
|
||||
|
||||
// Prevent lastSeen getting deleted before logged state is checked
|
||||
if (isLoggedIn === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!shouldCheckDisconnectedUser || !isLoggedIn) {
|
||||
lastSeenOnlineStore.delete();
|
||||
return;
|
||||
}
|
||||
|
||||
const lastSeenOnline = lastSeenOnlineStore.get();
|
||||
if (lastSeenOnline) {
|
||||
const now = new Date();
|
||||
const nowSeconds = Math.ceil(now.getTime() / 1000);
|
||||
const lastSeenOnlineSeconds = Math.ceil(lastSeenOnline.getTime() / 1000);
|
||||
if (nowSeconds - lastSeenOnlineSeconds > idleTimeoutSeconds) {
|
||||
lastSeenOnlineStore.delete();
|
||||
identityApi.signOut();
|
||||
return;
|
||||
}
|
||||
}
|
||||
lastSeenOnlineStore.save(new Date());
|
||||
}, [
|
||||
autologoutIsEnabled,
|
||||
enableEffect,
|
||||
isLoggedIn,
|
||||
identityApi,
|
||||
idleTimeoutSeconds,
|
||||
lastSeenOnlineStore,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# @backstage/create-app
|
||||
|
||||
## 0.8.4-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Bumped create-app version.
|
||||
|
||||
## 0.8.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/create-app",
|
||||
"version": "0.8.3",
|
||||
"version": "0.8.4-next.0",
|
||||
"description": "A CLI that helps you create your own Backstage app",
|
||||
"backstage": {
|
||||
"role": "cli"
|
||||
|
||||
@@ -37,3 +37,46 @@ fast-xml-builder@*:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/knex/-/knex-3.1.0.tgz#b6ddd5b5ad26a6315234a5b09ec38dc4a370bd8c"
|
||||
integrity sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==
|
||||
|
||||
// @protobufjs/inquire@1.1.1 dropped the eval-based workaround that hid its
|
||||
// dynamic require() from bundlers, which causes webpack/rspack to emit a
|
||||
// "Critical dependency: the request of a dependency is an expression" warning
|
||||
// that fails the build under CI=true. Pin to 1.1.0 until upstream is fixed.
|
||||
// See https://github.com/protobufjs/protobuf.js/issues/2057
|
||||
"@protobufjs/inquire@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
|
||||
integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
|
||||
|
||||
// protobufjs@7.5.6 bumped its @protobufjs/inquire dependency to ^1.1.1, which
|
||||
// would bypass the pin above. Pin protobufjs to 7.5.5 (the last version that
|
||||
// requests inquire ^1.1.0) for all known workspace queries.
|
||||
"protobufjs@^7.0.0":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.5.tgz#b7089ca4410374c75150baf277353ef76db69f96"
|
||||
integrity sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==
|
||||
|
||||
"protobufjs@^7.2.5":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.5.tgz#b7089ca4410374c75150baf277353ef76db69f96"
|
||||
integrity sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==
|
||||
|
||||
"protobufjs@^7.2.6":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.5.tgz#b7089ca4410374c75150baf277353ef76db69f96"
|
||||
integrity sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==
|
||||
|
||||
"protobufjs@^7.3.2":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.5.tgz#b7089ca4410374c75150baf277353ef76db69f96"
|
||||
integrity sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==
|
||||
|
||||
"protobufjs@^7.4.0":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.5.tgz#b7089ca4410374c75150baf277353ef76db69f96"
|
||||
integrity sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==
|
||||
|
||||
"protobufjs@^7.5.3":
|
||||
version "7.5.5"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.5.tgz#b7089ca4410374c75150baf277353ef76db69f96"
|
||||
integrity sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
# @backstage/dev-utils
|
||||
|
||||
## 1.1.24-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/plugin-catalog-react@3.0.1-next.0
|
||||
- @backstage/core-components@0.18.11-next.0
|
||||
- @backstage/app-defaults@1.7.9-next.0
|
||||
- @backstage/integration-react@1.2.19-next.0
|
||||
|
||||
## 1.1.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/dev-utils",
|
||||
"version": "1.1.23",
|
||||
"version": "1.1.24-next.0",
|
||||
"description": "Utilities for developing Backstage plugins.",
|
||||
"backstage": {
|
||||
"role": "web-library"
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
# e2e-test
|
||||
|
||||
## 0.2.41-next.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @backstage/create-app@0.8.4-next.0
|
||||
|
||||
## 0.2.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "e2e-test",
|
||||
"version": "0.2.40",
|
||||
"version": "0.2.41-next.0",
|
||||
"description": "E2E test for verifying Backstage packages",
|
||||
"backstage": {
|
||||
"role": "cli"
|
||||
|
||||
@@ -40,17 +40,22 @@ Replace deprecated tokens with their equivalents from the new semantic families.
|
||||
|
||||
### Neutral backgrounds
|
||||
|
||||
The neutral background scale has been renamed and extended. `--bui-bg-app` is replaced by the new `--bui-bg-neutral-1`, and the old overlay-based `--bui-bg-neutral-1..4` shift up by one to become `--bui-bg-neutral-2..5`:
|
||||
The neutral background tokens (`--bui-bg-app`, `--bui-bg-neutral-1..4`) are now the active semantic tokens with updated solid-color values. The `-hover`, `-pressed`, and `-disabled` interaction variants remain deprecated:
|
||||
|
||||
| Deprecated | Replacement |
|
||||
| ---------------------------------- | -------------------- |
|
||||
| `--bui-bg-app` | `--bui-bg-neutral-1` |
|
||||
| `--bui-bg-neutral-1` (old overlay) | `--bui-bg-neutral-2` |
|
||||
| `--bui-bg-neutral-2` (old overlay) | `--bui-bg-neutral-3` |
|
||||
| `--bui-bg-neutral-3` (old overlay) | `--bui-bg-neutral-4` |
|
||||
| `--bui-bg-neutral-4` (old overlay) | `--bui-bg-neutral-5` |
|
||||
|
||||
> Note: the old `--bui-bg-neutral-1..4` names are now reused for the new solid-color tokens, so this rule does not warn on them directly. The `-hover`, `-pressed`, and `-disabled` variants of those tokens remain deprecated.
|
||||
| Deprecated | Replacement |
|
||||
| ----------------------------- | --------------------- |
|
||||
| `--bui-bg-neutral-1-hover` | _(remove or restyle)_ |
|
||||
| `--bui-bg-neutral-1-pressed` | _(remove or restyle)_ |
|
||||
| `--bui-bg-neutral-1-disabled` | _(remove or restyle)_ |
|
||||
| `--bui-bg-neutral-2-hover` | _(remove or restyle)_ |
|
||||
| `--bui-bg-neutral-2-pressed` | _(remove or restyle)_ |
|
||||
| `--bui-bg-neutral-2-disabled` | _(remove or restyle)_ |
|
||||
| `--bui-bg-neutral-3-hover` | _(remove or restyle)_ |
|
||||
| `--bui-bg-neutral-3-pressed` | _(remove or restyle)_ |
|
||||
| `--bui-bg-neutral-3-disabled` | _(remove or restyle)_ |
|
||||
| `--bui-bg-neutral-4-hover` | _(remove or restyle)_ |
|
||||
| `--bui-bg-neutral-4-pressed` | _(remove or restyle)_ |
|
||||
| `--bui-bg-neutral-4-disabled` | _(remove or restyle)_ |
|
||||
|
||||
### Foreground
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ const DEPRECATED_TOKENS = [
|
||||
'--bui-bg-solid-hover',
|
||||
'--bui-bg-solid-pressed',
|
||||
'--bui-bg-solid-disabled',
|
||||
'--bui-bg-app',
|
||||
'--bui-bg-neutral-1-hover',
|
||||
'--bui-bg-neutral-1-pressed',
|
||||
'--bui-bg-neutral-1-disabled',
|
||||
|
||||
@@ -46,11 +46,11 @@ ruleTester.run('no-deprecated-bui-tokens', rule, {
|
||||
{ code: `const s = 'var(--bui-warning-bg)'` },
|
||||
{ code: `const s = 'var(--bui-announcement-bg)'` },
|
||||
{ code: `const s = 'var(--bui-gray-1)'` },
|
||||
{ code: `const s = 'var(--bui-bg-app)'` },
|
||||
{ code: `const s = 'var(--bui-bg-neutral-1)'` },
|
||||
{ code: `const s = 'var(--bui-bg-neutral-2)'` },
|
||||
{ code: `const s = 'var(--bui-bg-neutral-3)'` },
|
||||
{ code: `const s = 'var(--bui-bg-neutral-4)'` },
|
||||
{ code: `const s = 'var(--bui-bg-neutral-5)'` },
|
||||
// Unrelated strings — should not warn
|
||||
{ code: `const s = 'some-other-string'` },
|
||||
{ code: `const n = 42` },
|
||||
@@ -83,10 +83,6 @@ ruleTester.run('no-deprecated-bui-tokens', rule, {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: `const s = 'var(--bui-bg-app)'`,
|
||||
errors: [{ messageId: 'deprecated', data: { token: '--bui-bg-app' } }],
|
||||
},
|
||||
{
|
||||
code: `const s = 'var(--bui-bg-neutral-2-hover)'`,
|
||||
errors: [
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user