(
// Get the responsive values for the variant and weight
const responsiveVariant = useResponsiveValue(variant);
const responsiveWeight = useResponsiveValue(weight);
+ const responsiveColor = useResponsiveValue(color);
return (
(
'canon-Text',
responsiveVariant && `canon-Text--variant-${responsiveVariant}`,
responsiveWeight && `canon-Text--weight-${responsiveWeight}`,
+ responsiveColor && `canon-Text--color-${responsiveColor}`,
className,
)}
style={style}
diff --git a/packages/canon/src/components/Text/styles.css b/packages/canon/src/components/Text/styles.css
index 871960b8db..8252554280 100644
--- a/packages/canon/src/components/Text/styles.css
+++ b/packages/canon/src/components/Text/styles.css
@@ -48,3 +48,23 @@
.canon-Text--weight-bold {
font-weight: var(--canon-font-weight-bold);
}
+
+.canon-Text--color-primary {
+ color: var(--canon-fg-primary);
+}
+
+.canon-Text--color-secondary {
+ color: var(--canon-fg-secondary);
+}
+
+.canon-Text--color-danger {
+ color: var(--canon-fg-danger);
+}
+
+.canon-Text--color-warning {
+ color: var(--canon-fg-warning);
+}
+
+.canon-Text--color-success {
+ color: var(--canon-fg-success);
+}
diff --git a/packages/canon/src/components/Text/types.ts b/packages/canon/src/components/Text/types.ts
index 4fdc824120..d4f1615694 100644
--- a/packages/canon/src/components/Text/types.ts
+++ b/packages/canon/src/components/Text/types.ts
@@ -27,6 +27,18 @@ export interface TextProps {
| 'label'
| Partial>;
weight?: 'regular' | 'bold' | Partial>;
+ color?:
+ | 'primary'
+ | 'secondary'
+ | 'danger'
+ | 'warning'
+ | 'success'
+ | Partial<
+ Record<
+ Breakpoint,
+ 'primary' | 'secondary' | 'danger' | 'warning' | 'success'
+ >
+ >;
className?: string;
style?: CSSProperties;
}
diff --git a/packages/canon/src/css/components.css b/packages/canon/src/css/components.css
index 40b791a83f..3361c33248 100644
--- a/packages/canon/src/css/components.css
+++ b/packages/canon/src/css/components.css
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-/* Components */
@import '../components/Box/styles.css';
@import '../components/Button/styles.css';
@import '../components/Flex/styles.css';
@@ -28,3 +27,4 @@
@import '../components/Input/Input.styles.css';
@import '../components/Field/Field.styles.css';
@import '../components/Menu/Menu.styles.css';
+@import '../components/Link/styles.css';
diff --git a/packages/canon/src/css/core.css b/packages/canon/src/css/core.css
index e1b3baf0ca..8c7863953a 100644
--- a/packages/canon/src/css/core.css
+++ b/packages/canon/src/css/core.css
@@ -72,7 +72,8 @@
/* Background Colors */
--canon-bg: #f8f8f8;
- --canon-bg-elevated: #fff;
+ --canon-bg-surface-1: #fff;
+ --canon-bg-surface-2: #ececec;
--canon-bg-solid: #1f5493;
--canon-bg-solid-hover: #163a66;
--canon-bg-solid-pressed: #0f2b4e;
@@ -116,7 +117,8 @@
[data-theme='dark'] {
/* Background Colors */
--canon-bg: #000000;
- --canon-bg-elevated: #191919;
+ --canon-bg-surface-1: #191919;
+ --canon-bg-surface-2: #242424;
--canon-bg-solid: #9cc9ff;
--canon-bg-solid-hover: #83b9fd;
--canon-bg-solid-pressed: #83b9fd;
diff --git a/packages/canon/src/css/styles.css b/packages/canon/src/css/styles.css
new file mode 100644
index 0000000000..f5d623389f
--- /dev/null
+++ b/packages/canon/src/css/styles.css
@@ -0,0 +1,18 @@
+/*
+ * 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 './core.css';
+@import './components.css';
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index 145eaa5d3e..76c1af75bb 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,30 @@
# @backstage/cli
+## 0.31.0-next.1
+
+### Minor Changes
+
+- 5b70679: **BREAKING**: ESLint warnings no longer trigger system exit codes like errors do.
+
+ Set the max number of warnings to `-1` during linting to enable the gradual adoption of new ESLint rules. To restore the previous behavior, include the `--max-warnings 0` flag in the `backstage-cli lint` command.
+
+### Patch Changes
+
+- e0b226b: build(deps): bump `esbuild` from 0.24.2 to 0.25.0
+- 4d45498: Fixed the package prepack command so that it no longer produces unnecessary `index` entries in the `typesVersions` map, which could cause `/index` to be added when automatically adding imports.
+- f8bd342: Fix a bug in the translation of the deprecated `--scope` option for the `new` command that could cause plugins to have `backstage-backstage-plugin` in their name.
+- Updated dependencies
+ - @backstage/config-loader@1.10.0-next.0
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/cli-node@0.2.13
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/eslint-plugin@0.1.10
+ - @backstage/release-manifests@0.0.12
+ - @backstage/types@1.2.1
+
## 0.30.1-next.0
### Patch Changes
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 6406034c44..dec6fed874 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/cli",
- "version": "0.30.1-next.0",
+ "version": "0.31.0-next.1",
"description": "CLI for developing Backstage plugins and apps",
"backstage": {
"role": "cli"
diff --git a/packages/cli/src/alpha.ts b/packages/cli/src/alpha.ts
index d6189a4256..49e739bbdc 100644
--- a/packages/cli/src/alpha.ts
+++ b/packages/cli/src/alpha.ts
@@ -29,5 +29,6 @@ import chalk from 'chalk';
initializer.add(import('./modules/build/alpha'));
initializer.add(import('./modules/migrate/alpha'));
initializer.add(import('./modules/test/alpha'));
+ initializer.add(import('./modules/lint/alpha'));
await initializer.run();
})();
diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts
index ee73c071f8..9a76711d08 100644
--- a/packages/cli/src/commands/index.ts
+++ b/packages/cli/src/commands/index.ts
@@ -31,6 +31,10 @@ import {
registerRepoCommands as registerRepoTestCommands,
registerPackageCommands as registerPackageTestCommands,
} from '../modules/test';
+import {
+ registerPackageCommands as registerPackageLintCommands,
+ registerRepoCommands as registerRepoLintCommands,
+} from '../modules/lint';
export function registerRepoCommand(program: Command) {
const command = program
@@ -39,33 +43,7 @@ export function registerRepoCommand(program: Command) {
registerRepoBuildCommands(command);
registerRepoTestCommands(command);
-
- command
- .command('lint')
- .description('Lint all packages in the project')
- .option(
- '--format ',
- 'Lint report output format',
- 'eslint-formatter-friendly',
- )
- .option(
- '--output-file ',
- 'Write the lint report to a file instead of stdout',
- )
- .option(
- '--since [',
- 'Only lint packages that changed since the specified ref',
- )
- .option(
- '--successCache',
- 'Enable success caching, which skips running tests for unchanged packages that were successful in the previous run',
- )
- .option(
- '--successCacheDir ',
- 'Set the success cache location, (default: node_modules/.cache/backstage-cli)',
- )
- .option('--fix', 'Attempt to automatically fix violations')
- .action(lazy(() => import('./repo/lint'), 'command'));
+ registerRepoLintCommands(command);
command
.command('fix')
@@ -115,25 +93,7 @@ export function registerScriptCommand(program: Command) {
registerPackageBuildCommands(command);
registerPackageTestCommands(command);
- command
- .command('lint [directories...]')
- .option(
- '--format ',
- 'Lint report output format',
- 'eslint-formatter-friendly',
- )
- .option(
- '--output-file ',
- 'Write the lint report to a file instead of stdout',
- )
- .option('--fix', 'Attempt to automatically fix violations')
- .option(
- '--max-warnings ',
- 'Fail if more than this number of warnings. -1 allows warnings. (default: -1)',
- )
- .description('Lint a package')
- .action(lazy(() => import('./lint'), 'default'));
-
+ registerPackageLintCommands(command);
command
.command('clean')
.description('Delete cache directories')
diff --git a/packages/cli/src/modules/lint/alpha.ts b/packages/cli/src/modules/lint/alpha.ts
new file mode 100644
index 0000000000..8703e73b5e
--- /dev/null
+++ b/packages/cli/src/modules/lint/alpha.ts
@@ -0,0 +1,86 @@
+/*
+ * 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 { createCliPlugin } from '../../wiring/factory';
+import { Command } from 'commander';
+import { lazy } from '../../lib/lazy';
+
+export default createCliPlugin({
+ pluginId: 'lint',
+ init: async reg => {
+ reg.addCommand({
+ path: ['package', 'lint'],
+ description: 'Lint a package',
+ execute: async ({ args }) => {
+ const command = new Command();
+ command.arguments('[directories...]');
+ command.option('--fix', 'Attempt to automatically fix violations');
+ command.option(
+ '--format ',
+ 'Lint report output format',
+ 'eslint-formatter-friendly',
+ );
+ command.option(
+ '--output-file ',
+ 'Write the lint report to a file instead of stdout',
+ );
+ command.option(
+ '--max-warnings ',
+ 'Fail if more than this number of warnings. -1 allows warnings. (default: 0)',
+ );
+ command.description('Lint a package');
+ command.action(
+ lazy(() => import('./commands/package/lint'), 'default'),
+ );
+
+ await command.parseAsync(args, { from: 'user' });
+ },
+ });
+
+ reg.addCommand({
+ path: ['repo', 'lint'],
+ description: 'Lint a repository',
+ execute: async ({ args }) => {
+ const command = new Command();
+ command.option('--fix', 'Attempt to automatically fix violations');
+ command.option(
+ '--format ',
+ 'Lint report output format',
+ 'eslint-formatter-friendly',
+ );
+ command.option(
+ '--output-file ',
+ 'Write the lint report to a file instead of stdout',
+ );
+ command.option(
+ '--successCache',
+ 'Enable success caching, which skips running tests for unchanged packages that were successful in the previous run',
+ );
+ command.option(
+ '--successCacheDir ',
+ 'Set the success cache location, (default: node_modules/.cache/backstage-cli)',
+ );
+ command.option(
+ '--since ][',
+ 'Only lint packages that changed since the specified ref',
+ );
+ command.description('Lint a repository');
+ command.action(lazy(() => import('./commands/repo/lint'), 'command'));
+
+ await command.parseAsync(args, { from: 'user' });
+ },
+ });
+ },
+});
diff --git a/packages/cli/src/commands/lint.ts b/packages/cli/src/modules/lint/commands/package/lint.ts
similarity index 97%
rename from packages/cli/src/commands/lint.ts
rename to packages/cli/src/modules/lint/commands/package/lint.ts
index cd5c265e72..a1e45c0113 100644
--- a/packages/cli/src/commands/lint.ts
+++ b/packages/cli/src/modules/lint/commands/package/lint.ts
@@ -16,7 +16,7 @@
import fs from 'fs-extra';
import { OptionValues } from 'commander';
-import { paths } from '../lib/paths';
+import { paths } from '../../../../lib/paths';
import { ESLint } from 'eslint';
export default async (directories: string[], opts: OptionValues) => {
diff --git a/packages/cli/src/commands/repo/lint.ts b/packages/cli/src/modules/lint/commands/repo/lint.ts
similarity index 96%
rename from packages/cli/src/commands/repo/lint.ts
rename to packages/cli/src/modules/lint/commands/repo/lint.ts
index f78b51ea5d..15a3584020 100644
--- a/packages/cli/src/commands/repo/lint.ts
+++ b/packages/cli/src/modules/lint/commands/repo/lint.ts
@@ -24,10 +24,10 @@ import {
BackstagePackageJson,
Lockfile,
} from '@backstage/cli-node';
-import { paths } from '../../lib/paths';
-import { runWorkerQueueThreads } from '../../lib/parallel';
-import { createScriptOptionsParser } from './optionsParser';
-import { SuccessCache } from '../../lib/cache/SuccessCache';
+import { paths } from '../../../../lib/paths';
+import { runWorkerQueueThreads } from '../../../../lib/parallel';
+import { createScriptOptionsParser } from '../../../../commands/repo/optionsParser';
+import { SuccessCache } from '../../../../lib/cache/SuccessCache';
function depCount(pkg: BackstagePackageJson) {
const deps = pkg.dependencies ? Object.keys(pkg.dependencies).length : 0;
diff --git a/packages/cli/src/modules/lint/index.ts b/packages/cli/src/modules/lint/index.ts
new file mode 100644
index 0000000000..e2bfcd0779
--- /dev/null
+++ b/packages/cli/src/modules/lint/index.ts
@@ -0,0 +1,67 @@
+/*
+ * 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 { Command } from 'commander';
+import { lazy } from '../../lib/lazy';
+
+export function registerPackageCommands(command: Command) {
+ command
+ .command('lint [directories...]')
+ .option(
+ '--format ',
+ 'Lint report output format',
+ 'eslint-formatter-friendly',
+ )
+ .option(
+ '--output-file ',
+ 'Write the lint report to a file instead of stdout',
+ )
+ .option('--fix', 'Attempt to automatically fix violations')
+ .option(
+ '--max-warnings ',
+ 'Fail if more than this number of warnings. -1 allows warnings. (default: 0)',
+ )
+ .description('Lint a package')
+ .action(lazy(() => import('./commands/package/lint'), 'default'));
+}
+
+export function registerRepoCommands(command: Command) {
+ command
+ .command('lint')
+ .description('Lint all packages in the project')
+ .option(
+ '--format ',
+ 'Lint report output format',
+ 'eslint-formatter-friendly',
+ )
+ .option(
+ '--output-file ',
+ 'Write the lint report to a file instead of stdout',
+ )
+ .option(
+ '--since ][',
+ 'Only lint packages that changed since the specified ref',
+ )
+ .option(
+ '--successCache',
+ 'Enable success caching, which skips running tests for unchanged packages that were successful in the previous run',
+ )
+ .option(
+ '--successCacheDir ',
+ 'Set the success cache location, (default: node_modules/.cache/backstage-cli)',
+ )
+ .option('--fix', 'Attempt to automatically fix violations')
+ .action(lazy(() => import('./commands/repo/lint'), 'command'));
+}
diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md
index 7e64bab192..5a451559ed 100644
--- a/packages/config-loader/CHANGELOG.md
+++ b/packages/config-loader/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/config-loader
+## 1.10.0-next.0
+
+### Minor Changes
+
+- 2fd73aa: The include transforms applied during config loading will now only apply to the known keys `$file`, `$env`, and `$include`. Any other key that begins with a `# @backstage/config-loader will now be passed through as is.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 1.9.6
### Patch Changes
diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json
index 421c30db0c..37d4e94e44 100644
--- a/packages/config-loader/package.json
+++ b/packages/config-loader/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/config-loader",
- "version": "1.9.6",
+ "version": "1.10.0-next.0",
"description": "Config loading functionality used by Backstage backend, and CLI",
"backstage": {
"role": "node-library"
diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md
index e5b32c385f..ef8e52394f 100644
--- a/packages/core-app-api/CHANGELOG.md
+++ b/packages/core-app-api/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/core-app-api
+## 1.16.0-next.0
+
+### Minor Changes
+
+- 9262001: The default auth injection middleware for the `FetchApi` will now also take configuration under `discovery.endpoints` into consideration when deciding whether to include credentials or not.
+- 12f8e01: The `discovery.endpoints` configuration no longer requires both `internal` and `external` target when using the object form, instead falling back to the default.
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
## 1.15.5
### Patch Changes
diff --git a/packages/core-app-api/config.d.ts b/packages/core-app-api/config.d.ts
index 1a7ccb666f..18519bc291 100644
--- a/packages/core-app-api/config.d.ts
+++ b/packages/core-app-api/config.d.ts
@@ -163,7 +163,7 @@ export interface Config {
/**
* @visibility frontend
*/
- external: string;
+ external?: string;
};
/**
* Array of plugins which use the target baseUrl.
diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json
index 76c3632b65..eb61af575f 100644
--- a/packages/core-app-api/package.json
+++ b/packages/core-app-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/core-app-api",
- "version": "1.15.5",
+ "version": "1.16.0-next.0",
"description": "Core app API used by Backstage apps",
"backstage": {
"role": "web-library"
diff --git a/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.test.ts b/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.test.ts
index 0ba6457a1b..39e7ec7fab 100644
--- a/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.test.ts
+++ b/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.test.ts
@@ -72,6 +72,30 @@ describe('FrontendHostDiscovery', () => {
);
});
+ it('should not use internal plugin overrides', async () => {
+ const discovery = FrontendHostDiscovery.fromConfig(
+ new ConfigReader({
+ backend: {
+ baseUrl: 'http://localhost:40',
+ },
+ discovery: {
+ endpoints: [
+ {
+ target: {
+ internal: 'http://catalog-backend-internal:8080/api/catalog',
+ },
+ plugins: ['catalog'],
+ },
+ ],
+ },
+ }),
+ );
+
+ await expect(discovery.getBaseUrl('catalog')).resolves.toBe(
+ 'http://localhost:40/api/catalog',
+ );
+ });
+
it('uses a single target for internal and external for a plugin', async () => {
const discovery = FrontendHostDiscovery.fromConfig(
new ConfigReader({
diff --git a/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.ts b/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.ts
index 950b746684..9c10c386d6 100644
--- a/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.ts
+++ b/packages/core-app-api/src/apis/implementations/DiscoveryApi/FrontendHostDiscovery.ts
@@ -54,8 +54,11 @@ export class FrontendHostDiscovery implements DiscoveryApi {
?.flatMap(e => {
const target =
typeof e.get('target') === 'object'
- ? e.getString('target.external')
+ ? e.getOptionalString('target.external')
: e.getString('target');
+ if (!target) {
+ return [];
+ }
const discovery = UrlPatternDiscovery.compile(target);
return e
.getStringArray('plugins')
diff --git a/packages/core-app-api/src/apis/implementations/FetchApi/IdentityAuthInjectorFetchMiddleware.test.ts b/packages/core-app-api/src/apis/implementations/FetchApi/IdentityAuthInjectorFetchMiddleware.test.ts
index 50f93444ed..7d18325540 100644
--- a/packages/core-app-api/src/apis/implementations/FetchApi/IdentityAuthInjectorFetchMiddleware.test.ts
+++ b/packages/core-app-api/src/apis/implementations/FetchApi/IdentityAuthInjectorFetchMiddleware.test.ts
@@ -115,4 +115,49 @@ describe('IdentityAuthInjectorFetchMiddleware', () => {
['authorization', 'do-not-clobber'],
]);
});
+
+ describe('.getDiscoveryUrlPrefixes', () => {
+ it('works with no endpoints', () => {
+ const config = new ConfigReader({
+ backend: { baseUrl: 'https://a.com' },
+ });
+ expect(
+ IdentityAuthInjectorFetchMiddleware.getDiscoveryUrlPrefixes(config),
+ ).toEqual([]);
+ });
+
+ it('works with endpoints', () => {
+ const config = new ConfigReader({
+ backend: { baseUrl: 'https://a.com' },
+ discovery: {
+ endpoints: [
+ { target: 'https://b.com', plugins: ['p1'] },
+ { target: 'https://c.com/{{pluginId}}', plugins: ['p2', 'p3'] },
+ { target: { external: 'https://d.com' }, plugins: ['q1'] },
+ {
+ target: { external: 'https://e.com/{{pluginId}}' },
+ plugins: ['q2', 'q3'],
+ },
+ {
+ target: {
+ external: 'https://{{ pluginId }}.e.com/{{pluginId}}',
+ },
+ plugins: ['q4'],
+ },
+ ],
+ },
+ });
+ expect(
+ IdentityAuthInjectorFetchMiddleware.getDiscoveryUrlPrefixes(config),
+ ).toEqual([
+ 'https://b.com',
+ 'https://c.com/p2',
+ 'https://c.com/p3',
+ 'https://d.com',
+ 'https://e.com/q2',
+ 'https://e.com/q3',
+ 'https://q4.e.com/q4',
+ ]);
+ });
+ });
});
diff --git a/packages/core-app-api/src/apis/implementations/FetchApi/IdentityAuthInjectorFetchMiddleware.ts b/packages/core-app-api/src/apis/implementations/FetchApi/IdentityAuthInjectorFetchMiddleware.ts
index 6f9733cba1..842f9191eb 100644
--- a/packages/core-app-api/src/apis/implementations/FetchApi/IdentityAuthInjectorFetchMiddleware.ts
+++ b/packages/core-app-api/src/apis/implementations/FetchApi/IdentityAuthInjectorFetchMiddleware.ts
@@ -45,6 +45,25 @@ export class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {
);
}
+ /**
+ * Returns an array of plugin URL prefixes derived from the static `discovery`
+ * configuration, to be used as `urlPrefixAllowlist` option of {@link create}.
+ */
+ static getDiscoveryUrlPrefixes(config: Config): string[] {
+ const endpointConfigs =
+ config.getOptionalConfigArray('discovery.endpoints') || [];
+ return endpointConfigs.flatMap(c => {
+ const target =
+ typeof c.get('target') === 'object'
+ ? c.getString('target.external')
+ : c.getString('target');
+ const plugins = c.getStringArray('plugins');
+ return plugins.map(pluginId =>
+ target.replace(/\{\{\s*pluginId\s*\}\}/g, pluginId),
+ );
+ });
+ }
+
constructor(
public readonly identityApi: IdentityApi,
public readonly allowUrl: (url: string) => boolean,
@@ -87,7 +106,12 @@ function buildMatcher(options: {
} else if (options.urlPrefixAllowlist) {
return buildPrefixMatcher(options.urlPrefixAllowlist);
} else if (options.config) {
- return buildPrefixMatcher([options.config.getString('backend.baseUrl')]);
+ return buildPrefixMatcher([
+ options.config.getString('backend.baseUrl'),
+ ...IdentityAuthInjectorFetchMiddleware.getDiscoveryUrlPrefixes(
+ options.config,
+ ),
+ ]);
}
return () => false;
}
diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md
index 94f5bd638b..4580cdc7aa 100644
--- a/packages/core-compat-api/CHANGELOG.md
+++ b/packages/core-compat-api/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/core-compat-api
+## 0.4.0-next.2
+
+### Minor Changes
+
+- 8250ffe: **BREAKING**: Dropped support for the removed opaque `@backstage/ExtensionOverrides` and `@backstage/BackstagePlugin` types.
+
+### Patch Changes
+
+- e7fab55: Added the `entityPage` option to `convertLegacyApp`, which you can read more about in the [app migration docs](https://backstage.io/docs/frontend-system/building-apps/migrating#entity-pages).
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/version-bridge@1.0.11
+
## 0.3.7-next.1
### Patch Changes
diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json
index d49cb9bdaa..0db4872230 100644
--- a/packages/core-compat-api/package.json
+++ b/packages/core-compat-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/core-compat-api",
- "version": "0.3.7-next.1",
+ "version": "0.4.0-next.2",
"backstage": {
"role": "web-library"
},
@@ -33,6 +33,7 @@
"dependencies": {
"@backstage/core-plugin-api": "workspace:^",
"@backstage/frontend-plugin-api": "workspace:^",
+ "@backstage/plugin-catalog-react": "workspace:^",
"@backstage/version-bridge": "workspace:^",
"lodash": "^4.17.21"
},
diff --git a/packages/core-compat-api/report.api.md b/packages/core-compat-api/report.api.md
index 50aec1f646..125533c6d3 100644
--- a/packages/core-compat-api/report.api.md
+++ b/packages/core-compat-api/report.api.md
@@ -14,7 +14,6 @@ import { AppTheme } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { ComponentType } from 'react';
import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
-import { ExtensionOverrides } from '@backstage/frontend-plugin-api';
import { ExternalRouteRef } from '@backstage/core-plugin-api';
import { ExternalRouteRef as ExternalRouteRef_2 } from '@backstage/frontend-plugin-api';
import { FeatureFlag } from '@backstage/core-plugin-api';
@@ -34,7 +33,13 @@ export function compatWrapper(element: ReactNode): React_2.JSX.Element;
// @public (undocumented)
export function convertLegacyApp(
rootElement: React_2.JSX.Element,
-): (FrontendPlugin | FrontendModule | ExtensionOverrides)[];
+ options?: ConvertLegacyAppOptions,
+): (FrontendPlugin | FrontendModule)[];
+
+// @public (undocumented)
+export interface ConvertLegacyAppOptions {
+ entityPage?: React_2.JSX.Element;
+}
// @public (undocumented)
export function convertLegacyAppOptions(options?: {
diff --git a/packages/core-compat-api/src/collectEntityPageContents.test.tsx b/packages/core-compat-api/src/collectEntityPageContents.test.tsx
new file mode 100644
index 0000000000..53aa90f247
--- /dev/null
+++ b/packages/core-compat-api/src/collectEntityPageContents.test.tsx
@@ -0,0 +1,171 @@
+/*
+ * 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 { ExtensionAttachToSpec } from '@backstage/frontend-plugin-api';
+import { EntityLayout, EntitySwitch, isKind } from '@backstage/plugin-catalog';
+import React from 'react';
+import { collectEntityPageContents } from './collectEntityPageContents';
+import {
+ createComponentExtension,
+ createPlugin,
+} from '@backstage/core-plugin-api';
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+import {
+ resolveExtensionDefinition,
+ toInternalExtension,
+} from '../../frontend-plugin-api/src/wiring/resolveExtensionDefinition';
+
+const fooPlugin = createPlugin({
+ id: 'foo',
+});
+
+const FooContent = fooPlugin.provide(
+ createComponentExtension({
+ name: 'FooContent',
+ component: { sync: () => ]foo content
},
+ }),
+);
+const OtherFooContent = fooPlugin.provide(
+ createComponentExtension({
+ name: 'OtherFooContent',
+ component: { sync: () => other foo content
},
+ }),
+);
+
+const simpleTestContent = (
+
+
+ overview content
+
+
+
+
+
+ bar content
+
+
+);
+
+const otherTestContent = (
+
+
+ other overview content
+
+
+
+
+
+);
+
+function collect(element: React.JSX.Element) {
+ const result = new Array<{
+ id: string;
+ attachTo: ExtensionAttachToSpec;
+ }>();
+
+ collectEntityPageContents(element, {
+ discoverExtension(extension, plugin) {
+ const ext = toInternalExtension(
+ resolveExtensionDefinition(extension, {
+ namespace: plugin?.getId() ?? 'test',
+ }),
+ );
+ result.push({ id: ext.id, attachTo: ext.attachTo });
+ },
+ });
+ return result;
+}
+
+describe('collectEntityPageContents', () => {
+ it('should collect contents from a simple entity page', () => {
+ expect(collect(simpleTestContent)).toMatchInlineSnapshot(`
+ [
+ {
+ "attachTo": {
+ "id": "entity-content:catalog/overview",
+ "input": "cards",
+ },
+ "id": "entity-card:test/discovered-1",
+ },
+ {
+ "attachTo": {
+ "id": "page:catalog/entity",
+ "input": "contents",
+ },
+ "id": "entity-content:foo/discovered-1",
+ },
+ {
+ "attachTo": {
+ "id": "page:catalog/entity",
+ "input": "contents",
+ },
+ "id": "entity-content:test/discovered-2",
+ },
+ ]
+ `);
+ });
+
+ it('should collect contents from an entity page with an entity switch', () => {
+ expect(
+ collect(
+
+
+ {simpleTestContent}
+
+ {otherTestContent}
+ ,
+ ),
+ ).toMatchInlineSnapshot(`
+ [
+ {
+ "attachTo": {
+ "id": "entity-content:catalog/overview",
+ "input": "cards",
+ },
+ "id": "entity-card:test/discovered-1",
+ },
+ {
+ "attachTo": {
+ "id": "page:catalog/entity",
+ "input": "contents",
+ },
+ "id": "entity-content:foo/discovered-1",
+ },
+ {
+ "attachTo": {
+ "id": "page:catalog/entity",
+ "input": "contents",
+ },
+ "id": "entity-content:test/discovered-2",
+ },
+ {
+ "attachTo": {
+ "id": "entity-content:catalog/overview",
+ "input": "cards",
+ },
+ "id": "entity-card:test/discovered-2",
+ },
+ {
+ "attachTo": {
+ "id": "page:catalog/entity",
+ "input": "contents",
+ },
+ "id": "entity-content:foo/discovered-3",
+ },
+ ]
+ `);
+ });
+});
diff --git a/packages/core-compat-api/src/collectEntityPageContents.ts b/packages/core-compat-api/src/collectEntityPageContents.ts
new file mode 100644
index 0000000000..503642733e
--- /dev/null
+++ b/packages/core-compat-api/src/collectEntityPageContents.ts
@@ -0,0 +1,249 @@
+/*
+ * 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 {
+ ApiHolder,
+ getComponentData,
+ BackstagePlugin as LegacyBackstagePlugin,
+} from '@backstage/core-plugin-api';
+import { ExtensionDefinition } from '@backstage/frontend-plugin-api';
+import React from 'react';
+import {
+ EntityCardBlueprint,
+ EntityContentBlueprint,
+} from '@backstage/plugin-catalog-react/alpha';
+import { normalizeRoutePath } from './normalizeRoutePath';
+
+const ENTITY_SWITCH_KEY = 'core.backstage.entitySwitch';
+const ENTITY_ROUTE_KEY = 'plugin.catalog.entityLayoutRoute';
+
+// Placeholder to make sure internal types here are consitent
+type Entity = { apiVersion: string; kind: string };
+
+type EntityFilter = (entity: Entity, ctx: { apis: ApiHolder }) => boolean;
+type AsyncEntityFilter = (
+ entity: Entity,
+ context: { apis: ApiHolder },
+) => boolean | Promise;
+
+function allFilters(
+ ...ifs: (EntityFilter | undefined)[]
+): EntityFilter | undefined {
+ const filtered = ifs.filter(Boolean) as EntityFilter[];
+ if (!filtered.length) {
+ return undefined;
+ }
+ if (filtered.length === 1) {
+ return filtered[0];
+ }
+ return (entity, ctx) => filtered.every(ifFunc => ifFunc(entity, ctx));
+}
+
+function anyFilters(
+ ...ifs: (EntityFilter | undefined)[]
+): EntityFilter | undefined {
+ const filtered = ifs.filter(Boolean) as EntityFilter[];
+ if (!filtered.length) {
+ return undefined;
+ }
+ if (filtered.length === 1) {
+ return filtered[0];
+ }
+ return (entity, ctx) => filtered.some(ifFunc => ifFunc(entity, ctx));
+}
+
+function invertFilter(ifFunc?: EntityFilter): EntityFilter {
+ if (!ifFunc) {
+ return () => true;
+ }
+ return (entity, ctx) => !ifFunc(entity, ctx);
+}
+
+export function collectEntityPageContents(
+ entityPageElement: React.JSX.Element,
+ context: {
+ discoverExtension(
+ extension: ExtensionDefinition,
+ plugin?: LegacyBackstagePlugin,
+ ): void;
+ },
+) {
+ let cardCounter = 1;
+ let routeCounter = 1;
+
+ function traverse(element: React.ReactNode, parentFilter?: EntityFilter) {
+ if (!React.isValidElement(element)) {
+ return;
+ }
+
+ const pageNode = maybeParseEntityPageNode(element);
+ if (pageNode) {
+ if (pageNode.type === 'route') {
+ const mergedIf = allFilters(parentFilter, pageNode.if);
+
+ if (pageNode.path === '/') {
+ context.discoverExtension(
+ EntityCardBlueprint.makeWithOverrides({
+ name: `discovered-${cardCounter++}`,
+ factory(originalFactory, { apis }) {
+ return originalFactory({
+ type: 'content',
+ filter: mergedIf && (entity => mergedIf(entity, { apis })),
+ loader: () => Promise.resolve(pageNode.children),
+ });
+ },
+ }),
+ );
+ } else {
+ const name = `discovered-${routeCounter++}`;
+
+ context.discoverExtension(
+ EntityContentBlueprint.makeWithOverrides({
+ name,
+ factory(originalFactory, { apis }) {
+ return originalFactory({
+ defaultPath: normalizeRoutePath(pageNode.path),
+ defaultTitle: pageNode.title,
+ filter: mergedIf && (entity => mergedIf(entity, { apis })),
+ loader: () => Promise.resolve(pageNode.children),
+ });
+ },
+ }),
+ getComponentData(
+ pageNode.children,
+ 'core.plugin',
+ ),
+ );
+ }
+ }
+ if (pageNode.type === 'switch') {
+ if (pageNode.renderMultipleMatches === 'all') {
+ for (const entityCase of pageNode.cases) {
+ traverse(
+ entityCase.children,
+ allFilters(parentFilter, entityCase.if),
+ );
+ }
+ } else {
+ let previousIf: EntityFilter = () => false;
+ for (const entityCase of pageNode.cases) {
+ const didNotMatchEarlier = invertFilter(previousIf);
+ traverse(
+ entityCase.children,
+ allFilters(parentFilter, entityCase.if, didNotMatchEarlier),
+ );
+ previousIf = anyFilters(previousIf, entityCase.if)!;
+ }
+ }
+ }
+ return;
+ }
+
+ React.Children.forEach(
+ (element.props as { children?: React.ReactNode })?.children,
+ child => {
+ traverse(child, parentFilter);
+ },
+ );
+ }
+
+ traverse(entityPageElement);
+}
+
+type EntityRoute = {
+ type: 'route';
+ path: string;
+ title: string;
+ if?: EntityFilter;
+ children: JSX.Element;
+};
+
+type EntitySwitchCase = {
+ if?: EntityFilter;
+ children: React.ReactNode;
+};
+
+type EntitySwitch = {
+ type: 'switch';
+ cases: EntitySwitchCase[];
+ renderMultipleMatches: 'first' | 'all';
+};
+
+function wrapAsyncEntityFilter(
+ asyncFilter?: AsyncEntityFilter,
+): EntityFilter | undefined {
+ if (!asyncFilter) {
+ return asyncFilter;
+ }
+ let loggedError = false;
+ return (entity, ctx) => {
+ const result = asyncFilter(entity, ctx);
+ if (result && typeof result === 'object' && 'then' in result) {
+ if (!loggedError) {
+ // eslint-disable-next-line no-console
+ console.error(
+ `collectEntityPageContents does not support async entity filters, skipping filter ${asyncFilter}`,
+ );
+ loggedError = true;
+ }
+ return false;
+ }
+ return result;
+ };
+}
+
+function maybeParseEntityPageNode(
+ element: React.JSX.Element,
+): EntityRoute | EntitySwitch | undefined {
+ if (getComponentData(element, ENTITY_ROUTE_KEY)) {
+ const props = element.props as EntityRoute;
+ return {
+ type: 'route',
+ path: props.path,
+ title: props.title,
+ if: props.if,
+ children: props.children,
+ };
+ }
+
+ const parentProps = element.props as {
+ children?: React.ReactNode;
+ renderMultipleMatches?: 'first' | 'all';
+ };
+
+ const children = React.Children.toArray(parentProps?.children);
+ if (!children.length) {
+ return undefined;
+ }
+
+ const cases = [];
+ for (const child of children) {
+ if (!getComponentData(child, ENTITY_SWITCH_KEY)) {
+ return undefined;
+ }
+ const props = (child as { props: EntitySwitchCase }).props;
+
+ cases.push({
+ if: wrapAsyncEntityFilter(props.if),
+ children: props.children,
+ });
+ }
+ return {
+ type: 'switch',
+ cases,
+ renderMultipleMatches: parentProps?.renderMultipleMatches ?? 'first',
+ };
+}
diff --git a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx
index ce0b055c00..2d64f88535 100644
--- a/packages/core-compat-api/src/collectLegacyRoutes.test.tsx
+++ b/packages/core-compat-api/src/collectLegacyRoutes.test.tsx
@@ -57,7 +57,7 @@ describe('collectLegacyRoutes', () => {
expect(
collected.map(p => ({
- id: p.id,
+ id: p.$$type === '@backstage/FrontendPlugin' ? p.id : p.pluginId,
extensions: OpaqueFrontendPlugin.toInternal(p).extensions.map(e => ({
id: e.id,
attachTo: e.attachTo,
@@ -177,7 +177,7 @@ describe('collectLegacyRoutes', () => {
expect(
collected.map(p => ({
- id: p.id,
+ id: p.$$type === '@backstage/FrontendPlugin' ? p.id : p.pluginId,
extensions: OpaqueFrontendPlugin.toInternal(p).extensions.map(e => ({
id: e.id,
attachTo: e.attachTo,
diff --git a/packages/core-compat-api/src/collectLegacyRoutes.tsx b/packages/core-compat-api/src/collectLegacyRoutes.tsx
index b0a55f9ddd..072e6b3bb2 100644
--- a/packages/core-compat-api/src/collectLegacyRoutes.tsx
+++ b/packages/core-compat-api/src/collectLegacyRoutes.tsx
@@ -30,6 +30,8 @@ import {
createFrontendPlugin,
ApiBlueprint,
PageBlueprint,
+ FrontendModule,
+ createFrontendModule,
} from '@backstage/frontend-plugin-api';
import React, { Children, ReactNode, isValidElement } from 'react';
import { Route, Routes } from 'react-router-dom';
@@ -38,6 +40,8 @@ import {
convertLegacyRouteRefs,
} from './convertLegacyRouteRef';
import { compatWrapper } from './compatWrapper';
+import { collectEntityPageContents } from './collectEntityPageContents';
+import { normalizeRoutePath } from './normalizeRoutePath';
/*
@@ -102,7 +106,7 @@ function makeRoutingShimExtension(options: {
});
}
-function visitRouteChildren(options: {
+export function visitRouteChildren(options: {
children: ReactNode;
parentExtensionId: string;
context: {
@@ -157,7 +161,10 @@ function visitRouteChildren(options: {
/** @internal */
export function collectLegacyRoutes(
flatRoutesElement: JSX.Element,
-): FrontendPlugin[] {
+ entityPage?: JSX.Element,
+): (FrontendPlugin | FrontendModule)[] {
+ const output = new Array();
+
const pluginExtensions = new Map<
LegacyBackstagePlugin,
ExtensionDefinition[]
@@ -230,7 +237,7 @@ export function collectLegacyRoutes(
factory(originalFactory, { inputs: _inputs }) {
// todo(blam): why do we not use the inputs here?
return originalFactory({
- defaultPath: path[0] === '/' ? path.slice(1) : path,
+ defaultPath: normalizeRoutePath(path),
routeRef: routeRef ? convertLegacyRouteRef(routeRef) : undefined,
loader: async () =>
compatWrapper(
@@ -262,20 +269,59 @@ export function collectLegacyRoutes(
},
);
- return Array.from(pluginExtensions).map(([plugin, extensions]) =>
- createFrontendPlugin({
- id: plugin.getId(),
- extensions: [
- ...extensions,
- ...Array.from(plugin.getApis()).map(factory =>
- ApiBlueprint.make({
- name: factory.api.id,
- params: { factory },
- }),
- ),
- ],
- routes: convertLegacyRouteRefs(plugin.routes ?? {}),
- externalRoutes: convertLegacyRouteRefs(plugin.externalRoutes ?? {}),
- }),
- );
+ if (entityPage) {
+ collectEntityPageContents(entityPage, {
+ discoverExtension(extension, plugin) {
+ if (!plugin || plugin.getId() === 'catalog') {
+ getPluginExtensions(orphanRoutesPlugin).push(extension);
+ } else {
+ getPluginExtensions(plugin).push(extension);
+ }
+ },
+ });
+
+ const extensions = new Array();
+ visitRouteChildren({
+ children: entityPage,
+ parentExtensionId: `page:catalog/entity`,
+ context: {
+ pluginId: 'catalog',
+ extensions,
+ getUniqueName,
+ discoverPlugin(plugin) {
+ if (plugin.getId() !== 'catalog') {
+ getPluginExtensions(plugin);
+ }
+ },
+ },
+ });
+
+ output.push(
+ createFrontendModule({
+ pluginId: 'catalog',
+ extensions,
+ }),
+ );
+ }
+
+ for (const [plugin, extensions] of pluginExtensions) {
+ output.push(
+ createFrontendPlugin({
+ id: plugin.getId(),
+ extensions: [
+ ...extensions,
+ ...Array.from(plugin.getApis()).map(factory =>
+ ApiBlueprint.make({
+ name: factory.api.id,
+ params: { factory },
+ }),
+ ),
+ ],
+ routes: convertLegacyRouteRefs(plugin.routes ?? {}),
+ externalRoutes: convertLegacyRouteRefs(plugin.externalRoutes ?? {}),
+ }),
+ );
+ }
+
+ return output;
}
diff --git a/packages/core-compat-api/src/convertLegacyApp.test.tsx b/packages/core-compat-api/src/convertLegacyApp.test.tsx
index 7153fcb4be..c532c6da1a 100644
--- a/packages/core-compat-api/src/convertLegacyApp.test.tsx
+++ b/packages/core-compat-api/src/convertLegacyApp.test.tsx
@@ -21,6 +21,16 @@ import { ScoreBoardPage } from '@oriflame/backstage-plugin-score-card';
import React, { ReactNode } from 'react';
import { Route } from 'react-router-dom';
import { convertLegacyApp } from './convertLegacyApp';
+import {
+ createApiFactory,
+ createComponentExtension,
+ createPlugin,
+} from '@backstage/core-plugin-api';
+import { EntityLayout, EntitySwitch, isKind } from '@backstage/plugin-catalog';
+import { renderInTestApp } from '@backstage/frontend-test-utils';
+import { default as catalogPlugin } from '@backstage/plugin-catalog/alpha';
+import { catalogApiRef } from '@backstage/plugin-catalog-react';
+import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils';
const Root = ({ children }: { children: ReactNode }) => <>{children}>;
@@ -159,4 +169,153 @@ describe('convertLegacyApp', () => {
}),
]);
});
+
+ it('should convert entity pages', async () => {
+ const fooPlugin = createPlugin({
+ id: 'foo',
+ });
+
+ const FooContent = fooPlugin.provide(
+ createComponentExtension({
+ name: 'FooContent',
+ component: { sync: () => foo content
},
+ }),
+ );
+ const OtherFooContent = fooPlugin.provide(
+ createComponentExtension({
+ name: 'OtherFooContent',
+ component: { sync: () => other foo content
},
+ }),
+ );
+
+ const simpleTestContent = (
+
+
+ overview content
+
+
+
+
+
+ bar content
+
+
+ );
+
+ const otherTestContent = (
+
+
+ other overview content
+
+
+
+
+
+ );
+
+ const entityPage = (
+
+
+ {simpleTestContent}
+
+ {otherTestContent}
+
+ );
+
+ const converted = convertLegacyApp(
+
+ test} />
+ ,
+ { entityPage },
+ );
+
+ const catalogOverride = catalogPlugin.withOverrides({
+ extensions: [
+ catalogPlugin.getExtension('api:catalog').override({
+ params: {
+ factory: createApiFactory(
+ catalogApiRef,
+ catalogApiMock({
+ entities: [
+ {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'test',
+ metadata: {
+ name: 'x',
+ },
+ spec: {},
+ },
+ {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'other',
+ metadata: {
+ name: 'x',
+ },
+ spec: {},
+ },
+ ],
+ }),
+ ),
+ },
+ }),
+ ],
+ });
+
+ // Overview
+ const renderOverviewTest = await renderInTestApp(, {
+ features: [catalogOverride, ...converted],
+ initialRouteEntries: ['/catalog/default/test/x'],
+ });
+ await expect(
+ renderOverviewTest.findByText('overview content'),
+ ).resolves.toBeInTheDocument();
+ renderOverviewTest.unmount();
+
+ const renderOverviewOther = await renderInTestApp(, {
+ features: [catalogOverride, ...converted],
+ initialRouteEntries: ['/catalog/default/other/x'],
+ });
+ await expect(
+ renderOverviewOther.findByText('other overview content'),
+ ).resolves.toBeInTheDocument();
+ renderOverviewOther.unmount();
+
+ // Foo tab
+ const renderFooTest = await renderInTestApp(, {
+ features: [catalogOverride, ...converted],
+ initialRouteEntries: ['/catalog/default/test/x/foo'],
+ });
+ await expect(
+ renderFooTest.findByText('foo content'),
+ ).resolves.toBeInTheDocument();
+ renderFooTest.unmount();
+
+ const renderFooOther = await renderInTestApp(, {
+ features: [catalogOverride, ...converted],
+ initialRouteEntries: ['/catalog/default/other/x/foo'],
+ });
+ await expect(
+ renderFooOther.findByText('other foo content'),
+ ).resolves.toBeInTheDocument();
+ renderFooOther.unmount();
+
+ // Bar tab
+ const renderBarTest = await renderInTestApp(, {
+ features: [catalogOverride, ...converted],
+ initialRouteEntries: ['/catalog/default/test/x/bar'],
+ });
+ await expect(
+ renderBarTest.findByText('bar content'),
+ ).resolves.toBeInTheDocument();
+ renderBarTest.unmount();
+
+ const renderBarOther = await renderInTestApp(, {
+ features: [catalogOverride, ...converted],
+ initialRouteEntries: ['/catalog/default/other/x/bar'],
+ });
+ await expect(
+ renderBarOther.findByText('other overview content'),
+ ).resolves.toBeInTheDocument(); // /bar does not exist, fall back to rendering overview
+ renderBarOther.unmount();
+ });
});
diff --git a/packages/core-compat-api/src/convertLegacyApp.ts b/packages/core-compat-api/src/convertLegacyApp.ts
index f098d090ed..2a62a6c1c6 100644
--- a/packages/core-compat-api/src/convertLegacyApp.ts
+++ b/packages/core-compat-api/src/convertLegacyApp.ts
@@ -26,7 +26,6 @@ import {
FrontendPlugin,
coreExtensionData,
createExtension,
- ExtensionOverrides,
createExtensionInput,
createFrontendModule,
} from '@backstage/frontend-plugin-api';
@@ -60,12 +59,35 @@ function selectChildren(
});
}
+/** @public */
+export interface ConvertLegacyAppOptions {
+ /**
+ * By providing an entity page element here it will be split up and converted
+ * into individual extensions for the catalog plugin in the new frontend
+ * system.
+ *
+ * In order to use this option the entity page and other catalog extensions
+ * must be removed from the legacy app, and the catalog plugin for the new
+ * frontend system must be installed instead.
+ *
+ * In order for this conversion to work the entity page must be a plain React
+ * element tree without any component wrapping anywhere between the root
+ * element and the `EntityLayout.Route` elements.
+ *
+ * When enabling this conversion you are likely to encounter duplicate entity
+ * page content provided both via the old structure and the new plugins. Any
+ * duplicate content needs to be removed from the old structure.
+ */
+ entityPage?: React.JSX.Element;
+}
+
/** @public */
export function convertLegacyApp(
rootElement: React.JSX.Element,
-): (FrontendPlugin | FrontendModule | ExtensionOverrides)[] {
+ options: ConvertLegacyAppOptions = {},
+): (FrontendPlugin | FrontendModule)[] {
if (getComponentData(rootElement, 'core.type') === 'FlatRoutes') {
- return collectLegacyRoutes(rootElement);
+ return collectLegacyRoutes(rootElement, options?.entityPage);
}
const appRouterEls = selectChildren(
@@ -137,7 +159,7 @@ export function convertLegacyApp(
disabled: true,
});
- const collectedRoutes = collectLegacyRoutes(routesEl);
+ const collectedRoutes = collectLegacyRoutes(routesEl, options?.entityPage);
return [
...collectedRoutes,
diff --git a/packages/core-compat-api/src/index.ts b/packages/core-compat-api/src/index.ts
index 6c2df56d92..b1168184f9 100644
--- a/packages/core-compat-api/src/index.ts
+++ b/packages/core-compat-api/src/index.ts
@@ -17,7 +17,10 @@ export * from './compatWrapper';
export * from './apis';
-export { convertLegacyApp } from './convertLegacyApp';
+export {
+ convertLegacyApp,
+ type ConvertLegacyAppOptions,
+} from './convertLegacyApp';
export { convertLegacyAppOptions } from './convertLegacyAppOptions';
export { convertLegacyPlugin } from './convertLegacyPlugin';
export { convertLegacyPageExtension } from './convertLegacyPageExtension';
diff --git a/packages/core-compat-api/src/normalizeRoutePath.test.ts b/packages/core-compat-api/src/normalizeRoutePath.test.ts
new file mode 100644
index 0000000000..9e76d92167
--- /dev/null
+++ b/packages/core-compat-api/src/normalizeRoutePath.test.ts
@@ -0,0 +1,36 @@
+/*
+ * 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 { normalizeRoutePath } from './normalizeRoutePath';
+
+describe('normalizeRoutePath', () => {
+ it('should normalize the path', () => {
+ expect(normalizeRoutePath('')).toBe('/');
+ expect(normalizeRoutePath('/')).toBe('/');
+ expect(normalizeRoutePath('/*')).toBe('/');
+ expect(normalizeRoutePath('/////')).toBe('/');
+ expect(normalizeRoutePath('*//*//*')).toBe('/');
+ expect(normalizeRoutePath('/foo')).toBe('/foo');
+ expect(normalizeRoutePath('/foo/')).toBe('/foo');
+ expect(normalizeRoutePath('/foo/*')).toBe('/foo');
+ expect(normalizeRoutePath('/foo/**')).toBe('/foo');
+ expect(normalizeRoutePath('/foo//**')).toBe('/foo');
+ expect(normalizeRoutePath('/foo/*/*')).toBe('/foo');
+ expect(normalizeRoutePath('//foo/*/*')).toBe('/foo');
+ expect(normalizeRoutePath('/foo/bar//*/*')).toBe('/foo/bar');
+ expect(normalizeRoutePath('/foo//bar//*/*')).toBe('/foo//bar');
+ });
+});
diff --git a/packages/core-compat-api/src/normalizeRoutePath.ts b/packages/core-compat-api/src/normalizeRoutePath.ts
new file mode 100644
index 0000000000..7d22966362
--- /dev/null
+++ b/packages/core-compat-api/src/normalizeRoutePath.ts
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+/**
+ * Normalizes the path to make sure it always starts with a single '/' and do not end with '/' or '*' unless empty
+ */
+export function normalizeRoutePath(path: string) {
+ let normalized = path;
+ while (normalized.endsWith('/') || normalized.endsWith('*')) {
+ normalized = normalized.slice(0, -1);
+ }
+ while (normalized.startsWith('/')) {
+ normalized = normalized.slice(1);
+ }
+ if (!normalized) {
+ return '/';
+ }
+ return `/${normalized}`;
+}
diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md
index 074f1cc922..934b71b8d4 100644
--- a/packages/core-components/CHANGELOG.md
+++ b/packages/core-components/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/core-components
+## 0.16.5-next.1
+
+### Patch Changes
+
+- 48aab13: Add i18n support for scaffolder-react plugin
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.6.4
+ - @backstage/version-bridge@1.0.11
+
## 0.16.5-next.0
### Patch Changes
diff --git a/packages/core-components/package.json b/packages/core-components/package.json
index 082f3f28c9..847156abee 100644
--- a/packages/core-components/package.json
+++ b/packages/core-components/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/core-components",
- "version": "0.16.5-next.0",
+ "version": "0.16.5-next.1",
"description": "Core components used by Backstage plugins and apps",
"backstage": {
"role": "web-library"
diff --git a/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx b/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx
index 8c18a1feee..f65db08393 100644
--- a/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx
+++ b/packages/core-components/src/components/DismissableBanner/DismissableBanner.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { ReactNode, useState, useEffect } from 'react';
+import React, { ReactNode, useMemo } from 'react';
import { useApi, storageApiRef } from '@backstage/core-plugin-api';
import useObservable from 'react-use/esm/useObservable';
import classNames from 'classnames';
@@ -102,23 +102,17 @@ export const DismissableBanner = (props: Props) => {
const classes = useStyles();
const storageApi = useApi(storageApiRef);
const notificationsStore = storageApi.forBucket('notifications');
- const rawDismissedBanners =
- notificationsStore.snapshot('dismissedBanners').value ?? [];
-
- const [dismissedBanners, setDismissedBanners] = useState(
- new Set(rawDismissedBanners),
- );
-
const observedItems = useObservable(
notificationsStore.observe$('dismissedBanners'),
+ notificationsStore.snapshot('dismissedBanners'),
);
- useEffect(() => {
- if (observedItems?.value) {
- const currentValue = observedItems?.value ?? [];
- setDismissedBanners(new Set(currentValue));
- }
- }, [observedItems?.value]);
+ const dismissedBanners = useMemo(
+ () => new Set(observedItems.value ?? []),
+ [observedItems.value],
+ );
+
+ const loadingSettings = observedItems.presence === 'unknown';
const handleClick = () => {
notificationsStore.set('dismissedBanners', [...dismissedBanners, id]);
@@ -131,7 +125,7 @@ export const DismissableBanner = (props: Props) => {
? { vertical: 'bottom', horizontal: 'center' }
: { vertical: 'top', horizontal: 'center' }
}
- open={!dismissedBanners.has(id)}
+ open={!loadingSettings && !dismissedBanners.has(id)}
classes={{
root: classNames(classes.root, !fixed && classes.topPosition),
}}
diff --git a/packages/core-components/src/components/Select/Select.test.tsx b/packages/core-components/src/components/Select/Select.test.tsx
index b83ac68d5b..505152db5f 100644
--- a/packages/core-components/src/components/Select/Select.test.tsx
+++ b/packages/core-components/src/components/Select/Select.test.tsx
@@ -93,4 +93,62 @@ describe('', () => {
const input = getByTestId('custom-select');
expect(input.textContent).toBe('All results');
});
+
+ it('should not open dropdown when deleting Chip component from Select', () => {
+ const items = [
+ { value: 'test_1', label: 'test 1' },
+ { value: 'test_2', label: 'test 2' },
+ ];
+
+ const handleChange = jest.fn();
+
+ const { getByTestId, queryByText } = render(
+ ,
+ );
+
+ // Verify Chip component exist
+ const chip = getByTestId('chip');
+ expect(chip).toBeInTheDocument();
+ expect(chip.textContent).toContain('test 1');
+
+ // Find cancel icon
+ const cancelIcon = getByTestId('cancel-icon');
+ expect(cancelIcon).toBeInTheDocument();
+
+ // Verify dropdown is initially closed
+ expect(queryByText('test 2')).not.toBeInTheDocument();
+
+ // Fire mouseDown on Chip's CancelIcon that tests if onMouseDown={event => event.stopPropagation()} is working properly
+ fireEvent.mouseDown(cancelIcon);
+
+ // Verify dropdown is closed after mouseDown on Chip's CancelIcon
+ expect(queryByText('test 2')).not.toBeInTheDocument();
+
+ // Delete the Chip
+ fireEvent.click(cancelIcon);
+
+ // Verify dropdown is still closed after the removal of Chip
+ expect(queryByText('test 2')).not.toBeInTheDocument();
+
+ // Verify Chip is removed
+ expect(chip).not.toBeInTheDocument();
+ expect(queryByText('test 1')).not.toBeInTheDocument();
+
+ // Verify we can still open the dropdown with a click on the select
+ const selectInput = getByTestId('select');
+ expect(selectInput.textContent).toBe('All results');
+
+ // Simulate click on select
+ fireEvent.mouseDown(within(selectInput).getByRole('button'));
+
+ // Now dropdown should be open
+ expect(queryByText('test 2')).toBeInTheDocument();
+ });
});
diff --git a/packages/core-components/src/components/Select/Select.tsx b/packages/core-components/src/components/Select/Select.tsx
index 6b5174820b..7a862678b8 100644
--- a/packages/core-components/src/components/Select/Select.tsx
+++ b/packages/core-components/src/components/Select/Select.tsx
@@ -28,6 +28,7 @@ import {
withStyles,
} from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
+import CancelIcon from '@material-ui/icons/Cancel';
import React, { useEffect, useState } from 'react';
import ClosedDropdown from './static/ClosedDropdown';
@@ -222,9 +223,16 @@ export function SelectComponent(props: SelectProps) {
const item = items.find(el => el.value === selectedValue);
return item ? (
event.stopPropagation()}
+ />
+ }
onDelete={handleDelete(selectedValue)}
className={classes.chip}
/>
diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx
index 73baff8db2..e4093f07d2 100644
--- a/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx
+++ b/packages/core-components/src/components/SimpleStepper/SimpleStepper.test.tsx
@@ -173,4 +173,32 @@ describe('Stepper', () => {
fireEvent.click(getTextInSlide(rendered, 2)('Back') as Node);
expect(rendered.getByText('step1')).toBeInTheDocument();
});
+
+ it('Handles onBack action properly when activeStep is higher than 0', async () => {
+ const rendered = await renderInTestApp(
+
+
+ step0
+
+
+ step1
+
+
+ step2
+
+ ,
+ );
+
+ fireEvent.click(getTextInSlide(rendered, 2)('Back') as Node);
+ expect(rendered.getByText('step1')).toBeInTheDocument();
+
+ fireEvent.click(getTextInSlide(rendered, 1)('Back') as Node);
+ expect(rendered.getByText('step0')).toBeInTheDocument();
+
+ fireEvent.click(getTextInSlide(rendered, 0)('Next') as Node);
+ expect(rendered.getByText('step1')).toBeInTheDocument();
+
+ fireEvent.click(getTextInSlide(rendered, 1)('Next') as Node);
+ expect(rendered.getByText('step2')).toBeInTheDocument();
+ });
});
diff --git a/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx b/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx
index 4a1a4ab919..b0b6c1afd0 100644
--- a/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx
+++ b/packages/core-components/src/components/SimpleStepper/SimpleStepper.tsx
@@ -32,6 +32,7 @@ type InternalState = {
};
const noop = () => {};
+
export const VerticalStepperContext = React.createContext({
stepperLength: 0,
stepIndex: 0,
@@ -50,7 +51,17 @@ export interface StepperProps {
export function SimpleStepper(props: PropsWithChildren) {
const { children, elevated, onStepChange, activeStep = 0 } = props;
const [stepIndex, setStepIndex] = useState(activeStep);
- const [stepHistory, setStepHistory] = useState([0]);
+ /*
+ Recreates the stepHistory array based on the activeStep
+ to make sure the handleBack function of the Footer works when activeStep is higher than 0
+ */
+ const inOrderRecreatedStepHistory = Array.from(
+ { length: activeStep + 1 },
+ (_, i) => i,
+ );
+ const [stepHistory, setStepHistory] = useState(
+ inOrderRecreatedStepHistory,
+ );
useEffect(() => {
setStepIndex(activeStep);
diff --git a/packages/core-plugin-api/src/extensions/extensions.test.tsx b/packages/core-plugin-api/src/extensions/extensions.test.tsx
index 3eca133608..25d29fc392 100644
--- a/packages/core-plugin-api/src/extensions/extensions.test.tsx
+++ b/packages/core-plugin-api/src/extensions/extensions.test.tsx
@@ -15,7 +15,7 @@
*/
import { withLogCollector } from '@backstage/test-utils';
-import { render, screen } from '@testing-library/react';
+import { act, render, screen } from '@testing-library/react';
import React from 'react';
import { useAnalyticsContext } from '../analytics/AnalyticsContext';
import { useApp, ErrorBoundaryFallbackProps } from '../app';
@@ -27,6 +27,7 @@ import {
createReactExtension,
createRoutableExtension,
} from './extensions';
+import { ForwardedError } from '@backstage/errors';
jest.mock('../app');
@@ -120,6 +121,48 @@ describe('extensions', () => {
expect(errors[0]).toMatchObject({ detail: new Error('Test error') });
});
+ it('should handle failed lazy loads', async () => {
+ const BrokenComponent = plugin.provide(
+ createComponentExtension({
+ name: 'BrokenComponent',
+ component: {
+ lazy: async () => {
+ if (true as boolean) {
+ throw new Error('Test error');
+ }
+ return () => ;
+ },
+ },
+ }),
+ );
+
+ mocked(useApp).mockReturnValue({
+ getComponents: () => ({
+ Progress: () => null,
+ ErrorBoundaryFallback: (props: ErrorBoundaryFallbackProps) => (
+ <>
+ Error in {props.plugin?.getId()}: {String(props.error)}
+ >
+ ),
+ }),
+ });
+
+ const { error: errors } = await withLogCollector(['error'], async () => {
+ await act(async () => {
+ render();
+ });
+ });
+ screen.getByText(
+ 'Error in my-plugin: Error: Failed lazy loading of the BrokenComponent extension, try to reload the page; caused by Error: Test error',
+ );
+ expect(errors[0]).toMatchObject({
+ detail: new ForwardedError(
+ 'Failed lazy loading of the BrokenComponent extension, try to reload the page',
+ new Error('Test error'),
+ ),
+ });
+ });
+
it('should wrap extended component with analytics context', async () => {
const AnalyticsSpyExtension = plugin.provide(
createReactExtension({
diff --git a/packages/core-plugin-api/src/extensions/extensions.tsx b/packages/core-plugin-api/src/extensions/extensions.tsx
index b94354fa2c..51af857d4b 100644
--- a/packages/core-plugin-api/src/extensions/extensions.tsx
+++ b/packages/core-plugin-api/src/extensions/extensions.tsx
@@ -22,6 +22,7 @@ import { attachComponentData } from './componentData';
import { Extension, BackstagePlugin } from '../plugin';
import { PluginErrorBoundary } from './PluginErrorBoundary';
import { routableExtensionRenderedEvent } from '../analytics/Tracker';
+import { ForwardedError } from '@backstage/errors';
/**
* Lazy or synchronous retrieving of extension components.
@@ -79,62 +80,51 @@ export function createRoutableExtension<
return createReactExtension({
component: {
lazy: () =>
- component().then(
- InnerComponent => {
- const RoutableExtensionWrapper: any = (props: any) => {
- const analytics = useAnalytics();
+ component().then(InnerComponent => {
+ const RoutableExtensionWrapper: any = (props: any) => {
+ const analytics = useAnalytics();
- // Validate that the routing is wired up correctly in the App.tsx
- try {
- useRouteRef(mountPoint);
- } catch (error) {
- if (typeof error === 'object' && error !== null) {
- const { message } = error as { message?: unknown };
- if (
- typeof message === 'string' &&
- message.startsWith('No path for ')
- ) {
- throw new Error(
- `Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` +
- 'Routable extension components may not be rendered by other components and must be ' +
- 'directly available as an element within the App provider component.',
- );
- }
+ // Validate that the routing is wired up correctly in the App.tsx
+ try {
+ useRouteRef(mountPoint);
+ } catch (error) {
+ if (typeof error === 'object' && error !== null) {
+ const { message } = error as { message?: unknown };
+ if (
+ typeof message === 'string' &&
+ message.startsWith('No path for ')
+ ) {
+ throw new Error(
+ `Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` +
+ 'Routable extension components may not be rendered by other components and must be ' +
+ 'directly available as an element within the App provider component.',
+ );
}
- throw error;
}
+ throw error;
+ }
- // This event, never exposed to end-users of the analytics API,
- // helps inform which extension metadata gets associated with a
- // navigation event when the route navigated to is a gathered
- // mountpoint.
- useEffect(() => {
- analytics.captureEvent(routableExtensionRenderedEvent, '');
- }, [analytics]);
+ // This event, never exposed to end-users of the analytics API,
+ // helps inform which extension metadata gets associated with a
+ // navigation event when the route navigated to is a gathered
+ // mountpoint.
+ useEffect(() => {
+ analytics.captureEvent(routableExtensionRenderedEvent, '');
+ }, [analytics]);
- return ;
- };
+ return ;
+ };
- const componentName =
- name ||
- (InnerComponent as { displayName?: string }).displayName ||
- InnerComponent.name ||
- 'LazyComponent';
+ const componentName =
+ name ||
+ (InnerComponent as { displayName?: string }).displayName ||
+ InnerComponent.name ||
+ 'LazyComponent';
- RoutableExtensionWrapper.displayName = `RoutableExtension(${componentName})`;
+ RoutableExtensionWrapper.displayName = `RoutableExtension(${componentName})`;
- return RoutableExtensionWrapper as T;
- },
- error => {
- const RoutableExtensionWrapper: any = (_: any) => {
- const app = useApp();
- const { BootErrorPage } = app.getComponents();
-
- return ;
- };
- return RoutableExtensionWrapper;
- },
- ),
+ return RoutableExtensionWrapper as T;
+ }),
},
data: {
'core.mountPoint': mountPoint,
@@ -225,7 +215,16 @@ export function createReactExtension<
if ('lazy' in options.component) {
const lazyLoader = options.component.lazy;
Component = lazy(() =>
- lazyLoader().then(component => ({ default: component })),
+ lazyLoader().then(
+ component => ({ default: component }),
+ error => {
+ const ofExtension = name ? ` of the ${name} extension` : '';
+ throw new ForwardedError(
+ `Failed lazy loading${ofExtension}, try to reload the page`,
+ error,
+ );
+ },
+ ),
) as unknown as T;
} else {
Component = options.component.sync;
diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md
index a4da0a691e..4aca44a2df 100644
--- a/packages/create-app/CHANGELOG.md
+++ b/packages/create-app/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/create-app
+## 0.6.0-next.2
+
+### Minor Changes
+
+- 31731b0: Upgraded the TypeScript version in the template to `5.8`.
+
+### Patch Changes
+
+- 19e5c3f: Added link to multi-stage Dockerfile documentation as alternative option
+- Updated dependencies
+ - @backstage/cli-common@0.1.15
+
## 0.5.26-next.1
### Patch Changes
diff --git a/packages/create-app/package.json b/packages/create-app/package.json
index dffe59a294..22f095a823 100644
--- a/packages/create-app/package.json
+++ b/packages/create-app/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/create-app",
- "version": "0.5.26-next.1",
+ "version": "0.6.0-next.2",
"description": "A CLI that helps you create your own Backstage app",
"backstage": {
"role": "cli"
diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md
index fe1b461dbb..dc76465444 100644
--- a/packages/dev-utils/CHANGELOG.md
+++ b/packages/dev-utils/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/dev-utils
+## 1.1.8-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-app-api@1.16.0-next.0
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/app-defaults@1.6.0-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/integration-react@1.2.5-next.0
+ - @backstage/theme@0.6.4
+
## 1.1.8-next.1
### Patch Changes
diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json
index 41af496310..7d32d022e7 100644
--- a/packages/dev-utils/package.json
+++ b/packages/dev-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/dev-utils",
- "version": "1.1.8-next.1",
+ "version": "1.1.8-next.2",
"description": "Utilities for developing Backstage plugins.",
"backstage": {
"role": "web-library"
diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md
index 49f554762b..72656ea47e 100644
--- a/packages/e2e-test/CHANGELOG.md
+++ b/packages/e2e-test/CHANGELOG.md
@@ -1,5 +1,14 @@
# e2e-test
+## 0.2.26-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/create-app@0.6.0-next.2
+ - @backstage/cli-common@0.1.15
+ - @backstage/errors@1.2.7
+
## 0.2.26-next.1
### Patch Changes
diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json
index f0390de9f9..32b34bf482 100644
--- a/packages/e2e-test/package.json
+++ b/packages/e2e-test/package.json
@@ -1,6 +1,6 @@
{
"name": "e2e-test",
- "version": "0.2.26-next.1",
+ "version": "0.2.26-next.2",
"description": "E2E test for verifying Backstage packages",
"backstage": {
"role": "cli"
diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md
index 83af105777..b25e5cd722 100644
--- a/packages/frontend-app-api/CHANGELOG.md
+++ b/packages/frontend-app-api/CHANGELOG.md
@@ -1,5 +1,32 @@
# @backstage/frontend-app-api
+## 0.11.0-next.2
+
+### Minor Changes
+
+- abcdf44: **BREAKING**: The returned object from `createSpecializedApp` no longer contains a `createRoot()` method, and it instead now contains `apis` and `tree`.
+
+ You can replace existing usage of `app.createRoot()` with the following:
+
+ ```ts
+ const root = tree.root.instance?.getData(coreExtensionData.reactElement);
+ ```
+
+- 8250ffe: **BREAKING**: Dropped support for the removed opaque `@backstage/ExtensionOverrides` and `@backstage/BackstagePlugin` types.
+
+### Patch Changes
+
+- 4d18b55: It's now possible to provide a middleware that wraps all extension factories by passing an `extensionFactoryMiddleware` to either `createApp()` or `createSpecializedApp()`.
+- Updated dependencies
+ - @backstage/frontend-defaults@0.2.0-next.2
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/core-app-api@1.16.0-next.0
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
## 0.10.6-next.1
### Patch Changes
diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json
index 2ee763d7ef..2c280774e4 100644
--- a/packages/frontend-app-api/package.json
+++ b/packages/frontend-app-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-app-api",
- "version": "0.10.6-next.1",
+ "version": "0.11.0-next.2",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-app-api/report.api.md b/packages/frontend-app-api/report.api.md
index d3c2045f85..f24d662df4 100644
--- a/packages/frontend-app-api/report.api.md
+++ b/packages/frontend-app-api/report.api.md
@@ -8,8 +8,7 @@ import { AppTree } from '@backstage/frontend-plugin-api';
import { ConfigApi } from '@backstage/core-plugin-api';
import { ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api';
import { ExternalRouteRef } from '@backstage/frontend-plugin-api';
-import { FrontendModule } from '@backstage/frontend-plugin-api';
-import { FrontendPlugin } from '@backstage/frontend-plugin-api';
+import { FrontendFeature as FrontendFeature_2 } from '@backstage/frontend-plugin-api';
import { RouteRef } from '@backstage/frontend-plugin-api';
import { SubRouteRef } from '@backstage/frontend-plugin-api';
@@ -40,14 +39,6 @@ export function createSpecializedApp(options?: {
tree: AppTree;
};
-// @public (undocumented)
-export type FrontendFeature =
- | FrontendPlugin
- | FrontendModule
- | {
- $$type: '@backstage/ExtensionOverrides';
- }
- | {
- $$type: '@backstage/BackstagePlugin';
- };
+// @public @deprecated (undocumented)
+export type FrontendFeature = FrontendFeature_2;
```
diff --git a/packages/frontend-app-api/src/wiring/types.ts b/packages/frontend-app-api/src/wiring/types.ts
index 18d9c51d0e..2bf08f327f 100644
--- a/packages/frontend-app-api/src/wiring/types.ts
+++ b/packages/frontend-app-api/src/wiring/types.ts
@@ -14,16 +14,13 @@
* limitations under the License.
*/
import { RouteRef } from '@backstage/frontend-plugin-api';
-import { FrontendModule, FrontendPlugin } from '@backstage/frontend-plugin-api';
+import { FrontendFeature as PluginApiFrontendFeature } from '@backstage/frontend-plugin-api';
import { BackstageRouteObject } from '../routing/types';
-/** @public */
-export type FrontendFeature =
- | FrontendPlugin
- | FrontendModule
- // TODO(blam): This is just forwards backwards compatibility, remove after v1.31.0
- | { $$type: '@backstage/ExtensionOverrides' }
- | { $$type: '@backstage/BackstagePlugin' };
+/** @public
+ * @deprecated Use {@link @backstage/frontend-plugin-api#FrontendFeature} instead.
+ */
+export type FrontendFeature = PluginApiFrontendFeature;
/** @internal */
export type RouteInfo = {
diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md
index 8b385dd639..da18fe1155 100644
--- a/packages/frontend-defaults/CHANGELOG.md
+++ b/packages/frontend-defaults/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/frontend-defaults
+## 0.2.0-next.2
+
+### Minor Changes
+
+- 8250ffe: **BREAKING**: Dropped support for the removed opaque `@backstage/ExtensionOverrides` and `@backstage/BackstagePlugin` types.
+
+### Patch Changes
+
+- 4d18b55: It's now possible to provide a middleware that wraps all extension factories by passing an `extensionFactoryMiddleware` to either `createApp()` or `createSpecializedApp()`.
+- abcdf44: Internal refactor to match updated `createSpecializedApp`.
+- e3f19db: Feature discovery and resolution logic used in `createApp` is now exposed via the `discoverAvailableFeatures` and `resolveAsyncFeatures` functions respectively.
+- Updated dependencies
+ - @backstage/frontend-app-api@0.11.0-next.2
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/plugin-app@0.1.7-next.2
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.1.7-next.1
### Patch Changes
diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json
index 80b241cc47..b04c655746 100644
--- a/packages/frontend-defaults/package.json
+++ b/packages/frontend-defaults/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-defaults",
- "version": "0.1.7-next.1",
+ "version": "0.2.0-next.2",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-defaults/report.api.md b/packages/frontend-defaults/report.api.md
index ac115566b8..8bfe2ee017 100644
--- a/packages/frontend-defaults/report.api.md
+++ b/packages/frontend-defaults/report.api.md
@@ -7,7 +7,8 @@ import { Config } from '@backstage/config';
import { ConfigApi } from '@backstage/frontend-plugin-api';
import { CreateAppRouteBinder } from '@backstage/frontend-app-api';
import { ExtensionFactoryMiddleware } from '@backstage/frontend-plugin-api';
-import { FrontendFeature } from '@backstage/frontend-app-api';
+import { FrontendFeature } from '@backstage/frontend-plugin-api';
+import { FrontendFeatureLoader } from '@backstage/frontend-plugin-api';
import { JSX as JSX_2 } from 'react';
import { default as React_2 } from 'react';
import { ReactNode } from 'react';
@@ -17,7 +18,7 @@ export function createApp(options?: CreateAppOptions): {
createRoot(): JSX_2.Element;
};
-// @public
+// @public @deprecated
export interface CreateAppFeatureLoader {
getLoaderName(): string;
load(options: { config: ConfigApi }): Promise<{
@@ -38,7 +39,11 @@ export interface CreateAppOptions {
| ExtensionFactoryMiddleware
| ExtensionFactoryMiddleware[];
// (undocumented)
- features?: (FrontendFeature | CreateAppFeatureLoader)[];
+ features?: (
+ | FrontendFeature
+ | FrontendFeatureLoader
+ | CreateAppFeatureLoader
+ )[];
loadingComponent?: ReactNode;
}
@@ -49,13 +54,17 @@ export function createPublicSignInApp(options?: CreateAppOptions): {
// @public (undocumented)
export function discoverAvailableFeatures(config: Config): {
- features: FrontendFeature[];
+ features: (FrontendFeature | FrontendFeatureLoader)[];
};
// @public (undocumented)
export function resolveAsyncFeatures(options: {
config: Config;
- features?: (FrontendFeature | CreateAppFeatureLoader)[];
+ features?: (
+ | FrontendFeature
+ | FrontendFeatureLoader
+ | CreateAppFeatureLoader
+ )[];
}): Promise<{
features: FrontendFeature[];
}>;
diff --git a/packages/frontend-defaults/src/createApp.tsx b/packages/frontend-defaults/src/createApp.tsx
index 69278d33ed..c6ed4759fa 100644
--- a/packages/frontend-defaults/src/createApp.tsx
+++ b/packages/frontend-defaults/src/createApp.tsx
@@ -19,6 +19,8 @@ import {
ConfigApi,
coreExtensionData,
ExtensionFactoryMiddleware,
+ FrontendFeature,
+ FrontendFeatureLoader,
} from '@backstage/frontend-plugin-api';
// eslint-disable-next-line @backstage/no-relative-monorepo-imports
import { defaultConfigLoaderSync } from '../../core-app-api/src/app/defaultConfigLoader';
@@ -27,7 +29,6 @@ import { overrideBaseUrlConfigs } from '../../core-app-api/src/app/overrideBaseU
import { ConfigReader } from '@backstage/config';
import {
CreateAppRouteBinder,
- FrontendFeature,
createSpecializedApp,
} from '@backstage/frontend-app-api';
import appPlugin from '@backstage/plugin-app';
@@ -38,6 +39,7 @@ import { resolveAsyncFeatures } from './resolution';
* A source of dynamically loaded frontend features.
*
* @public
+ * @deprecated Use the {@link @backstage/frontend-plugin-api#createFrontendFeatureLoader} function instead.
*/
export interface CreateAppFeatureLoader {
/**
@@ -59,7 +61,11 @@ export interface CreateAppFeatureLoader {
* @public
*/
export interface CreateAppOptions {
- features?: (FrontendFeature | CreateAppFeatureLoader)[];
+ features?: (
+ | FrontendFeature
+ | FrontendFeatureLoader
+ | CreateAppFeatureLoader
+ )[];
configLoader?: () => Promise<{ config: ConfigApi }>;
bindRoutes?(context: { bind: CreateAppRouteBinder }): void;
/**
@@ -94,15 +100,16 @@ export function createApp(options?: CreateAppOptions): {
overrideBaseUrlConfigs(defaultConfigLoaderSync()),
);
- const { features: discoveredFeatures } = discoverAvailableFeatures(config);
- const { features: providedFeatures } = await resolveAsyncFeatures({
+ const { features: discoveredFeaturesAndLoaders } =
+ discoverAvailableFeatures(config);
+ const { features: loadedFeatures } = await resolveAsyncFeatures({
config,
- features: options?.features,
+ features: [...discoveredFeaturesAndLoaders, ...(options?.features ?? [])],
});
const app = createSpecializedApp({
config,
- features: [appPlugin, ...discoveredFeatures, ...providedFeatures],
+ features: [appPlugin, ...loadedFeatures],
bindRoutes: options?.bindRoutes,
extensionFactoryMiddleware: options?.extensionFactoryMiddleware,
});
diff --git a/packages/frontend-defaults/src/discovery.test.ts b/packages/frontend-defaults/src/discovery.test.ts
index 80be0b25c5..52a2fcde11 100644
--- a/packages/frontend-defaults/src/discovery.test.ts
+++ b/packages/frontend-defaults/src/discovery.test.ts
@@ -14,7 +14,10 @@
* limitations under the License.
*/
-import { createFrontendPlugin } from '@backstage/frontend-plugin-api';
+import {
+ createFrontendFeatureLoader,
+ createFrontendPlugin,
+} from '@backstage/frontend-plugin-api';
import { discoverAvailableFeatures } from './discovery';
import { ConfigReader } from '@backstage/config';
@@ -51,6 +54,20 @@ describe('discoverAvailableFeatures', () => {
});
});
+ it('should discover a frontend feature loader', () => {
+ const testLoader = createFrontendFeatureLoader({
+ loader() {
+ return [];
+ },
+ });
+ globalSpy.mockReturnValue({
+ modules: [{ default: testLoader }],
+ });
+ expect(discoverAvailableFeatures(config)).toEqual({
+ features: [testLoader],
+ });
+ });
+
it('should ignore garbage', () => {
globalSpy.mockReturnValueOnce({ modules: [{ default: null }] });
expect(discoverAvailableFeatures(config)).toEqual({ features: [] });
diff --git a/packages/frontend-defaults/src/discovery.ts b/packages/frontend-defaults/src/discovery.ts
index e82f2acfda..6da6146389 100644
--- a/packages/frontend-defaults/src/discovery.ts
+++ b/packages/frontend-defaults/src/discovery.ts
@@ -15,7 +15,11 @@
*/
import { Config, ConfigReader } from '@backstage/config';
-import { FrontendFeature } from '@backstage/frontend-app-api';
+import {
+ FrontendFeature,
+ FrontendFeatureLoader,
+} from '@backstage/frontend-plugin-api';
+import { isBackstageFeatureLoader } from './resolution';
interface DiscoveryGlobal {
modules: Array<{ name: string; export?: string; default: unknown }>;
@@ -56,7 +60,7 @@ function readPackageDetectionConfig(config: Config) {
* @public
*/
export function discoverAvailableFeatures(config: Config): {
- features: FrontendFeature[];
+ features: (FrontendFeature | FrontendFeatureLoader)[];
} {
const discovered = (
window as { '__@backstage/discovered__'?: DiscoveryGlobal }
@@ -80,7 +84,7 @@ export function discoverAvailableFeatures(config: Config): {
return true;
})
.map(m => m.default)
- .filter(isBackstageFeature) ?? [],
+ .filter(isFeatureOrLoader) ?? [],
};
}
@@ -88,12 +92,14 @@ function isBackstageFeature(obj: unknown): obj is FrontendFeature {
if (obj !== null && typeof obj === 'object' && '$$type' in obj) {
return (
obj.$$type === '@backstage/FrontendPlugin' ||
- obj.$$type === '@backstage/FrontendModule' ||
- // TODO: Remove this once the old plugin type and extension overrides
- // are no longer supported
- obj.$$type === '@backstage/BackstagePlugin' ||
- obj.$$type === '@backstage/ExtensionOverrides'
+ obj.$$type === '@backstage/FrontendModule'
);
}
return false;
}
+
+function isFeatureOrLoader(
+ obj: unknown,
+): obj is FrontendFeature | FrontendFeatureLoader {
+ return isBackstageFeature(obj) || isBackstageFeatureLoader(obj);
+}
diff --git a/packages/frontend-defaults/src/resolution.test.ts b/packages/frontend-defaults/src/resolution.test.ts
index d833716666..609b975a33 100644
--- a/packages/frontend-defaults/src/resolution.test.ts
+++ b/packages/frontend-defaults/src/resolution.test.ts
@@ -15,7 +15,9 @@
*/
import {
+ createFrontendFeatureLoader,
createFrontendPlugin,
+ FrontendFeatureLoader,
PageBlueprint,
} from '@backstage/frontend-plugin-api';
import { CreateAppFeatureLoader } from './createApp';
@@ -69,7 +71,7 @@ describe('resolveAsyncFeatures', () => {
]);
});
- it('supports feature loaders', async () => {
+ it('supports deprecated feature loaders', async () => {
const loader: CreateAppFeatureLoader = {
getLoaderName() {
return 'test-loader';
@@ -118,7 +120,7 @@ describe('resolveAsyncFeatures', () => {
]);
});
- it('should propagate errors thrown by feature loaders', async () => {
+ it('should propagate errors thrown by deprecated feature loaders', async () => {
const loader: CreateAppFeatureLoader = {
getLoaderName() {
return 'test-loader';
@@ -137,4 +139,65 @@ describe('resolveAsyncFeatures', () => {
`"Failed to read frontend features from loader 'test-loader', TypeError: boom"`,
);
});
+
+ it('supports feature loaders', async () => {
+ const loader: FrontendFeatureLoader = createFrontendFeatureLoader({
+ async loader({ config: _ }) {
+ return [
+ createFrontendPlugin({
+ id: 'test',
+ extensions: [
+ PageBlueprint.make({
+ params: {
+ defaultPath: '/',
+ loader: () => new Promise(() => {}),
+ },
+ }),
+ ],
+ }),
+ ];
+ },
+ });
+
+ const { features } = await resolveAsyncFeatures({
+ config: mockApis.config(),
+ features: [loader],
+ });
+
+ expect(features).toMatchObject([
+ {
+ $$type: '@backstage/FrontendPlugin',
+ id: 'test',
+ version: 'v1',
+ extensions: [
+ {
+ $$type: '@backstage/Extension',
+ id: 'page:test',
+ version: 'v2',
+ attachTo: {
+ id: 'app/routes',
+ input: 'routes',
+ },
+ },
+ ],
+ },
+ ]);
+ });
+
+ it('should propagate errors thrown by feature loaders', async () => {
+ const loader: FrontendFeatureLoader = createFrontendFeatureLoader({
+ async loader({ config: _ }) {
+ throw new TypeError('boom');
+ },
+ });
+
+ await expect(() =>
+ resolveAsyncFeatures({
+ config: mockApis.config(),
+ features: [loader],
+ }),
+ ).rejects.toThrow(
+ /^Failed to read frontend features from loader created at .*: TypeError: boom$/,
+ );
+ });
});
diff --git a/packages/frontend-defaults/src/resolution.ts b/packages/frontend-defaults/src/resolution.ts
index e51ecbc2d3..6ab26c3d1d 100644
--- a/packages/frontend-defaults/src/resolution.ts
+++ b/packages/frontend-defaults/src/resolution.ts
@@ -16,30 +16,98 @@
import { Config } from '@backstage/config';
import { stringifyError } from '@backstage/errors';
-import { FrontendFeature } from '@backstage/frontend-app-api';
+import {
+ FrontendFeature,
+ FrontendFeatureLoader,
+} from '@backstage/frontend-plugin-api';
import { CreateAppFeatureLoader } from './createApp';
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+import { isInternalFrontendFeatureLoader } from '../../frontend-plugin-api/src/wiring/createFrontendFeatureLoader';
/** @public */
export async function resolveAsyncFeatures(options: {
config: Config;
- features?: (FrontendFeature | CreateAppFeatureLoader)[];
+ features?: (
+ | FrontendFeature
+ | FrontendFeatureLoader
+ | CreateAppFeatureLoader
+ )[];
}): Promise<{ features: FrontendFeature[] }> {
- const features = [];
- for (const entry of options.features ?? []) {
- if ('load' in entry) {
+ const features: (FrontendFeature | FrontendFeatureLoader)[] = [];
+
+ // Separate deprecated CreateAppFeatureLoader elements from the frontend features,
+ // and manage the deprecated elements first.
+ for (const item of options?.features ?? []) {
+ if ('load' in item) {
try {
- const result = await entry.load({ config: options.config });
+ const result = await item.load({ config: options.config });
features.push(...result.features);
} catch (e) {
throw new Error(
- `Failed to read frontend features from loader '${entry.getLoaderName()}', ${stringifyError(
+ `Failed to read frontend features from loader '${item.getLoaderName()}', ${stringifyError(
e,
)}`,
);
}
} else {
- features.push(entry);
+ features.push(item);
}
}
- return { features };
+
+ const loadedFeatures: FrontendFeature[] = [];
+ const alreadyMetFeatureLoaders: FrontendFeatureLoader[] = [];
+ const maxRecursionDepth = 5;
+
+ async function applyFeatureLoaders(
+ featuresOrLoaders: (FrontendFeature | FrontendFeatureLoader)[],
+ recursionDepth: number,
+ ) {
+ if (featuresOrLoaders.length === 0) {
+ return;
+ }
+
+ for (const featureOrLoader of featuresOrLoaders) {
+ if (isBackstageFeatureLoader(featureOrLoader)) {
+ if (alreadyMetFeatureLoaders.some(l => l === featureOrLoader)) {
+ continue;
+ }
+ if (isInternalFrontendFeatureLoader(featureOrLoader)) {
+ if (recursionDepth > maxRecursionDepth) {
+ throw new Error(
+ `Maximum feature loading recursion depth (${maxRecursionDepth}) reached for the feature loader ${featureOrLoader.description}`,
+ );
+ }
+ alreadyMetFeatureLoaders.push(featureOrLoader);
+ let result: (FrontendFeature | FrontendFeatureLoader)[];
+ try {
+ result = await featureOrLoader.loader({ config: options.config });
+ } catch (e) {
+ throw new Error(
+ `Failed to read frontend features from loader ${
+ featureOrLoader.description
+ }: ${stringifyError(e)}`,
+ );
+ }
+ await applyFeatureLoaders(result, recursionDepth + 1);
+ }
+ } else {
+ loadedFeatures.push(featureOrLoader);
+ }
+ }
+ }
+
+ await applyFeatureLoaders(features, 1);
+
+ return { features: loadedFeatures };
+}
+
+export function isBackstageFeatureLoader(
+ obj: unknown,
+): obj is FrontendFeatureLoader {
+ return (
+ obj !== null &&
+ typeof obj === 'object' &&
+ '$$type' in obj &&
+ obj.$$type === '@backstage/FrontendFeatureLoader'
+ );
}
diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md
index 1a737ee40f..04ec065aab 100644
--- a/packages/frontend-internal/CHANGELOG.md
+++ b/packages/frontend-internal/CHANGELOG.md
@@ -1,5 +1,14 @@
# @internal/frontend
+## 0.0.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
## 0.0.7-next.1
### Patch Changes
diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json
index b5524a6cc2..3aa48e03d0 100644
--- a/packages/frontend-internal/package.json
+++ b/packages/frontend-internal/package.json
@@ -1,6 +1,6 @@
{
"name": "@internal/frontend",
- "version": "0.0.7-next.1",
+ "version": "0.0.7-next.2",
"backstage": {
"role": "web-library",
"inline": true
diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md
index 95cb6c846b..48e7368cba 100644
--- a/packages/frontend-plugin-api/CHANGELOG.md
+++ b/packages/frontend-plugin-api/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/frontend-plugin-api
+## 0.10.0-next.2
+
+### Minor Changes
+
+- 8250ffe: **BREAKING**: Removed the deprecated `ExtensionOverrides` and `FrontendFeature` types.
+- 0d1a397: **BREAKING**: Removed deprecated variant of `createExtensionDataRef` where the ID is passed directly.
+
+### Patch Changes
+
+- 5aa7f2c: Added a new Utility API, `DialogApi`, which can be used to show dialogs in the React tree that can collect input from the user.
+- e23f5e0: Added new `ExtensionMiddlewareFactory` type.
+- a6cb67d: The extensions map for plugins created with `createFrontendPlugin` is now sorted alphabetically by ID in the TypeScript type.
+- Updated dependencies
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
## 0.9.6-next.1
### Patch Changes
diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json
index 3551ac5e75..1e2818eb6e 100644
--- a/packages/frontend-plugin-api/package.json
+++ b/packages/frontend-plugin-api/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-plugin-api",
- "version": "0.9.6-next.1",
+ "version": "0.10.0-next.2",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md
index 52d0276952..a8b5937af1 100644
--- a/packages/frontend-plugin-api/report.api.md
+++ b/packages/frontend-plugin-api/report.api.md
@@ -610,11 +610,6 @@ export type CreateExtensionBlueprintOptions<
dataRefs?: TDataRefs;
} & VerifyExtensionFactoryOutput;
-// @public @deprecated (undocumented)
-export function createExtensionDataRef(
- id: string,
-): ConfigurableExtensionDataRef;
-
// @public (undocumented)
export function createExtensionDataRef(): {
with(options: {
@@ -712,6 +707,40 @@ export function createExternalRouteRef<
}
>;
+// @public (undocumented)
+export function createFrontendFeatureLoader(
+ options: CreateFrontendFeatureLoaderOptions,
+): FrontendFeatureLoader;
+
+// @public (undocumented)
+export interface CreateFrontendFeatureLoaderOptions {
+ // (undocumented)
+ loader(deps: { config: ConfigApi }):
+ | Iterable<
+ | FrontendFeature
+ | FrontendFeatureLoader
+ | Promise<{
+ default: FrontendFeature | FrontendFeatureLoader;
+ }>
+ >
+ | Promise<
+ Iterable<
+ | FrontendFeature
+ | FrontendFeatureLoader
+ | Promise<{
+ default: FrontendFeature | FrontendFeatureLoader;
+ }>
+ >
+ >
+ | AsyncIterable<
+ | FrontendFeature
+ | FrontendFeatureLoader
+ | {
+ default: FrontendFeature | FrontendFeatureLoader;
+ }
+ >;
+}
+
// @public (undocumented)
export function createFrontendModule<
TId extends string,
@@ -1212,12 +1241,6 @@ export interface ExtensionInput<
}>;
}
-// @public (undocumented)
-export interface ExtensionOverrides {
- // (undocumented)
- readonly $$type: '@backstage/ExtensionOverrides';
-}
-
// @public
export interface ExternalRouteRef<
TParams extends AnyRouteRefParams = AnyRouteRefParams,
@@ -1247,8 +1270,14 @@ export { FetchApi };
export { fetchApiRef };
-// @public @deprecated (undocumented)
-export type FrontendFeature = FrontendPlugin | ExtensionOverrides;
+// @public (undocumented)
+export type FrontendFeature = FrontendPlugin | FrontendModule;
+
+// @public (undocumented)
+export interface FrontendFeatureLoader {
+ // (undocumented)
+ readonly $$type: '@backstage/FrontendFeatureLoader';
+}
// @public (undocumented)
export interface FrontendModule {
diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.test.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.test.ts
index 4b652298f1..b5acf78384 100644
--- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.test.ts
+++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.test.ts
@@ -29,15 +29,6 @@ describe('createExtensionDataRef', () => {
expect(String(refOptional)).toBe('ExtensionDataRef{id=foo,optional=true}');
});
- it('can be created and read in the deprecated way', () => {
- const ref = createExtensionDataRef('foo');
- expect(ref.id).toBe('foo');
- expect(String(ref)).toBe('ExtensionDataRef{id=foo,optional=false}');
- const refOptional = ref.optional();
- expect(refOptional.id).toBe('foo');
- expect(String(refOptional)).toBe('ExtensionDataRef{id=foo,optional=true}');
- });
-
it('can be used to encapsulate a value', () => {
const ref = createExtensionDataRef().with({ id: 'foo' });
const val: ExtensionDataValue = ref('hello');
diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts
index 80413c1688..94acb192a8 100644
--- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts
+++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts
@@ -60,26 +60,12 @@ export interface ConfigurableExtensionDataRef<
(t: TData): ExtensionDataValue;
}
-/**
- * @public
- * @deprecated Use the following form instead: `createExtensionDataRef().with({ id: 'core.foo' })`
- */
-export function createExtensionDataRef(
- id: string,
-): ConfigurableExtensionDataRef;
/** @public */
export function createExtensionDataRef(): {
with(options: {
id: TId;
}): ConfigurableExtensionDataRef;
-};
-export function createExtensionDataRef(id?: string):
- | ConfigurableExtensionDataRef
- | {
- with(options: {
- id: TId;
- }): ConfigurableExtensionDataRef;
- } {
+} {
const createRef = (refId: TId) =>
Object.assign(
(value: TData): ExtensionDataValue => ({
@@ -103,9 +89,6 @@ export function createExtensionDataRef(id?: string):
},
} as ConfigurableExtensionDataRef,
);
- if (id) {
- return createRef(id);
- }
return {
with(options: { id: TId }) {
return createRef(options.id);
diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.test.ts b/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.test.ts
new file mode 100644
index 0000000000..f240b4f678
--- /dev/null
+++ b/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.test.ts
@@ -0,0 +1,484 @@
+/*
+ * 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 React from 'react';
+// eslint-disable-next-line @backstage/no-relative-monorepo-imports
+import { createApp } from '../../../frontend-defaults/src/createApp';
+import { screen } from '@testing-library/react';
+import { createFrontendPlugin } from './createFrontendPlugin';
+import { JsonObject } from '@backstage/types';
+import { createExtension } from './createExtension';
+import { createExtensionDataRef } from './createExtensionDataRef';
+import { coreExtensionData } from './coreExtensionData';
+import { mockApis, renderWithEffects } from '@backstage/test-utils';
+import { createExtensionInput } from './createExtensionInput';
+import {
+ CreateFrontendFeatureLoaderOptions,
+ InternalFrontendFeatureLoader,
+ createFrontendFeatureLoader,
+ FrontendFeatureLoader,
+} from './createFrontendFeatureLoader';
+import { FrontendFeature } from './types';
+
+const nameExtensionDataRef = createExtensionDataRef().with({
+ id: 'name',
+});
+
+function createTestAppRoot({
+ features,
+ config = {},
+}: {
+ features: (FrontendFeature | FrontendFeatureLoader)[];
+ config: JsonObject;
+}) {
+ return createApp({
+ features: [...features],
+ configLoader: async () => ({ config: mockApis.config({ data: config }) }),
+ }).createRoot();
+}
+
+describe('createFrontendFeatureLoader', () => {
+ it('should create several plugins with only one feature loader', async () => {
+ const featureLoader: FrontendFeatureLoader = createFrontendFeatureLoader({
+ loader({ config }) {
+ const pluginIdPrefix = config.getOptionalString('pluginIdPrefix');
+ const extensionNamePrefix = config.getOptionalString(
+ 'extensionNamePrefix',
+ );
+ return [
+ createFrontendPlugin({
+ id: `${pluginIdPrefix}-1`,
+ extensions: [
+ createExtension({
+ name: '1',
+ attachTo: {
+ id: `${pluginIdPrefix}-output/output`,
+ input: 'names',
+ },
+ output: [nameExtensionDataRef],
+ factory() {
+ return [nameExtensionDataRef(`${extensionNamePrefix}-1`)];
+ },
+ }),
+ ],
+ }) as FrontendFeature | FrontendFeatureLoader,
+ createFrontendPlugin({
+ id: `${pluginIdPrefix}-2`,
+ extensions: [
+ createExtension({
+ name: '2',
+ attachTo: {
+ id: `${pluginIdPrefix}-output/output`,
+ input: 'names',
+ },
+ output: [nameExtensionDataRef],
+ factory() {
+ return [nameExtensionDataRef(`${extensionNamePrefix}-2`)];
+ },
+ }),
+ ],
+ }) as FrontendFeature | FrontendFeatureLoader,
+ createFrontendPlugin({
+ id: `${pluginIdPrefix}-output`,
+ extensions: [
+ createExtension({
+ name: 'output',
+ attachTo: { id: 'app', input: 'root' },
+ inputs: {
+ names: createExtensionInput([nameExtensionDataRef]),
+ },
+ output: [coreExtensionData.reactElement],
+ factory({ inputs }) {
+ return [
+ coreExtensionData.reactElement(
+ React.createElement('span', {}, [
+ `Names: ${inputs.names
+ .map(n => n.get(nameExtensionDataRef))
+ .join(', ')}`,
+ ]),
+ ),
+ ];
+ },
+ }),
+ ],
+ }) as FrontendFeature | FrontendFeatureLoader,
+ ];
+ },
+ } as CreateFrontendFeatureLoaderOptions);
+
+ expect(featureLoader).toBeDefined();
+ expect(String(featureLoader)).toMatch(
+ /^FeatureLoader{description=created at '.*\/packages\/frontend-plugin-api\/src\/wiring\/createFrontendFeatureLoader\.test\.ts:.*'}$/,
+ );
+
+ await renderWithEffects(
+ createTestAppRoot({
+ features: [featureLoader],
+ config: {
+ app: { extensions: [{ 'app/root': false }] },
+ extensionNamePrefix: 'extension',
+ pluginIdPrefix: 'plugin',
+ },
+ }),
+ );
+
+ await expect(
+ screen.findByText('Names: extension-1, extension-2'),
+ ).resolves.toBeInTheDocument();
+ });
+
+ it('should propagate errors thrown by feature loaders', async () => {
+ const featureLoader: FrontendFeature | FrontendFeatureLoader =
+ createFrontendFeatureLoader({
+ async loader(_) {
+ throw new TypeError('boom');
+ },
+ });
+
+ await expect(
+ renderWithEffects(
+ createTestAppRoot({
+ features: [featureLoader],
+ config: {},
+ }),
+ ),
+ ).rejects.toThrow(
+ /^Failed to read frontend features from loader created at '.*\/packages\/frontend-plugin-api\/src\/wiring\/createFrontendFeatureLoader\.test\.ts:.*': TypeError: boom$/,
+ );
+ });
+
+ it('should support loading feature loaders', async () => {
+ const featureLoader: FrontendFeature | FrontendFeatureLoader =
+ createFrontendFeatureLoader({
+ async loader(_) {
+ return [
+ createFrontendPlugin({
+ id: 'plugin-0',
+ extensions: [
+ createExtension({
+ name: '0',
+ attachTo: {
+ id: 'plugin-output/output',
+ input: 'names',
+ },
+ output: [nameExtensionDataRef],
+ factory() {
+ return [nameExtensionDataRef('extension-0')];
+ },
+ }),
+ ],
+ }),
+ createFrontendFeatureLoader({
+ async *loader(__) {
+ yield createFrontendPlugin({
+ id: 'plugin-1',
+ extensions: [
+ createExtension({
+ name: '1',
+ attachTo: {
+ id: 'plugin-output/output',
+ input: 'names',
+ },
+ output: [nameExtensionDataRef],
+ factory() {
+ return [nameExtensionDataRef('extension-1')];
+ },
+ }),
+ ],
+ });
+ yield createFrontendFeatureLoader({
+ loader: async ___ => [
+ createFrontendPlugin({
+ id: 'plugin-2',
+ extensions: [
+ createExtension({
+ name: '2',
+ attachTo: {
+ id: 'plugin-output/output',
+ input: 'names',
+ },
+ output: [nameExtensionDataRef],
+ factory() {
+ return [nameExtensionDataRef('extension-2')];
+ },
+ }),
+ ],
+ }),
+ ],
+ });
+ },
+ }),
+ createFrontendPlugin({
+ id: 'plugin-output',
+ extensions: [
+ createExtension({
+ name: 'output',
+ attachTo: { id: 'app', input: 'root' },
+ inputs: {
+ names: createExtensionInput([nameExtensionDataRef]),
+ },
+ output: [coreExtensionData.reactElement],
+ factory({ inputs }) {
+ return [
+ coreExtensionData.reactElement(
+ React.createElement('span', {}, [
+ `Names: ${inputs.names
+ .map(n => n.get(nameExtensionDataRef))
+ .join(', ')}`,
+ ]),
+ ),
+ ];
+ },
+ }),
+ ],
+ }),
+ ];
+ },
+ });
+
+ expect(featureLoader).toBeDefined();
+ expect(String(featureLoader)).toMatch(
+ /^FeatureLoader{description=created at '.*\/packages\/frontend-plugin-api\/src\/wiring\/createFrontendFeatureLoader\.test\.ts:.*'}$/,
+ );
+
+ await renderWithEffects(
+ createTestAppRoot({
+ features: [featureLoader],
+ config: {
+ app: { extensions: [{ 'app/root': false }] },
+ },
+ }),
+ );
+
+ await expect(
+ screen.findByText('Names: extension-0, extension-1, extension-2'),
+ ).resolves.toBeInTheDocument();
+ });
+
+ it('should guard against infinite recursion of nested feature loaders', async () => {
+ const nestedFeatureLoaderHolder: {
+ loader?: FrontendFeature | FrontendFeatureLoader;
+ } = {};
+ const featureLoader: FrontendFeature | FrontendFeatureLoader =
+ createFrontendFeatureLoader({
+ loader: () =>
+ [
+ nestedFeatureLoaderHolder.loader,
+ createFrontendPlugin({
+ id: 'plugin',
+ extensions: [
+ createExtension({
+ name: 'output',
+ attachTo: { id: 'app', input: 'root' },
+ inputs: {},
+ output: [coreExtensionData.reactElement],
+ factory() {
+ return [
+ coreExtensionData.reactElement(
+ React.createElement('span', {}, [`My Content`]),
+ ),
+ ];
+ },
+ }),
+ ],
+ }),
+ ].filter(
+ (f): f is FrontendFeature | FrontendFeatureLoader =>
+ f !== undefined,
+ ),
+ });
+ nestedFeatureLoaderHolder.loader = featureLoader;
+
+ expect(featureLoader).toBeDefined();
+ expect(String(featureLoader)).toMatch(
+ /^FeatureLoader{description=created at '.*\/packages\/frontend-plugin-api\/src\/wiring\/createFrontendFeatureLoader\.test\.ts:.*'}$/,
+ );
+
+ await renderWithEffects(
+ createTestAppRoot({
+ features: [featureLoader],
+ config: {
+ app: { extensions: [{ 'app/root': false }] },
+ },
+ }),
+ );
+ });
+
+ it('should support multiple output formats', async () => {
+ const feature = createFrontendPlugin({
+ id: 'test',
+ });
+ const dynamicFeature = Promise.resolve({ default: feature });
+
+ async function extractResult(f: FrontendFeature | FrontendFeatureLoader) {
+ const internal = f as InternalFrontendFeatureLoader;
+ return internal.loader({ config: mockApis.config() });
+ }
+
+ await expect(
+ extractResult(
+ createFrontendFeatureLoader({
+ loader() {
+ return [feature];
+ },
+ }),
+ ),
+ ).resolves.toEqual([feature]);
+
+ await expect(
+ extractResult(
+ createFrontendFeatureLoader({
+ async loader() {
+ return [feature];
+ },
+ }),
+ ),
+ ).resolves.toEqual([feature]);
+
+ await expect(
+ extractResult(
+ createFrontendFeatureLoader({
+ *loader() {
+ yield feature;
+ },
+ }),
+ ),
+ ).resolves.toEqual([feature]);
+
+ await expect(
+ extractResult(
+ createFrontendFeatureLoader({
+ async *loader() {
+ yield feature;
+ },
+ }),
+ ),
+ ).resolves.toEqual([feature]);
+
+ await expect(
+ extractResult(
+ createFrontendFeatureLoader({
+ loader() {
+ return [dynamicFeature];
+ },
+ }),
+ ),
+ ).resolves.toEqual([feature]);
+
+ await expect(
+ extractResult(
+ createFrontendFeatureLoader({
+ async loader() {
+ return [dynamicFeature];
+ },
+ }),
+ ),
+ ).resolves.toEqual([feature]);
+
+ await expect(
+ extractResult(
+ createFrontendFeatureLoader({
+ *loader() {
+ yield dynamicFeature;
+ },
+ }),
+ ),
+ ).resolves.toEqual([feature]);
+
+ await expect(
+ extractResult(
+ createFrontendFeatureLoader({
+ async *loader() {
+ yield dynamicFeature;
+ },
+ }),
+ ),
+ ).resolves.toEqual([feature]);
+ });
+
+ it('should limit feature loading recursion', async () => {
+ const plugin = createFrontendPlugin({
+ id: 'plugin',
+ extensions: [
+ createExtension({
+ name: 'output',
+ attachTo: { id: 'app', input: 'root' },
+ inputs: {},
+ output: [coreExtensionData.reactElement],
+ factory() {
+ return [
+ coreExtensionData.reactElement(
+ React.createElement('span', {}, [`My Content`]),
+ ),
+ ];
+ },
+ }),
+ ],
+ });
+
+ const featureLoader: FrontendFeature | FrontendFeatureLoader =
+ createFrontendFeatureLoader({
+ loader: () => [
+ createFrontendFeatureLoader({
+ loader: () => [
+ createFrontendFeatureLoader({
+ loader: () => [
+ createFrontendFeatureLoader({
+ loader: () => [
+ createFrontendFeatureLoader({
+ loader: () => [
+ createFrontendFeatureLoader({
+ loader: () => [plugin],
+ }),
+ ],
+ }),
+ ],
+ }),
+ ],
+ }),
+ ],
+ }),
+ ],
+ });
+
+ await expect(
+ renderWithEffects(
+ createTestAppRoot({
+ features: [featureLoader],
+ config: {
+ app: { extensions: [{ 'app/root': false }] },
+ },
+ }),
+ ),
+ ).rejects.toThrow(
+ /^Maximum feature loading recursion depth \(5\) reached for the feature loader created at '.*\/packages\/frontend-plugin-api\/src\/wiring\/createFrontendFeatureLoader\.test\.ts:.*'$/,
+ );
+
+ const nestedLoaders = await (
+ featureLoader as InternalFrontendFeatureLoader
+ ).loader({ config: mockApis.config() });
+ await renderWithEffects(
+ createTestAppRoot({
+ features: [nestedLoaders[0]],
+ config: {
+ app: { extensions: [{ 'app/root': false }] },
+ },
+ }),
+ );
+
+ await expect(screen.findByText('My Content')).resolves.toBeInTheDocument();
+ });
+});
diff --git a/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.ts b/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.ts
new file mode 100644
index 0000000000..ef71a79db8
--- /dev/null
+++ b/packages/frontend-plugin-api/src/wiring/createFrontendFeatureLoader.ts
@@ -0,0 +1,129 @@
+/*
+ * 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 { ConfigApi } from '../apis/definitions';
+import { describeParentCallSite } from '../routing/describeParentCallSite';
+import { FrontendFeature } from './types';
+
+/** @public */
+export interface CreateFrontendFeatureLoaderOptions {
+ loader(deps: {
+ config: ConfigApi;
+ }):
+ | Iterable<
+ | FrontendFeature
+ | FrontendFeatureLoader
+ | Promise<{ default: FrontendFeature | FrontendFeatureLoader }>
+ >
+ | Promise<
+ Iterable<
+ | FrontendFeature
+ | FrontendFeatureLoader
+ | Promise<{ default: FrontendFeature | FrontendFeatureLoader }>
+ >
+ >
+ | AsyncIterable<
+ | FrontendFeature
+ | FrontendFeatureLoader
+ | { default: FrontendFeature | FrontendFeatureLoader }
+ >;
+}
+
+/** @public */
+export interface FrontendFeatureLoader {
+ readonly $$type: '@backstage/FrontendFeatureLoader';
+}
+
+/** @internal */
+export interface InternalFrontendFeatureLoader extends FrontendFeatureLoader {
+ readonly version: 'v1';
+ readonly description: string;
+ readonly loader: (deps: {
+ config: ConfigApi;
+ }) => Promise<(FrontendFeature | FrontendFeatureLoader)[]>;
+}
+
+/** @public */
+export function createFrontendFeatureLoader(
+ options: CreateFrontendFeatureLoaderOptions,
+): FrontendFeatureLoader {
+ const description = `created at '${describeParentCallSite()}'`;
+ return {
+ $$type: '@backstage/FrontendFeatureLoader',
+ version: 'v1',
+ description,
+ toString() {
+ return `FeatureLoader{description=${description}}`;
+ },
+ async loader(deps: {
+ config: ConfigApi;
+ }): Promise<(FrontendFeature | FrontendFeatureLoader)[]> {
+ const it = await options.loader(deps);
+ const result = new Array();
+ for await (const item of it) {
+ if (isFeatureOrLoader(item)) {
+ result.push(item);
+ } else if ('default' in item) {
+ result.push(item.default);
+ } else {
+ throw new Error(`Invalid item "${item}"`);
+ }
+ }
+ return result;
+ },
+ } as InternalFrontendFeatureLoader;
+}
+
+/** @internal */
+export function isInternalFrontendFeatureLoader(opaque: {
+ $$type: string;
+}): opaque is InternalFrontendFeatureLoader {
+ if (opaque.$$type === '@backstage/FrontendFeatureLoader') {
+ // Make sure we throw if invalid
+ toInternalFrontendFeatureLoader(opaque as FrontendFeatureLoader);
+ return true;
+ }
+ return false;
+}
+
+/** @internal */
+export function toInternalFrontendFeatureLoader(
+ plugin: FrontendFeatureLoader,
+): InternalFrontendFeatureLoader {
+ const internal = plugin as InternalFrontendFeatureLoader;
+ if (internal.$$type !== '@backstage/FrontendFeatureLoader') {
+ throw new Error(`Invalid plugin instance, bad type '${internal.$$type}'`);
+ }
+ if (internal.version !== 'v1') {
+ throw new Error(
+ `Invalid plugin instance, bad version '${internal.version}'`,
+ );
+ }
+ return internal;
+}
+
+function isFeatureOrLoader(
+ obj: unknown,
+): obj is FrontendFeature | FrontendFeatureLoader {
+ if (obj !== null && typeof obj === 'object' && '$$type' in obj) {
+ return (
+ obj.$$type === '@backstage/FrontendPlugin' ||
+ obj.$$type === '@backstage/FrontendModule' ||
+ obj.$$type === '@backstage/FrontendFeatureLoader'
+ );
+ }
+ return false;
+}
diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts
index 6dec88b5f4..b584fc9769 100644
--- a/packages/frontend-plugin-api/src/wiring/index.ts
+++ b/packages/frontend-plugin-api/src/wiring/index.ts
@@ -46,15 +46,19 @@ export {
type FrontendModule,
type CreateFrontendModuleOptions,
} from './createFrontendModule';
+export {
+ createFrontendFeatureLoader,
+ type FrontendFeatureLoader,
+ type CreateFrontendFeatureLoaderOptions,
+} from './createFrontendFeatureLoader';
export { type Extension } from './resolveExtensionDefinition';
export {
type AnyRoutes,
type AnyExternalRoutes,
type ExtensionDataContainer,
- type ExtensionOverrides,
type FeatureFlagConfig,
- type FrontendFeature,
type ExtensionFactoryMiddleware,
+ type FrontendFeature,
} from './types';
export {
type CreateExtensionBlueprintOptions,
diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts
index 3aba32d9fc..22bcbdadb7 100644
--- a/packages/frontend-plugin-api/src/wiring/types.ts
+++ b/packages/frontend-plugin-api/src/wiring/types.ts
@@ -22,8 +22,9 @@ import {
ExtensionDataRef,
ExtensionDataValue,
} from './createExtensionDataRef';
-import { FrontendPlugin } from './createFrontendPlugin';
import { ApiHolder, AppNode } from '../apis';
+import { FrontendModule } from './createFrontendModule';
+import { FrontendPlugin } from './createFrontendPlugin';
/**
* Feature flag configuration.
@@ -48,17 +49,6 @@ export type ExtensionMap<
get(id: TId): TExtensionMap[TId];
};
-/** @public */
-export interface ExtensionOverrides {
- readonly $$type: '@backstage/ExtensionOverrides';
-}
-
-/**
- * @public
- * @deprecated import from {@link @backstage/frontend-app-api#FrontendFeature} instead
- */
-export type FrontendFeature = FrontendPlugin | ExtensionOverrides;
-
/** @public */
export type ExtensionDataContainer =
Iterable<
@@ -92,3 +82,6 @@ export type ExtensionFactoryMiddleware = (
config?: JsonObject;
},
) => Iterable>;
+
+/** @public */
+export type FrontendFeature = FrontendPlugin | FrontendModule;
diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md
index b16edb4723..0442172cb9 100644
--- a/packages/frontend-test-utils/CHANGELOG.md
+++ b/packages/frontend-test-utils/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/frontend-test-utils
+## 0.3.0-next.2
+
+### Minor Changes
+
+- bba525b: **BREAKING**: Removed deprecated `setupRequestMockHandlers` which was replaced by `registerMswTestHooks`.
+
+### Patch Changes
+
+- f861bfc: Added a `initialRouteEntries` option to `renderInTestApp`.
+- f861bfc: The `renderInTestApp` helper now provides a default mock config with mock values for both `app.baseUrl` and `backend.baseUrl`.
+- abcdf44: Internal refactor to match updated `createSpecializedApp`.
+- Updated dependencies
+ - @backstage/frontend-app-api@0.11.0-next.2
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/plugin-app@0.1.7-next.2
+ - @backstage/test-utils@1.7.6-next.0
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+
## 0.2.7-next.1
### Patch Changes
diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json
index d6f98a1491..0e81615e71 100644
--- a/packages/frontend-test-utils/package.json
+++ b/packages/frontend-test-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/frontend-test-utils",
- "version": "0.2.7-next.1",
+ "version": "0.3.0-next.2",
"backstage": {
"role": "web-library"
},
diff --git a/packages/frontend-test-utils/report.api.md b/packages/frontend-test-utils/report.api.md
index fd6aba0223..b139538cbe 100644
--- a/packages/frontend-test-utils/report.api.md
+++ b/packages/frontend-test-utils/report.api.md
@@ -120,13 +120,6 @@ export function renderInTestApp(
options?: TestAppOptions,
): RenderResult;
-// @public @deprecated (undocumented)
-export function setupRequestMockHandlers(worker: {
- listen: (t: any) => void;
- close: () => void;
- resetHandlers: () => void;
-}): void;
-
export { TestApiProvider };
export { TestApiProviderProps };
@@ -141,6 +134,7 @@ export type TestAppOptions = {
config?: JsonObject;
extensions?: ExtensionDefinition[];
features?: FrontendFeature[];
+ initialRouteEntries?: string[];
};
export { withLogCollector };
diff --git a/packages/frontend-test-utils/src/app/renderInTestApp.tsx b/packages/frontend-test-utils/src/app/renderInTestApp.tsx
index cd9f4439b3..1643b06fac 100644
--- a/packages/frontend-test-utils/src/app/renderInTestApp.tsx
+++ b/packages/frontend-test-utils/src/app/renderInTestApp.tsx
@@ -36,6 +36,11 @@ import {
} from '@backstage/frontend-plugin-api';
import appPlugin from '@backstage/plugin-app';
+const DEFAULT_MOCK_CONFIG = {
+ app: { baseUrl: 'http://localhost:3000' },
+ backend: { baseUrl: 'http://localhost:7007' },
+};
+
/**
* Options to customize the behavior of the test app.
* @public
@@ -73,6 +78,11 @@ export type TestAppOptions = {
* Additional features to add to the test app.
*/
features?: FrontendFeature[];
+
+ /**
+ * Initial route entries to use for the router.
+ */
+ initialRouteEntries?: string[];
};
const NavItem = (props: {
@@ -150,7 +160,11 @@ export function renderInTestApp(
}),
RouterBlueprint.make({
params: {
- Component: ({ children }) => {children},
+ Component: ({ children }) => (
+
+ {children}
+
+ ),
},
}),
];
@@ -197,7 +211,10 @@ export function renderInTestApp(
const app = createSpecializedApp({
features,
config: ConfigReader.fromConfigs([
- { context: 'render-config', data: options?.config ?? {} },
+ {
+ context: 'render-config',
+ data: options?.config ?? DEFAULT_MOCK_CONFIG,
+ },
]),
});
diff --git a/packages/frontend-test-utils/src/index.ts b/packages/frontend-test-utils/src/index.ts
index 20f393706e..cab66895e0 100644
--- a/packages/frontend-test-utils/src/index.ts
+++ b/packages/frontend-test-utils/src/index.ts
@@ -20,7 +20,6 @@
* Contains utilities that can be used when testing frontend features such as extensions.
*/
-export * from './deprecated';
export * from './apis';
export * from './app';
diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md
index 1b9f5732de..8d4d636a7b 100644
--- a/packages/integration-react/CHANGELOG.md
+++ b/packages/integration-react/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/integration-react
+## 1.2.5-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.4
+
## 1.2.4
### Patch Changes
diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json
index 163b0338d8..150537049b 100644
--- a/packages/integration-react/package.json
+++ b/packages/integration-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/integration-react",
- "version": "1.2.4",
+ "version": "1.2.5-next.0",
"description": "Frontend package for managing integrations towards external systems",
"backstage": {
"role": "web-library"
diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md
index 3e9c912904..9f94a068c8 100644
--- a/packages/integration/CHANGELOG.md
+++ b/packages/integration/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/integration
+## 1.16.2-next.0
+
+### Patch Changes
+
+- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder
+- Updated dependencies
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 1.16.1
### Patch Changes
diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts
index 1e4c8d373d..bd5267ab3d 100644
--- a/packages/integration/config.d.ts
+++ b/packages/integration/config.d.ts
@@ -62,6 +62,11 @@ export interface Config {
tenantId?: string;
personalAccessToken?: string;
}[];
+ /**
+ * PGP signing key for signing commits.
+ * @visibility secret
+ */
+ commitSigningKey?: string;
}>;
/**
@@ -94,6 +99,11 @@ export interface Config {
* @visibility secret
*/
appPassword?: string;
+ /**
+ * PGP signing key for signing commits.
+ * @visibility secret
+ */
+ commitSigningKey?: string;
}>;
/** Integration configuration for Bitbucket Cloud */
@@ -108,6 +118,11 @@ export interface Config {
* @visibility secret
*/
appPassword: string;
+ /**
+ * PGP signing key for signing commits.
+ * @visibility secret
+ */
+ commitSigningKey?: string;
}>;
/** Integration configuration for Bitbucket Server */
@@ -137,6 +152,11 @@ export interface Config {
* @visibility frontend
*/
apiBaseUrl?: string;
+ /**
+ * PGP signing key for signing commits.
+ * @visibility secret
+ */
+ commitSigningKey?: string;
}>;
/** Integration configuration for Gerrit */
@@ -172,6 +192,11 @@ export interface Config {
* @visibility secret
*/
password?: string;
+ /**
+ * PGP signing key for signing commits.
+ * @visibility secret
+ */
+ commitSigningKey?: string;
}>;
/** Integration configuration for GitHub */
@@ -269,6 +294,11 @@ export interface Config {
* @visibility frontend
*/
baseUrl?: string;
+ /**
+ * PGP signing key for signing commits.
+ * @visibility secret
+ */
+ commitSigningKey?: string;
}>;
/** Integration configuration for Google Cloud Storage */
@@ -349,6 +379,11 @@ export interface Config {
* @visibility secret
*/
password?: string;
+ /**
+ * PGP signing key for signing commits.
+ * @visibility secret
+ */
+ commitSigningKey?: string;
}>;
/** Integration configuration for Harness Code */
harness?: Array<{
diff --git a/packages/integration/package.json b/packages/integration/package.json
index a51850ed9c..121c297e44 100644
--- a/packages/integration/package.json
+++ b/packages/integration/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/integration",
- "version": "1.16.1",
+ "version": "1.16.2-next.0",
"description": "Helpers for managing integrations towards external systems",
"backstage": {
"role": "common-library"
diff --git a/packages/integration/report.api.md b/packages/integration/report.api.md
index 6bb48b8185..e44b02851c 100644
--- a/packages/integration/report.api.md
+++ b/packages/integration/report.api.md
@@ -201,6 +201,7 @@ export type AzureIntegrationConfig = {
token?: string;
credential?: AzureDevOpsCredential;
credentials?: AzureDevOpsCredential[];
+ commitSigningKey?: string;
};
// @public
@@ -237,6 +238,7 @@ export type BitbucketCloudIntegrationConfig = {
username?: string;
appPassword?: string;
token?: string;
+ commitSigningKey?: string;
};
// @public @deprecated
@@ -267,6 +269,7 @@ export type BitbucketIntegrationConfig = {
token?: string;
username?: string;
appPassword?: string;
+ commitSigningKey?: string;
};
// @public
@@ -297,6 +300,7 @@ export type BitbucketServerIntegrationConfig = {
token?: string;
username?: string;
password?: string;
+ commitSigningKey?: string;
};
// @public @deprecated
@@ -395,6 +399,7 @@ export type GerritIntegrationConfig = {
gitilesBaseUrl: string;
username?: string;
password?: string;
+ commitSigningKey?: string;
};
// @public
@@ -630,6 +635,7 @@ export type GiteaIntegrationConfig = {
baseUrl?: string;
username?: string;
password?: string;
+ commitSigningKey?: string;
};
// @public
@@ -744,6 +750,7 @@ export type GitLabIntegrationConfig = {
apiBaseUrl: string;
token?: string;
baseUrl: string;
+ commitSigningKey?: string;
};
// @public
diff --git a/packages/integration/src/azure/config.ts b/packages/integration/src/azure/config.ts
index 5ed1914ea2..2910387829 100644
--- a/packages/integration/src/azure/config.ts
+++ b/packages/integration/src/azure/config.ts
@@ -57,6 +57,11 @@ export type AzureIntegrationConfig = {
* If no credentials are specified at all, either a default credential (for Azure DevOps) or anonymous access (for Azure DevOps Server) is used.
*/
credentials?: AzureDevOpsCredential[];
+
+ /**
+ * Signing key for commits
+ */
+ commitSigningKey?: string;
};
/**
@@ -349,6 +354,7 @@ export function readAzureIntegrationConfig(
return {
host,
credentials,
+ commitSigningKey: config.getOptionalString('commitSigningKey'),
};
}
diff --git a/packages/integration/src/bitbucket/config.ts b/packages/integration/src/bitbucket/config.ts
index 4b6296b0e9..6939034e7d 100644
--- a/packages/integration/src/bitbucket/config.ts
+++ b/packages/integration/src/bitbucket/config.ts
@@ -62,6 +62,11 @@ export type BitbucketIntegrationConfig = {
* See https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/
*/
appPassword?: string;
+
+ /**
+ * Signing key for commits
+ */
+ commitSigningKey?: string;
};
/**
@@ -100,6 +105,7 @@ export function readBitbucketIntegrationConfig(
token,
username,
appPassword,
+ commitSigningKey: config.getOptionalString('commitSigningKey'),
};
}
diff --git a/packages/integration/src/bitbucketCloud/config.ts b/packages/integration/src/bitbucketCloud/config.ts
index 32a2af9313..eb4b5c4904 100644
--- a/packages/integration/src/bitbucketCloud/config.ts
+++ b/packages/integration/src/bitbucketCloud/config.ts
@@ -51,6 +51,9 @@ export type BitbucketCloudIntegrationConfig = {
* The access token to use for requests to Bitbucket Cloud (bitbucket.org).
*/
token?: string;
+
+ /** PGP private key for signing commits. */
+ commitSigningKey?: string;
};
/**
@@ -74,6 +77,7 @@ export function readBitbucketCloudIntegrationConfig(
apiBaseUrl,
username,
appPassword,
+ commitSigningKey: config.getOptionalString('commitSigningKey'),
};
}
diff --git a/packages/integration/src/bitbucketServer/config.ts b/packages/integration/src/bitbucketServer/config.ts
index 1412b7ab17..b2a243b3b3 100644
--- a/packages/integration/src/bitbucketServer/config.ts
+++ b/packages/integration/src/bitbucketServer/config.ts
@@ -64,6 +64,11 @@ export type BitbucketServerIntegrationConfig = {
* See https://developer.atlassian.com/server/bitbucket/how-tos/command-line-rest/#authentication
*/
password?: string;
+
+ /**
+ * Signing key for commits
+ */
+ commitSigningKey?: string;
};
/**
@@ -99,6 +104,7 @@ export function readBitbucketServerIntegrationConfig(
token,
username,
password,
+ commitSigningKey: config.getOptionalString('commitSigningKey'),
};
}
diff --git a/packages/integration/src/gerrit/config.ts b/packages/integration/src/gerrit/config.ts
index a5fdc8dabf..fe88750687 100644
--- a/packages/integration/src/gerrit/config.ts
+++ b/packages/integration/src/gerrit/config.ts
@@ -60,6 +60,11 @@ export type GerritIntegrationConfig = {
* The password or http token to use for authentication.
*/
password?: string;
+
+ /**
+ * The signing key to use for signing commits.
+ */
+ commitSigningKey?: string;
};
/**
@@ -119,6 +124,7 @@ export function readGerritIntegrationConfig(
gitilesBaseUrl,
username,
password,
+ commitSigningKey: config.getOptionalString('commitSigningKey'),
};
}
diff --git a/packages/integration/src/gitea/config.ts b/packages/integration/src/gitea/config.ts
index ae1fe5ee87..461ee60715 100644
--- a/packages/integration/src/gitea/config.ts
+++ b/packages/integration/src/gitea/config.ts
@@ -45,6 +45,11 @@ export type GiteaIntegrationConfig = {
* The password or http token to use for authentication.
*/
password?: string;
+
+ /**
+ * Signing key to to sign commits
+ */
+ commitSigningKey?: string;
};
/**
@@ -79,5 +84,6 @@ export function readGiteaConfig(config: Config): GiteaIntegrationConfig {
baseUrl,
username,
password,
+ commitSigningKey: config.getOptionalString('commitSigningKey'),
};
}
diff --git a/packages/integration/src/gitlab/config.ts b/packages/integration/src/gitlab/config.ts
index 79161561f3..636919291c 100644
--- a/packages/integration/src/gitlab/config.ts
+++ b/packages/integration/src/gitlab/config.ts
@@ -54,6 +54,11 @@ export type GitLabIntegrationConfig = {
* If no baseUrl is provided, it will default to `https://${host}`
*/
baseUrl: string;
+
+ /**
+ * Signing key to sign commits
+ */
+ commitSigningKey?: string;
};
/**
@@ -95,7 +100,13 @@ export function readGitLabIntegrationConfig(
);
}
- return { host, token, apiBaseUrl, baseUrl };
+ return {
+ host,
+ token,
+ apiBaseUrl,
+ baseUrl,
+ commitSigningKey: config.getOptionalString('commitSigningKey'),
+ };
}
/**
diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md
index 1929807367..7c9c722d08 100644
--- a/packages/repo-tools/CHANGELOG.md
+++ b/packages/repo-tools/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/repo-tools
+## 0.13.1-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config-loader@1.10.0-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/cli-node@0.2.13
+ - @backstage/errors@1.2.7
+
## 0.13.1-next.1
### Patch Changes
diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json
index f352917dd4..c0f6f68773 100644
--- a/packages/repo-tools/package.json
+++ b/packages/repo-tools/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/repo-tools",
- "version": "0.13.1-next.1",
+ "version": "0.13.1-next.2",
"description": "CLI for Backstage repo tooling ",
"backstage": {
"role": "cli"
diff --git a/packages/scaffolder-internal/CHANGELOG.md b/packages/scaffolder-internal/CHANGELOG.md
index 47d8472a1d..76f68005a4 100644
--- a/packages/scaffolder-internal/CHANGELOG.md
+++ b/packages/scaffolder-internal/CHANGELOG.md
@@ -1,5 +1,13 @@
# @internal/scaffolder
+## 0.0.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/plugin-scaffolder-react@1.14.6-next.2
+
## 0.0.7-next.1
### Patch Changes
diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json
index 10d44a9555..11fd798a8b 100644
--- a/packages/scaffolder-internal/package.json
+++ b/packages/scaffolder-internal/package.json
@@ -1,6 +1,6 @@
{
"name": "@internal/scaffolder",
- "version": "0.0.7-next.1",
+ "version": "0.0.7-next.2",
"backstage": {
"role": "web-library",
"inline": true
diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md
index 35b7dd86f6..4dc3a48f7c 100644
--- a/packages/techdocs-cli-embedded-app/CHANGELOG.md
+++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md
@@ -1,5 +1,24 @@
# techdocs-cli-embedded-app
+## 0.2.106-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog@1.28.0-next.2
+ - @backstage/cli@0.31.0-next.1
+ - @backstage/core-app-api@1.16.0-next.0
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/app-defaults@1.6.0-next.1
+ - @backstage/test-utils@1.7.6-next.0
+ - @backstage/plugin-techdocs@1.12.4-next.2
+ - @backstage/plugin-techdocs-react@1.2.15-next.2
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/integration-react@1.2.5-next.0
+ - @backstage/theme@0.6.4
+
## 0.2.106-next.1
### Patch Changes
diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json
index e40215eb96..14dce40c95 100644
--- a/packages/techdocs-cli-embedded-app/package.json
+++ b/packages/techdocs-cli-embedded-app/package.json
@@ -1,6 +1,6 @@
{
"name": "techdocs-cli-embedded-app",
- "version": "0.2.106-next.1",
+ "version": "0.2.106-next.2",
"backstage": {
"role": "frontend"
},
diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md
index 19b44d7d9d..2c01a023ae 100644
--- a/packages/techdocs-cli/CHANGELOG.md
+++ b/packages/techdocs-cli/CHANGELOG.md
@@ -1,5 +1,16 @@
# @techdocs/cli
+## 1.9.1-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.8.2-next.2
+ - @backstage/catalog-model@1.7.3
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/plugin-techdocs-node@1.13.1-next.2
+
## 1.9.1-next.1
### Patch Changes
diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json
index 283035dd97..580d88a8ce 100644
--- a/packages/techdocs-cli/package.json
+++ b/packages/techdocs-cli/package.json
@@ -1,6 +1,6 @@
{
"name": "@techdocs/cli",
- "version": "1.9.1-next.1",
+ "version": "1.9.1-next.2",
"description": "Utility CLI for managing TechDocs sites in Backstage.",
"backstage": {
"role": "cli"
diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md
index 8ec67ed51b..90d90a46f9 100644
--- a/packages/test-utils/CHANGELOG.md
+++ b/packages/test-utils/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/test-utils
+## 1.7.6-next.0
+
+### Patch Changes
+
+- 37c6510: Moved `@types/jest` to `devDependencies`.
+- Updated dependencies
+ - @backstage/core-app-api@1.16.0-next.0
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/theme@0.6.4
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-permission-react@0.4.31
+
## 1.7.5
### Patch Changes
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
index 5d97304350..f23f6902fd 100644
--- a/packages/test-utils/package.json
+++ b/packages/test-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/test-utils",
- "version": "1.7.5",
+ "version": "1.7.6-next.0",
"description": "Utilities to test Backstage plugins and apps.",
"backstage": {
"role": "web-library"
diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md
index 4f7d91ff35..6117c0bd5e 100644
--- a/plugins/api-docs/CHANGELOG.md
+++ b/plugins/api-docs/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-api-docs
+## 0.12.5-next.2
+
+### Patch Changes
+
+- 74871cc: Use consistent Typography in Entity HasApisCard
+- Updated dependencies
+ - @backstage/plugin-catalog@1.28.0-next.2
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-compat-api@0.4.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-react@0.4.31
+
## 0.12.5-next.1
### Patch Changes
diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json
index 80890155a7..d8cbb3dd8e 100644
--- a/plugins/api-docs/package.json
+++ b/plugins/api-docs/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-api-docs",
- "version": "0.12.5-next.1",
+ "version": "0.12.5-next.2",
"description": "A Backstage plugin that helps represent API entities in the frontend",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/api-docs/report-alpha.api.md b/plugins/api-docs/report-alpha.api.md
index 0108d94e49..25fda584e1 100644
--- a/plugins/api-docs/report-alpha.api.md
+++ b/plugins/api-docs/report-alpha.api.md
@@ -48,11 +48,11 @@ const _default: FrontendPlugin<
name: 'consumed-apis';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef<
@@ -93,11 +93,11 @@ const _default: FrontendPlugin<
name: 'consuming-components';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef<
@@ -138,11 +138,11 @@ const _default: FrontendPlugin<
name: 'definition';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef<
@@ -183,11 +183,11 @@ const _default: FrontendPlugin<
name: 'has-apis';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef<
@@ -228,11 +228,11 @@ const _default: FrontendPlugin<
name: 'provided-apis';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef<
@@ -273,11 +273,11 @@ const _default: FrontendPlugin<
name: 'providing-components';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef<
diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md
index 4fe5387594..67a6b900d8 100644
--- a/plugins/app-backend/CHANGELOG.md
+++ b/plugins/app-backend/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/plugin-app-backend
+## 0.5.0-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config-loader@1.10.0-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-app-node@0.1.31-next.2
+ - @backstage/plugin-auth-node@0.6.1-next.1
+
## 0.5.0-next.1
### Minor Changes
diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json
index 9b6526be4e..4cf444996b 100644
--- a/plugins/app-backend/package.json
+++ b/plugins/app-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app-backend",
- "version": "0.5.0-next.1",
+ "version": "0.5.0-next.2",
"description": "A Backstage backend plugin that serves the Backstage frontend app",
"backstage": {
"role": "backend-plugin",
diff --git a/plugins/app-backend/src/setupTests.ts b/plugins/app-backend/src/setupTests.ts
index 4ed508a382..ea10a94839 100644
--- a/plugins/app-backend/src/setupTests.ts
+++ b/plugins/app-backend/src/setupTests.ts
@@ -17,5 +17,5 @@
import { TestDatabases } from '@backstage/backend-test-utils';
TestDatabases.setDefaults({
- ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'],
+ ids: ['MYSQL_8', 'POSTGRES_17', 'POSTGRES_13', 'SQLITE_3'],
});
diff --git a/plugins/app-node/CHANGELOG.md b/plugins/app-node/CHANGELOG.md
index 08ded25c6f..1b5fd25b6a 100644
--- a/plugins/app-node/CHANGELOG.md
+++ b/plugins/app-node/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-app-node
+## 0.1.31-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config-loader@1.10.0-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+
## 0.1.31-next.1
### Patch Changes
diff --git a/plugins/app-node/package.json b/plugins/app-node/package.json
index 1a782cbac4..b178c5079b 100644
--- a/plugins/app-node/package.json
+++ b/plugins/app-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app-node",
- "version": "0.1.31-next.1",
+ "version": "0.1.31-next.2",
"description": "Node.js library for the app plugin",
"backstage": {
"role": "node-library",
diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md
index fbd2003184..e89d604c34 100644
--- a/plugins/app-visualizer/CHANGELOG.md
+++ b/plugins/app-visualizer/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-app-visualizer
+## 0.1.17-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/core-plugin-api@1.10.4
+
## 0.1.17-next.1
### Patch Changes
diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json
index 7f52bb2d5b..1b924d529d 100644
--- a/plugins/app-visualizer/package.json
+++ b/plugins/app-visualizer/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app-visualizer",
- "version": "0.1.17-next.1",
+ "version": "0.1.17-next.2",
"description": "Visualizes the Backstage app structure",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md
index 886d02e9aa..1dad673c93 100644
--- a/plugins/app/CHANGELOG.md
+++ b/plugins/app/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-app
+## 0.1.7-next.2
+
+### Patch Changes
+
+- 0aa9d82: Added implementation of the new `DialogApi`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/integration-react@1.2.5-next.0
+ - @backstage/theme@0.6.4
+ - @backstage/types@1.2.1
+ - @backstage/plugin-permission-react@0.4.31
+
## 0.1.7-next.1
### Patch Changes
diff --git a/plugins/app/package.json b/plugins/app/package.json
index 86007ac2d0..74fa075e77 100644
--- a/plugins/app/package.json
+++ b/plugins/app/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-app",
- "version": "0.1.7-next.1",
+ "version": "0.1.7-next.2",
"backstage": {
"role": "frontend-plugin",
"pluginId": "app",
diff --git a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md
index a732b9100e..246a101f14 100644
--- a/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-aws-alb-provider/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-auth-backend-module-aws-alb-provider
+## 0.4.1-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-auth-backend@0.24.4-next.2
+ - @backstage/plugin-auth-node@0.6.1-next.1
+
## 0.4.1-next.1
### Patch Changes
diff --git a/plugins/auth-backend-module-aws-alb-provider/package.json b/plugins/auth-backend-module-aws-alb-provider/package.json
index a2905159f8..0eba4cb971 100644
--- a/plugins/auth-backend-module-aws-alb-provider/package.json
+++ b/plugins/auth-backend-module-aws-alb-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-aws-alb-provider",
- "version": "0.4.1-next.1",
+ "version": "0.4.1-next.2",
"description": "The aws-alb provider module for the Backstage auth backend.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md
index faf545c9f9..9317b326ff 100644
--- a/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-oauth2-provider/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-auth-backend-module-oauth2-provider
+## 0.4.1-next.2
+
+### Patch Changes
+
+- ce15e30: Fixed repository url in `README.md`
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/plugin-auth-node@0.6.1-next.1
+
## 0.4.1-next.1
### Patch Changes
diff --git a/plugins/auth-backend-module-oauth2-provider/README.md b/plugins/auth-backend-module-oauth2-provider/README.md
index 6c23d24ff3..1a6937515e 100644
--- a/plugins/auth-backend-module-oauth2-provider/README.md
+++ b/plugins/auth-backend-module-oauth2-provider/README.md
@@ -4,5 +4,5 @@ This module provides an oauth2 auth provider implementation for `@backstage/plug
## Links
-- [Repository](https://oauth2.com/backstage/backstage/tree/master/plugins/auth-backend-module-oauth2-provider)
+- [Repository](https://github.com/backstage/backstage/tree/master/plugins/auth-backend-module-oauth2-provider)
- [Backstage Project Homepage](https://backstage.io)
diff --git a/plugins/auth-backend-module-oauth2-provider/package.json b/plugins/auth-backend-module-oauth2-provider/package.json
index e8dcccd33a..c83d99a276 100644
--- a/plugins/auth-backend-module-oauth2-provider/package.json
+++ b/plugins/auth-backend-module-oauth2-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-oauth2-provider",
- "version": "0.4.1-next.1",
+ "version": "0.4.1-next.2",
"description": "The oauth2-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md
index 8807ae4100..32d0961094 100644
--- a/plugins/auth-backend-module-oidc-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-oidc-provider/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-auth-backend-module-oidc-provider
+## 0.4.1-next.2
+
+### Patch Changes
+
+- ce15e30: Fixed repository url in `README.md`
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/plugin-auth-backend@0.24.4-next.2
+ - @backstage/plugin-auth-node@0.6.1-next.1
+
## 0.4.1-next.1
### Patch Changes
diff --git a/plugins/auth-backend-module-oidc-provider/README.md b/plugins/auth-backend-module-oidc-provider/README.md
index e586f32fd7..1cbe3525cd 100644
--- a/plugins/auth-backend-module-oidc-provider/README.md
+++ b/plugins/auth-backend-module-oidc-provider/README.md
@@ -4,5 +4,5 @@ This module provides an Oidc auth provider implementation for `@backstage/plugin
## Links
-- [Repository](https://oidc.com/backstage/backstage/tree/master/plugins/auth-backend-module-oidc-provider)
+- [Repository](https://github.com/backstage/backstage/tree/master/plugins/auth-backend-module-oidc-provider)
- [Backstage Project Homepage](https://backstage.io)
diff --git a/plugins/auth-backend-module-oidc-provider/package.json b/plugins/auth-backend-module-oidc-provider/package.json
index e5cb3b6d9e..3de2277163 100644
--- a/plugins/auth-backend-module-oidc-provider/package.json
+++ b/plugins/auth-backend-module-oidc-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-oidc-provider",
- "version": "0.4.1-next.1",
+ "version": "0.4.1-next.2",
"description": "The oidc-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend-module-okta-provider/CHANGELOG.md b/plugins/auth-backend-module-okta-provider/CHANGELOG.md
index 2d77340cc5..0e33cc5451 100644
--- a/plugins/auth-backend-module-okta-provider/CHANGELOG.md
+++ b/plugins/auth-backend-module-okta-provider/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-auth-backend-module-okta-provider
+## 0.2.1-next.2
+
+### Patch Changes
+
+- ce15e30: Fixed repository url in `README.md`
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/plugin-auth-node@0.6.1-next.1
+
## 0.2.1-next.1
### Patch Changes
diff --git a/plugins/auth-backend-module-okta-provider/README.md b/plugins/auth-backend-module-okta-provider/README.md
index 3a320a9b1a..51aa13c47e 100644
--- a/plugins/auth-backend-module-okta-provider/README.md
+++ b/plugins/auth-backend-module-okta-provider/README.md
@@ -26,5 +26,5 @@ export const okta = createAuthProviderIntegration({
## Links
-- [Repository](https://okta.com/backstage/backstage/tree/master/plugins/auth-backend-module-okta-provider)
+- [Repository](https://github.com/backstage/backstage/tree/master/plugins/auth-backend-module-okta-provider)
- [Backstage Project Homepage](https://backstage.io)
diff --git a/plugins/auth-backend-module-okta-provider/package.json b/plugins/auth-backend-module-okta-provider/package.json
index 86c3041f4d..8ac9af58cd 100644
--- a/plugins/auth-backend-module-okta-provider/package.json
+++ b/plugins/auth-backend-module-okta-provider/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend-module-okta-provider",
- "version": "0.2.1-next.1",
+ "version": "0.2.1-next.2",
"description": "The okta-provider backend module for the auth plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md
index db43089c46..8a2893bffc 100644
--- a/plugins/auth-backend/CHANGELOG.md
+++ b/plugins/auth-backend/CHANGELOG.md
@@ -1,5 +1,36 @@
# @backstage/plugin-auth-backend
+## 0.24.4-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-auth-backend-module-oauth2-provider@0.4.1-next.2
+ - @backstage/plugin-auth-backend-module-oidc-provider@0.4.1-next.2
+ - @backstage/plugin-auth-backend-module-okta-provider@0.2.1-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-auth-backend-module-atlassian-provider@0.4.1-next.1
+ - @backstage/plugin-auth-backend-module-auth0-provider@0.2.1-next.1
+ - @backstage/plugin-auth-backend-module-aws-alb-provider@0.4.1-next.2
+ - @backstage/plugin-auth-backend-module-azure-easyauth-provider@0.2.6-next.1
+ - @backstage/plugin-auth-backend-module-bitbucket-provider@0.3.1-next.1
+ - @backstage/plugin-auth-backend-module-bitbucket-server-provider@0.2.1-next.1
+ - @backstage/plugin-auth-backend-module-cloudflare-access-provider@0.4.1-next.1
+ - @backstage/plugin-auth-backend-module-gcp-iap-provider@0.4.1-next.1
+ - @backstage/plugin-auth-backend-module-github-provider@0.3.1-next.1
+ - @backstage/plugin-auth-backend-module-gitlab-provider@0.3.1-next.1
+ - @backstage/plugin-auth-backend-module-google-provider@0.3.1-next.1
+ - @backstage/plugin-auth-backend-module-microsoft-provider@0.3.1-next.1
+ - @backstage/plugin-auth-backend-module-oauth2-proxy-provider@0.2.6-next.1
+ - @backstage/plugin-auth-backend-module-onelogin-provider@0.3.1-next.1
+ - @backstage/plugin-auth-node@0.6.1-next.1
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+
## 0.24.4-next.1
### Patch Changes
diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json
index d971a0f6b0..46a4e04364 100644
--- a/plugins/auth-backend/package.json
+++ b/plugins/auth-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-backend",
- "version": "0.24.4-next.1",
+ "version": "0.24.4-next.2",
"description": "A Backstage backend plugin that handles authentication",
"backstage": {
"role": "backend-plugin",
diff --git a/plugins/auth-backend/src/setupTests.ts b/plugins/auth-backend/src/setupTests.ts
index f7c56ef27d..5af5f05360 100644
--- a/plugins/auth-backend/src/setupTests.ts
+++ b/plugins/auth-backend/src/setupTests.ts
@@ -19,5 +19,5 @@ import { TestDatabases } from '@backstage/backend-test-utils';
export {};
TestDatabases.setDefaults({
- ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'],
+ ids: ['MYSQL_8', 'POSTGRES_17', 'POSTGRES_13', 'SQLITE_3'],
});
diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md
index 762227a2e8..10cb852de8 100644
--- a/plugins/auth-react/CHANGELOG.md
+++ b/plugins/auth-react/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-auth-react
+## 0.1.13-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/errors@1.2.7
+
## 0.1.13-next.0
### Patch Changes
diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json
index 7a008cd28b..d30e73ed2c 100644
--- a/plugins/auth-react/package.json
+++ b/plugins/auth-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-auth-react",
- "version": "0.1.13-next.0",
+ "version": "0.1.13-next.1",
"description": "Web library for the auth plugin",
"backstage": {
"role": "web-library",
diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md
index a7922129cf..c298b964e0 100644
--- a/plugins/bitbucket-cloud-common/CHANGELOG.md
+++ b/plugins/bitbucket-cloud-common/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/plugin-bitbucket-cloud-common
+## 0.2.28-next.0
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.2-next.0
+
## 0.2.27
### Patch Changes
diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json
index ffc2f097b0..d279781744 100644
--- a/plugins/bitbucket-cloud-common/package.json
+++ b/plugins/bitbucket-cloud-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-bitbucket-cloud-common",
- "version": "0.2.27",
+ "version": "0.2.28-next.0",
"description": "Common functionalities for bitbucket-cloud plugins",
"backstage": {
"role": "common-library",
diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md
index b670e59d45..80b26f796f 100644
--- a/plugins/catalog-backend-module-aws/CHANGELOG.md
+++ b/plugins/catalog-backend-module-aws/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-catalog-backend-module-aws
+## 0.4.9-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.8.2-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+ - @backstage/plugin-kubernetes-common@0.9.4-next.0
+
## 0.4.9-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json
index 03679af187..afcb5dfba2 100644
--- a/plugins/catalog-backend-module-aws/package.json
+++ b/plugins/catalog-backend-module-aws/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-aws",
- "version": "0.4.9-next.1",
+ "version": "0.4.9-next.2",
"description": "A Backstage catalog backend module that helps integrate towards AWS",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md
index 2011ebce75..ae84239ea5 100644
--- a/plugins/catalog-backend-module-azure/CHANGELOG.md
+++ b/plugins/catalog-backend-module-azure/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-catalog-backend-module-azure
+## 0.3.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+
## 0.3.3-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json
index b8d5ceb1bc..d14317c251 100644
--- a/plugins/catalog-backend-module-azure/package.json
+++ b/plugins/catalog-backend-module-azure/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-azure",
- "version": "0.3.3-next.1",
+ "version": "0.3.3-next.2",
"description": "A Backstage catalog backend module that helps integrate towards Azure",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
index 553d1ffd4d..69132a9095 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
+++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/plugin-catalog-backend-module-bitbucket-cloud
+## 0.4.6-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-bitbucket-cloud-common@0.2.28-next.0
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+
## 0.4.6-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json
index d014fb3f5d..647d77f7cf 100644
--- a/plugins/catalog-backend-module-bitbucket-cloud/package.json
+++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud",
- "version": "0.4.6-next.1",
+ "version": "0.4.6-next.2",
"description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
index 18074e247c..0447d8afb1 100644
--- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
+++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-catalog-backend-module-bitbucket-server
+## 0.3.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+
## 0.3.3-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json
index 2fa3b67510..245d4ed3f2 100644
--- a/plugins/catalog-backend-module-bitbucket-server/package.json
+++ b/plugins/catalog-backend-module-bitbucket-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-bitbucket-server",
- "version": "0.3.3-next.1",
+ "version": "0.3.3-next.2",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "catalog",
diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md
index 8010ff9a4c..c916830d5e 100644
--- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md
+++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-catalog-backend-module-gerrit
+## 0.2.8-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+
## 0.2.8-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json
index cf2c9a7f69..4c97d0f621 100644
--- a/plugins/catalog-backend-module-gerrit/package.json
+++ b/plugins/catalog-backend-module-gerrit/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-gerrit",
- "version": "0.2.8-next.1",
+ "version": "0.2.8-next.2",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "catalog",
diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md
index aa55fde053..ad5726a01d 100644
--- a/plugins/catalog-backend-module-github-org/CHANGELOG.md
+++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-catalog-backend-module-github-org
+## 0.3.8-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-backend-module-github@0.7.11-next.2
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+
## 0.3.8-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json
index 9726d6234d..34b4a647a6 100644
--- a/plugins/catalog-backend-module-github-org/package.json
+++ b/plugins/catalog-backend-module-github-org/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-github-org",
- "version": "0.3.8-next.1",
+ "version": "0.3.8-next.2",
"description": "The github-org backend module for the catalog plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md
index c30eedc661..b4eda44153 100644
--- a/plugins/catalog-backend-module-github/CHANGELOG.md
+++ b/plugins/catalog-backend-module-github/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/plugin-catalog-backend-module-github
+## 0.7.11-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.32.0-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+
## 0.7.11-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json
index 37fa762684..fb92604868 100644
--- a/plugins/catalog-backend-module-github/package.json
+++ b/plugins/catalog-backend-module-github/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-github",
- "version": "0.7.11-next.1",
+ "version": "0.7.11-next.2",
"description": "A Backstage catalog backend module that helps integrate towards GitHub",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md
index 0b32b8ead8..efe993b351 100644
--- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md
+++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-catalog-backend-module-gitlab-org
+## 0.2.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/plugin-catalog-backend-module-gitlab@0.6.4-next.2
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+
## 0.2.7-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json
index 3f1761fc1e..aae1f2afd6 100644
--- a/plugins/catalog-backend-module-gitlab-org/package.json
+++ b/plugins/catalog-backend-module-gitlab-org/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-gitlab-org",
- "version": "0.2.7-next.1",
+ "version": "0.2.7-next.2",
"description": "The gitlab-org backend module for the catalog plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md
index 9d22620a51..719d614474 100644
--- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md
+++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-catalog-backend-module-gitlab
+## 0.6.4-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-defaults@0.8.2-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+
## 0.6.4-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json
index ae29409cb0..3c005aba46 100644
--- a/plugins/catalog-backend-module-gitlab/package.json
+++ b/plugins/catalog-backend-module-gitlab/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-gitlab",
- "version": "0.6.4-next.1",
+ "version": "0.6.4-next.2",
"description": "A Backstage catalog backend module that helps integrate towards GitLab",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
index eda4c60f2c..fba30c8ede 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
+++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-catalog-backend-module-incremental-ingestion
+## 0.6.4-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.32.0-next.2
+ - @backstage/backend-defaults@0.8.2-next.2
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+ - @backstage/plugin-permission-common@0.8.4
+
## 0.6.4-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json
index b3515b63f8..0e2811b3d7 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/package.json
+++ b/plugins/catalog-backend-module-incremental-ingestion/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-incremental-ingestion",
- "version": "0.6.4-next.1",
+ "version": "0.6.4-next.2",
"description": "An entity provider for streaming large asset sources into the catalog",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts
index a8862cfc2a..bed216b5de 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts
+++ b/plugins/catalog-backend-module-incremental-ingestion/src/database/IncrementalIngestionDatabaseManager.test.ts
@@ -24,7 +24,7 @@ jest.setTimeout(60_000);
describe('IncrementalIngestionDatabaseManager', () => {
const databases = TestDatabases.create({
- ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
+ ids: ['POSTGRES_17', 'POSTGRES_13', 'SQLITE_3'],
});
it.each(databases.eachSupportedId())(
diff --git a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts
index 95b3f203c4..877e32acfb 100644
--- a/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts
+++ b/plugins/catalog-backend-module-incremental-ingestion/src/module/WrapperProviders.test.ts
@@ -25,7 +25,7 @@ jest.setTimeout(60_000);
describe('WrapperProviders', () => {
const applyDatabaseMigrations = jest.fn();
const databases = TestDatabases.create({
- ids: ['POSTGRES_16', 'POSTGRES_12', 'SQLITE_3', 'MYSQL_8'],
+ ids: ['POSTGRES_17', 'POSTGRES_13', 'SQLITE_3', 'MYSQL_8'],
});
const config = new ConfigReader({});
const logger = mockServices.logger.mock();
diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md
index 1b7986cc0d..058e8d215a 100644
--- a/plugins/catalog-backend-module-ldap/CHANGELOG.md
+++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/plugin-catalog-backend-module-ldap
+## 0.11.3-next.2
+
+### Patch Changes
+
+- e43f41b: Fix `config.d.ts` for `ldapOrg` being incorrect. The documentation says a single
+ object or an array are accepted, but the definition only allows an object.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+
## 0.11.3-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json
index b05c573c21..488f5d625e 100644
--- a/plugins/catalog-backend-module-ldap/package.json
+++ b/plugins/catalog-backend-module-ldap/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-ldap",
- "version": "0.11.3-next.1",
+ "version": "0.11.3-next.2",
"description": "A Backstage catalog backend module that helps integrate towards LDAP",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md
index f876afe4c1..81985ea512 100644
--- a/plugins/catalog-backend-module-logs/CHANGELOG.md
+++ b/plugins/catalog-backend-module-logs/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-catalog-backend-module-logs
+## 0.1.8-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-backend@1.32.0-next.2
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+
## 0.1.8-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json
index aa6de8ef45..b40c106851 100644
--- a/plugins/catalog-backend-module-logs/package.json
+++ b/plugins/catalog-backend-module-logs/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-logs",
- "version": "0.1.8-next.1",
+ "version": "0.1.8-next.2",
"description": "A module that subscribes to catalog releated events and logs them.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md
index 105f93513a..33cd7610c2 100644
--- a/plugins/catalog-backend-module-openapi/CHANGELOG.md
+++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-catalog-backend-module-openapi
+## 0.2.8-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+
## 0.2.8-next.1
### Patch Changes
diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json
index a687e83c20..59307ffc4f 100644
--- a/plugins/catalog-backend-module-openapi/package.json
+++ b/plugins/catalog-backend-module-openapi/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend-module-openapi",
- "version": "0.2.8-next.1",
+ "version": "0.2.8-next.2",
"description": "A Backstage catalog backend module that helps with OpenAPI specifications",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md
index a93e5e663a..ca1305c43b 100644
--- a/plugins/catalog-backend/CHANGELOG.md
+++ b/plugins/catalog-backend/CHANGELOG.md
@@ -1,5 +1,28 @@
# @backstage/plugin-catalog-backend
+## 1.32.0-next.2
+
+### Patch Changes
+
+- 4306303: Added a fix in `@backstage/plugin-catalog-backend` to prevent duplicate path keys in entity search if only casing is different.
+- 5243aa4: Fixed an issue occurred when authorizing permissions using custom rules passed via the `PermissionsRegistryService`.
+- Updated dependencies
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-openapi-utils@0.5.1-next.1
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-permission-node@0.8.9-next.1
+ - @backstage/plugin-search-backend-module-catalog@0.3.2-next.1
+ - @backstage/plugin-search-common@1.2.17
+
## 1.32.0-next.1
### Minor Changes
diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json
index b62b3d89f2..02f4878304 100644
--- a/plugins/catalog-backend/package.json
+++ b/plugins/catalog-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend",
- "version": "1.32.0-next.1",
+ "version": "1.32.0-next.2",
"description": "The Backstage backend plugin that provides the Backstage catalog",
"backstage": {
"role": "backend-plugin",
diff --git a/plugins/catalog-backend/src/setupTests.ts b/plugins/catalog-backend/src/setupTests.ts
index f7c56ef27d..5af5f05360 100644
--- a/plugins/catalog-backend/src/setupTests.ts
+++ b/plugins/catalog-backend/src/setupTests.ts
@@ -19,5 +19,5 @@ import { TestDatabases } from '@backstage/backend-test-utils';
export {};
TestDatabases.setDefaults({
- ids: ['MYSQL_8', 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'],
+ ids: ['MYSQL_8', 'POSTGRES_17', 'POSTGRES_13', 'SQLITE_3'],
});
diff --git a/plugins/catalog-backend/src/tests/performance/getEntitiesPerformance.test.ts b/plugins/catalog-backend/src/tests/performance/getEntitiesPerformance.test.ts
index 9d1bb8d80a..aa76bace54 100644
--- a/plugins/catalog-backend/src/tests/performance/getEntitiesPerformance.test.ts
+++ b/plugins/catalog-backend/src/tests/performance/getEntitiesPerformance.test.ts
@@ -40,7 +40,7 @@ import { describePerformanceTest, performanceTraceEnabled } from './lib/env';
jest.setTimeout(600_000);
const databases = TestDatabases.create({
- ids: [/* 'MYSQL_8', */ 'POSTGRES_16', /* 'POSTGRES_12',*/ 'SQLITE_3'],
+ ids: [/* 'MYSQL_8', */ 'POSTGRES_17', /* 'POSTGRES_13',*/ 'SQLITE_3'],
disableDocker: false,
});
diff --git a/plugins/catalog-backend/src/tests/performance/getProcessableEntitiesPerformance.test.ts b/plugins/catalog-backend/src/tests/performance/getProcessableEntitiesPerformance.test.ts
index 8218ae82cc..e2ed160b42 100644
--- a/plugins/catalog-backend/src/tests/performance/getProcessableEntitiesPerformance.test.ts
+++ b/plugins/catalog-backend/src/tests/performance/getProcessableEntitiesPerformance.test.ts
@@ -25,7 +25,7 @@ import { describePerformanceTest, performanceTraceEnabled } from './lib/env';
jest.setTimeout(600_000);
const databases = TestDatabases.create({
- ids: [/* 'MYSQL_8', */ 'POSTGRES_16', /* 'POSTGRES_12',*/ 'SQLITE_3'],
+ ids: [/* 'MYSQL_8', */ 'POSTGRES_17', /* 'POSTGRES_13',*/ 'SQLITE_3'],
disableDocker: false,
});
diff --git a/plugins/catalog-backend/src/tests/performance/providerDeltaMutations.test.ts b/plugins/catalog-backend/src/tests/performance/providerDeltaMutations.test.ts
index 6e95c2535a..377843643b 100644
--- a/plugins/catalog-backend/src/tests/performance/providerDeltaMutations.test.ts
+++ b/plugins/catalog-backend/src/tests/performance/providerDeltaMutations.test.ts
@@ -26,7 +26,7 @@ import { DeferredEntity } from '@backstage/plugin-catalog-node';
jest.setTimeout(600_000);
const databases = TestDatabases.create({
- ids: [/* 'MYSQL_8', */ 'POSTGRES_16', /* 'POSTGRES_12',*/ 'SQLITE_3'],
+ ids: [/* 'MYSQL_8', */ 'POSTGRES_17', /* 'POSTGRES_13',*/ 'SQLITE_3'],
disableDocker: false,
});
diff --git a/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts b/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts
index a4d843649e..d0bf7c2cd8 100644
--- a/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts
+++ b/plugins/catalog-backend/src/tests/performance/stitchingPerformance.test.ts
@@ -143,7 +143,7 @@ class Tracker {
describePerformanceTest('stitchingPerformance', () => {
const databases = TestDatabases.create({
- ids: [/* 'MYSQL_8', */ 'POSTGRES_16', 'POSTGRES_12', 'SQLITE_3'],
+ ids: [/* 'MYSQL_8', */ 'POSTGRES_17', 'POSTGRES_13', 'SQLITE_3'],
});
it.each(databases.eachSupportedId())(
diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md
index 0be8f15993..0da3f48885 100644
--- a/plugins/catalog-graph/CHANGELOG.md
+++ b/plugins/catalog-graph/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-catalog-graph
+## 0.4.17-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-compat-api@0.4.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/types@1.2.1
+
## 0.4.17-next.1
### Patch Changes
diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json
index ac54e13330..3d9bdb96c1 100644
--- a/plugins/catalog-graph/package.json
+++ b/plugins/catalog-graph/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-graph",
- "version": "0.4.17-next.1",
+ "version": "0.4.17-next.2",
"backstage": {
"role": "frontend-plugin",
"pluginId": "catalog-graph",
diff --git a/plugins/catalog-graph/report-alpha.api.md b/plugins/catalog-graph/report-alpha.api.md
index 4cc884e7d7..0687efce05 100644
--- a/plugins/catalog-graph/report-alpha.api.md
+++ b/plugins/catalog-graph/report-alpha.api.md
@@ -45,7 +45,7 @@ const _default: FrontendPlugin<
height: number | undefined;
} & {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
height?: number | undefined;
@@ -61,7 +61,7 @@ const _default: FrontendPlugin<
relationPairs?: [string, string][] | undefined;
} & {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef<
diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md
index a6c8a4725d..0760281898 100644
--- a/plugins/catalog-import/CHANGELOG.md
+++ b/plugins/catalog-import/CHANGELOG.md
@@ -1,5 +1,23 @@
# @backstage/plugin-catalog-import
+## 0.12.11-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-compat-api@0.4.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/errors@1.2.7
+ - @backstage/integration-react@1.2.5-next.0
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.12.11-next.1
### Patch Changes
diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json
index 05ce3a5982..49eaed96f8 100644
--- a/plugins/catalog-import/package.json
+++ b/plugins/catalog-import/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-import",
- "version": "0.12.11-next.1",
+ "version": "0.12.11-next.2",
"description": "A Backstage plugin the helps you import entities into your catalog",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md
index b925168e39..adcacbc699 100644
--- a/plugins/catalog-react/CHANGELOG.md
+++ b/plugins/catalog-react/CHANGELOG.md
@@ -1,5 +1,42 @@
# @backstage/plugin-catalog-react
+## 1.16.0-next.2
+
+### Minor Changes
+
+- 7f57365: Add support for a new entity predicate syntax when defining `filter`s related to the blueprints exported via `/alpha` for the new frontend system. For more information, see the [entity filters documentation](https://backstage.io/docs/features/software-catalog/catalog-customization#advanced-customization#entity-filters).
+- 247a40b: Introduces a new `EntityHeaderBlueprint` that allows you to override the default entity page header.
+
+ ```jsx
+ import { EntityHeaderBlueprint } from '@backstage/plugin-catalog-react/alpha';
+
+ EntityHeaderBlueprint.make({
+ name: 'my-default-header',
+ params: {
+ loader: () =>
+ import('./MyDefaultHeader').then(m => ),
+ },
+ });
+ ```
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/frontend-test-utils@0.3.0-next.2
+ - @backstage/core-compat-api@0.4.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/errors@1.2.7
+ - @backstage/integration-react@1.2.5-next.0
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-permission-react@0.4.31
+
## 1.16.0-next.1
### Patch Changes
@@ -78,7 +115,7 @@
The layout components receive card elements and can render them as they see fit. Cards is an array of objects with the following properties:
- element: `JSx.Element`;
- - type: `"peek" | "info" | "full" | undefined`;
+ - type: `"summary" | "info" | "content" | undefined`;
### Usage example
@@ -115,14 +152,14 @@
{cards
- .filter(card => card.type === 'peek')
+ .filter(card => card.type === 'summary')
.map((card, index) => (
{card.element}
))}
{cards
- .filter(card => !card.type || card.type === 'full')
+ .filter(card => !card.type || card.type === 'content')
.map((card, index) => (
{card.element}
@@ -176,9 +213,9 @@
Initially the following three types are supported:
- - `peek`: small vertical cards that provide information at a glance, for example recent builds, deployments, and service health.
+ - `summary`: small vertical cards that provide information at a glance, for example recent builds, deployments, and service health.
- `info`: medium size cards with high priority and frequently used information such as common actions, entity metadata, and links.
- - `full`: Large cards that are more feature rich with more information, typically used by plugins that don't quite need the full content view and want to show a card instead.
+ - `content`: Large cards that are more feature rich with more information, typically used by plugins that don't quite need the content content view and want to show a card instead.
### Usage examples
diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json
index 33893deefc..3809a18c09 100644
--- a/plugins/catalog-react/package.json
+++ b/plugins/catalog-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-react",
- "version": "1.16.0-next.1",
+ "version": "1.16.0-next.2",
"description": "A frontend library that helps other Backstage plugins interact with the catalog",
"backstage": {
"role": "web-library",
diff --git a/plugins/catalog-react/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md
index c191e55f2a..4a154356d1 100644
--- a/plugins/catalog-react/report-alpha.api.md
+++ b/plugins/catalog-react/report-alpha.api.md
@@ -137,11 +137,11 @@ export const EntityCardBlueprint: ExtensionBlueprint<{
inputs: {};
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
dataRefs: {
filterFunction: ConfigurableExtensionDataRef<
@@ -163,7 +163,7 @@ export const EntityCardBlueprint: ExtensionBlueprint<{
}>;
// @alpha (undocumented)
-export type EntityCardType = 'peek' | 'info' | 'full';
+export type EntityCardType = 'summary' | 'info' | 'content';
// @alpha
export const EntityContentBlueprint: ExtensionBlueprint<{
diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx
index f4e518799e..70c2b9d849 100644
--- a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx
+++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.test.tsx
@@ -175,9 +175,9 @@ describe('EntityCardBlueprint', () => {
},
"type": {
"enum": [
- "peek",
+ "summary",
"info",
- "full",
+ "content",
],
"type": "string",
},
diff --git a/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx
index bae7d4e668..c085912123 100644
--- a/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx
+++ b/plugins/catalog-react/src/alpha/blueprints/extensionData.tsx
@@ -54,13 +54,13 @@ export const entityContentGroupDataRef = createExtensionDataRef().with({
* Available entity card types
*/
export const entityCardTypes = [
- 'peek',
+ 'summary',
'info',
- 'full',
+ 'content',
] as const satisfies readonly EntityCardType[];
/** @alpha */
-export type EntityCardType = 'peek' | 'info' | 'full';
+export type EntityCardType = 'summary' | 'info' | 'content';
/** @internal */
export const entityCardTypeDataRef =
diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md
index 112c60b877..8eac4566bc 100644
--- a/plugins/catalog-unprocessed-entities/CHANGELOG.md
+++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-catalog-unprocessed-entities
+## 0.2.15-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/core-compat-api@0.4.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/errors@1.2.7
+
## 0.2.15-next.1
### Patch Changes
diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json
index c18e957860..e3540de818 100644
--- a/plugins/catalog-unprocessed-entities/package.json
+++ b/plugins/catalog-unprocessed-entities/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-unprocessed-entities",
- "version": "0.2.15-next.1",
+ "version": "0.2.15-next.2",
"backstage": {
"role": "frontend-plugin",
"pluginId": "catalog-unprocessed-entities",
diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md
index 08b7c83c50..896249a08c 100644
--- a/plugins/catalog/CHANGELOG.md
+++ b/plugins/catalog/CHANGELOG.md
@@ -1,5 +1,75 @@
# @backstage/plugin-catalog
+## 1.28.0-next.2
+
+### Minor Changes
+
+- 247a40b: Now a custom entity page header can be passed as input to the default entity page.
+- 93533bd: The order in which group tabs appear on the entity page has been changed.
+
+ ### Before
+
+ Previously, entity contents determined the order in which groups were rendered, so a group was rendered as soon as its first entity content was detected.
+
+ ### After
+
+ Groups are now rendered first by default based on their order in the `app-config.yaml` file:
+
+ ```diff
+ app:
+ extensions:
+ - page:catalog/entity:
+ + config:
+ + groups:
+ + # this will be the first tab of the default entity page
+ + - deployment:
+ + title: Deployment
+ + # this will be the second tab of the default entiy page
+ + - documentation:
+ + title: Documentation
+ ```
+
+ If you wish to place a normal tab before a group, you must add the tab to a group and place the group in the order you wish it to appear on the entity page (groups that contains only one tab are rendered as normal tabs).
+
+ ```diff
+ app:
+ extensions:
+ - page:catalog/entity:
+ config:
+ groups:
+ + # Example placing the overview tab first
+ + - overview:
+ + title: Overview
+ - deployment:
+ title: Deployment
+ # this will be the second tab of the default entiy page
+ - documentation:
+ title: Documentation
+ - entity-content:catalog/overview:
+ + config:
+ + group: 'overview'
+ ```
+
+### Patch Changes
+
+- 31731b0: Internal refactor to avoid `expiry-map` dependency.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-compat-api@0.4.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/plugin-search-react@1.8.7-next.2
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/errors@1.2.7
+ - @backstage/integration-react@1.2.5-next.0
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-common@1.1.3
+ - @backstage/plugin-permission-react@0.4.31
+ - @backstage/plugin-scaffolder-common@1.5.10-next.0
+ - @backstage/plugin-search-common@1.2.17
+
## 1.28.0-next.1
### Minor Changes
diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json
index 2d12ec5367..67711d4a35 100644
--- a/plugins/catalog/package.json
+++ b/plugins/catalog/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog",
- "version": "1.28.0-next.1",
+ "version": "1.28.0-next.2",
"description": "The Backstage plugin for browsing the Backstage catalog",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/catalog/report-alpha.api.md b/plugins/catalog/report-alpha.api.md
index ca6d01b0ac..77d8dbec50 100644
--- a/plugins/catalog/report-alpha.api.md
+++ b/plugins/catalog/report-alpha.api.md
@@ -354,11 +354,11 @@ const _default: FrontendPlugin<
name: 'about';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef
@@ -395,11 +395,11 @@ const _default: FrontendPlugin<
name: 'depends-on-components';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef
@@ -436,11 +436,11 @@ const _default: FrontendPlugin<
name: 'depends-on-resources';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef
@@ -477,11 +477,11 @@ const _default: FrontendPlugin<
name: 'has-components';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef
@@ -518,11 +518,11 @@ const _default: FrontendPlugin<
name: 'has-resources';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef
@@ -559,11 +559,11 @@ const _default: FrontendPlugin<
name: 'has-subcomponents';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef
@@ -600,11 +600,11 @@ const _default: FrontendPlugin<
name: 'has-subdomains';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef
@@ -641,11 +641,11 @@ const _default: FrontendPlugin<
name: 'has-systems';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef
@@ -682,11 +682,11 @@ const _default: FrontendPlugin<
name: 'labels';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef
@@ -723,11 +723,11 @@ const _default: FrontendPlugin<
name: 'links';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef
diff --git a/plugins/catalog/src/alpha/DefaultEntityContentLayout.tsx b/plugins/catalog/src/alpha/DefaultEntityContentLayout.tsx
index 7a73f991bc..c5cbe452f2 100644
--- a/plugins/catalog/src/alpha/DefaultEntityContentLayout.tsx
+++ b/plugins/catalog/src/alpha/DefaultEntityContentLayout.tsx
@@ -35,14 +35,14 @@ import { HorizontalScrollGrid } from '@backstage/core-components';
const useStyles = makeStyles<
Theme,
- { infoCards: boolean; peekCards: boolean; fullCards: boolean }
+ { infoCards: boolean; summaryCards: boolean; contentCards: boolean }
>(theme => ({
root: {
display: 'flex',
flexFlow: 'column nowrap',
gap: theme.spacing(3),
},
- fullArea: {
+ contentArea: {
display: 'flex',
flexFlow: 'column',
gap: theme.spacing(3),
@@ -56,10 +56,10 @@ const useStyles = makeStyles<
gap: theme.spacing(3),
minWidth: 0,
},
- peekArea: {
+ summaryArea: {
margin: theme.spacing(1.5), // To counteract MUI negative grid margin
},
- peekCard: {
+ summaryCard: {
flex: '0 0 auto',
'& + &': {
marginLeft: theme.spacing(3),
@@ -69,9 +69,9 @@ const useStyles = makeStyles<
root: {
display: 'grid',
gap: 0,
- gridTemplateAreas: ({ peekCards }) => `
- "${peekCards ? 'peek' : 'full'} info"
- "full info"
+ gridTemplateAreas: ({ summaryCards }) => `
+ "${summaryCards ? 'summary' : 'content'} info"
+ "content info"
`,
gridTemplateColumns: ({ infoCards }) => (infoCards ? '2fr 1fr' : '1fr'),
alignItems: 'start',
@@ -82,11 +82,11 @@ const useStyles = makeStyles<
top: theme.spacing(3),
marginLeft: theme.spacing(3),
},
- fullArea: {
- gridArea: 'full',
+ contentArea: {
+ gridArea: 'content',
},
- peekArea: {
- gridArea: 'peek',
+ summaryArea: {
+ gridArea: 'summary',
marginBottom: theme.spacing(3),
},
},
@@ -124,13 +124,15 @@ export function DefaultEntityContentLayout(props: EntityContentLayoutProps) {
const { cards } = props;
const infoCards = cards.filter(card => card.type === 'info');
- const peekCards = cards.filter(card => card.type === 'peek');
- const fullCards = cards.filter(card => !card.type || card.type === 'full');
+ const summaryCards = cards.filter(card => card.type === 'summary');
+ const contentCards = cards.filter(
+ card => !card.type || card.type === 'content',
+ );
const classes = useStyles({
infoCards: !!infoCards.length,
- peekCards: !!peekCards.length,
- fullCards: !!fullCards.length,
+ summaryCards: !!summaryCards.length,
+ contentCards: !!contentCards.length,
});
return (
@@ -142,18 +144,18 @@ export function DefaultEntityContentLayout(props: EntityContentLayoutProps) {
{infoCards.map(card => card.element)}
) : null}
- {peekCards.length > 0 ? (
-
+ {summaryCards.length > 0 ? (
+
- {peekCards.map(card => (
- {card.element}
+ {summaryCards.map(card => (
+ {card.element}
))}
) : null}
- {fullCards.length > 0 ? (
-
- {fullCards.map(card => card.element)}
+ {contentCards.length > 0 ? (
+
+ {contentCards.map(card => card.element)}
) : null}
diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md
index 43558f5f80..e1a8813f81 100644
--- a/plugins/config-schema/CHANGELOG.md
+++ b/plugins/config-schema/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-config-schema
+## 0.1.66-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 0.1.66-next.0
### Patch Changes
diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json
index 389c49f002..0d99cb3123 100644
--- a/plugins/config-schema/package.json
+++ b/plugins/config-schema/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-config-schema",
- "version": "0.1.66-next.0",
+ "version": "0.1.66-next.1",
"description": "A Backstage plugin that lets you browse the configuration schema of your app",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md
index b33f9bb723..66c2cf9b81 100644
--- a/plugins/devtools-backend/CHANGELOG.md
+++ b/plugins/devtools-backend/CHANGELOG.md
@@ -1,5 +1,21 @@
# @backstage/plugin-devtools-backend
+## 0.5.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/config-loader@1.10.0-next.0
+ - @backstage/backend-defaults@0.8.2-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/cli-common@0.1.15
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-devtools-common@0.1.15
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-permission-node@0.8.9-next.1
+
## 0.5.3-next.1
### Patch Changes
diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json
index 6e27f0042f..55f90afc28 100644
--- a/plugins/devtools-backend/package.json
+++ b/plugins/devtools-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-devtools-backend",
- "version": "0.5.3-next.1",
+ "version": "0.5.3-next.2",
"backstage": {
"role": "backend-plugin",
"pluginId": "devtools",
diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md
index 38c350f233..f2731bc555 100644
--- a/plugins/devtools/CHANGELOG.md
+++ b/plugins/devtools/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/plugin-devtools
+## 0.1.25-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/core-compat-api@0.4.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-devtools-common@0.1.15
+ - @backstage/plugin-permission-react@0.4.31
+
## 0.1.25-next.1
### Patch Changes
diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json
index 374559121c..3386cc4b36 100644
--- a/plugins/devtools/package.json
+++ b/plugins/devtools/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-devtools",
- "version": "0.1.25-next.1",
+ "version": "0.1.25-next.2",
"backstage": {
"role": "frontend-plugin",
"pluginId": "devtools",
diff --git a/plugins/events-backend-module-aws-sqs/CHANGELOG.md b/plugins/events-backend-module-aws-sqs/CHANGELOG.md
index e09be1d750..12c383da36 100644
--- a/plugins/events-backend-module-aws-sqs/CHANGELOG.md
+++ b/plugins/events-backend-module-aws-sqs/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-events-backend-module-aws-sqs
+## 0.4.9-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/types@1.2.1
+
## 0.4.9-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-aws-sqs/package.json b/plugins/events-backend-module-aws-sqs/package.json
index 240a8e09c1..b415cfed2a 100644
--- a/plugins/events-backend-module-aws-sqs/package.json
+++ b/plugins/events-backend-module-aws-sqs/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-aws-sqs",
- "version": "0.4.9-next.1",
+ "version": "0.4.9-next.2",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-azure/CHANGELOG.md b/plugins/events-backend-module-azure/CHANGELOG.md
index a76d0b376b..7964007e85 100644
--- a/plugins/events-backend-module-azure/CHANGELOG.md
+++ b/plugins/events-backend-module-azure/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-events-backend-module-azure
+## 0.2.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+
## 0.2.18-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-azure/package.json b/plugins/events-backend-module-azure/package.json
index e0597bfc04..4faaec2894 100644
--- a/plugins/events-backend-module-azure/package.json
+++ b/plugins/events-backend-module-azure/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-azure",
- "version": "0.2.18-next.1",
+ "version": "0.2.18-next.2",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
index 864917568d..1d48f9c126 100644
--- a/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
+++ b/plugins/events-backend-module-bitbucket-cloud/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-events-backend-module-bitbucket-cloud
+## 0.2.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+
## 0.2.18-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-bitbucket-cloud/package.json b/plugins/events-backend-module-bitbucket-cloud/package.json
index 47aaf4c4d6..3dee4b53df 100644
--- a/plugins/events-backend-module-bitbucket-cloud/package.json
+++ b/plugins/events-backend-module-bitbucket-cloud/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-bitbucket-cloud",
- "version": "0.2.18-next.1",
+ "version": "0.2.18-next.2",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-gerrit/CHANGELOG.md b/plugins/events-backend-module-gerrit/CHANGELOG.md
index 4002632b7e..cfc38635fa 100644
--- a/plugins/events-backend-module-gerrit/CHANGELOG.md
+++ b/plugins/events-backend-module-gerrit/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-events-backend-module-gerrit
+## 0.2.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+
## 0.2.18-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-gerrit/package.json b/plugins/events-backend-module-gerrit/package.json
index 8474da935b..6d4a0b163c 100644
--- a/plugins/events-backend-module-gerrit/package.json
+++ b/plugins/events-backend-module-gerrit/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-gerrit",
- "version": "0.2.18-next.1",
+ "version": "0.2.18-next.2",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md
index 4d04573ae3..ff479d22a1 100644
--- a/plugins/events-backend-module-github/CHANGELOG.md
+++ b/plugins/events-backend-module-github/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-events-backend-module-github
+## 0.2.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+
## 0.2.18-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json
index bee5b66f05..f2d171bb42 100644
--- a/plugins/events-backend-module-github/package.json
+++ b/plugins/events-backend-module-github/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-github",
- "version": "0.2.18-next.1",
+ "version": "0.2.18-next.2",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-module-gitlab/CHANGELOG.md b/plugins/events-backend-module-gitlab/CHANGELOG.md
index aa1e4aae9b..02c3f7ea71 100644
--- a/plugins/events-backend-module-gitlab/CHANGELOG.md
+++ b/plugins/events-backend-module-gitlab/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-events-backend-module-gitlab
+## 0.2.18-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+
## 0.2.18-next.1
### Patch Changes
diff --git a/plugins/events-backend-module-gitlab/package.json b/plugins/events-backend-module-gitlab/package.json
index db978f4ea5..ba1b090769 100644
--- a/plugins/events-backend-module-gitlab/package.json
+++ b/plugins/events-backend-module-gitlab/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-module-gitlab",
- "version": "0.2.18-next.1",
+ "version": "0.2.18-next.2",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "events",
diff --git a/plugins/events-backend-test-utils/CHANGELOG.md b/plugins/events-backend-test-utils/CHANGELOG.md
index f15325e3df..f04f62f673 100644
--- a/plugins/events-backend-test-utils/CHANGELOG.md
+++ b/plugins/events-backend-test-utils/CHANGELOG.md
@@ -1,5 +1,12 @@
# @backstage/plugin-events-backend-test-utils
+## 0.1.42-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.9-next.2
+
## 0.1.42-next.1
### Patch Changes
diff --git a/plugins/events-backend-test-utils/package.json b/plugins/events-backend-test-utils/package.json
index 748b75c5c7..88185e9745 100644
--- a/plugins/events-backend-test-utils/package.json
+++ b/plugins/events-backend-test-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend-test-utils",
- "version": "0.1.42-next.1",
+ "version": "0.1.42-next.2",
"description": "The plugin-events-backend-test-utils for @backstage/plugin-events-node",
"backstage": {
"role": "node-library",
diff --git a/plugins/events-backend/CHANGELOG.md b/plugins/events-backend/CHANGELOG.md
index 2eb7ac5419..fa58ec66d9 100644
--- a/plugins/events-backend/CHANGELOG.md
+++ b/plugins/events-backend/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/plugin-events-backend
+## 0.4.4-next.2
+
+### Patch Changes
+
+- b95aa77: add `addHttpPostBodyParser` to events extension to allow body parse customization. This feature will enhance flexibility in handling HTTP POST requests in event-related operations.
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-openapi-utils@0.5.1-next.1
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 0.4.4-next.1
### Patch Changes
diff --git a/plugins/events-backend/README.md b/plugins/events-backend/README.md
index 7f88452ff6..219b60f213 100644
--- a/plugins/events-backend/README.md
+++ b/plugins/events-backend/README.md
@@ -140,6 +140,48 @@ export const eventsModuleYourFeature = createBackendModule({
});
```
+### Request Body Parse
+
+We need to parse the request body before we can validate it. We have some default parsers but you can provide your own when necessary.
+
+```ts
+import { eventsExtensionPoint } from '@backstage/plugin-events-node/alpha';
+
+// [...]
+
+export const eventsModuleYourFeature = createBackendModule({
+ pluginId: 'events',
+ moduleId: 'your-feature',
+ register(env) {
+ // [...]
+ env.registerInit({
+ deps: {
+ // [...]
+ events: eventsExtensionPoint,
+ // [...]
+ },
+ async init({ /* ... */ events /*, ... */ }) {
+ // [...]
+ events.addHttpPostBodyParser({
+ contentType: 'application/x-www-form-urlencoded',
+ parser: async (req, _topic) => {
+ return {
+ bodyParsed: req.body.toString('utf-8'),
+ bodyBuffer: req.body,
+ encoding: 'utf-8',
+ };
+ },
+ });
+ },
+ });
+ },
+});
+```
+
+We have the following default parsers:
+
+- `application/json`
+
#### Legacy Backend System
```ts
diff --git a/plugins/events-backend/package.json b/plugins/events-backend/package.json
index 6d7690d9dc..44acf9b557 100644
--- a/plugins/events-backend/package.json
+++ b/plugins/events-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-backend",
- "version": "0.4.4-next.1",
+ "version": "0.4.4-next.2",
"backstage": {
"role": "backend-plugin",
"pluginId": "events",
diff --git a/plugins/events-backend/report.api.md b/plugins/events-backend/report.api.md
index fb9f5fb237..286bc8317d 100644
--- a/plugins/events-backend/report.api.md
+++ b/plugins/events-backend/report.api.md
@@ -11,6 +11,7 @@ import { EventPublisher } from '@backstage/plugin-events-node';
import { EventsService } from '@backstage/plugin-events-node';
import { EventSubscriber } from '@backstage/plugin-events-node';
import express from 'express';
+import { HttpBodyParser } from '@backstage/plugin-events-node';
import { HttpPostIngressOptions } from '@backstage/plugin-events-node';
import { Logger } from 'winston';
import { LoggerService } from '@backstage/backend-plugin-api';
@@ -58,6 +59,9 @@ export class HttpPostIngressEventPublisher {
ingresses?: {
[topic: string]: Omit
;
};
+ bodyParsers?: {
+ [contentType: string]: HttpBodyParser;
+ };
logger: LoggerService;
}): HttpPostIngressEventPublisher;
}
diff --git a/plugins/events-backend/src/service/EventsPlugin.test.ts b/plugins/events-backend/src/service/EventsPlugin.test.ts
index 35813d7b8e..12a0f63002 100644
--- a/plugins/events-backend/src/service/EventsPlugin.test.ts
+++ b/plugins/events-backend/src/service/EventsPlugin.test.ts
@@ -141,7 +141,7 @@ describe('eventsPlugin', () => {
}
const databases = TestDatabases.create({
- ids: ['SQLITE_3', 'MYSQL_8', 'POSTGRES_9', 'POSTGRES_13', 'POSTGRES_16'],
+ ids: ['SQLITE_3', 'MYSQL_8', 'POSTGRES_13', 'POSTGRES_17'],
});
async function mockKnexFactory(databaseId: TestDatabaseId) {
diff --git a/plugins/events-backend/src/service/EventsPlugin.ts b/plugins/events-backend/src/service/EventsPlugin.ts
index a837e69c32..1b43f0c527 100644
--- a/plugins/events-backend/src/service/EventsPlugin.ts
+++ b/plugins/events-backend/src/service/EventsPlugin.ts
@@ -24,6 +24,7 @@ import {
} from '@backstage/plugin-events-node/alpha';
import {
eventsServiceRef,
+ HttpBodyParserOptions,
HttpPostIngressOptions,
} from '@backstage/plugin-events-node';
import Router from 'express-promise-router';
@@ -31,7 +32,8 @@ import { HttpPostIngressEventPublisher } from './http';
import { createEventBusRouter } from './hub';
class EventsExtensionPointImpl implements EventsExtensionPoint {
- #httpPostIngresses: HttpPostIngressOptions[] = [];
+ readonly #httpPostIngresses: HttpPostIngressOptions[] = [];
+ readonly #httpBodyParsers: HttpBodyParserOptions[] = [];
setEventBroker(_: any): void {
throw new Error(
@@ -55,9 +57,17 @@ class EventsExtensionPointImpl implements EventsExtensionPoint {
this.#httpPostIngresses.push(options);
}
+ addHttpPostBodyParser(options: HttpBodyParserOptions): void {
+ this.#httpBodyParsers.push(options);
+ }
+
get httpPostIngresses() {
return this.#httpPostIngresses;
}
+
+ get httpBodyParsers() {
+ return this.#httpBodyParsers;
+ }
}
/**
@@ -99,10 +109,18 @@ export const eventsPlugin = createBackendPlugin({
]),
);
+ const bodyParsers = Object.fromEntries(
+ extensionPoint.httpBodyParsers.map(option => [
+ option.contentType,
+ option.parser,
+ ]),
+ );
+
const http = HttpPostIngressEventPublisher.fromConfig({
config,
events,
ingresses,
+ bodyParsers,
logger,
});
const eventsRouter = Router();
diff --git a/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.test.ts b/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.test.ts
index 45ee055fa9..23e7a4212d 100644
--- a/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.test.ts
+++ b/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.test.ts
@@ -22,6 +22,7 @@ import request from 'supertest';
import { HttpPostIngressEventPublisher } from './HttpPostIngressEventPublisher';
import { mockServices } from '@backstage/backend-test-utils';
import { MiddlewareFactory } from '@backstage/backend-defaults/rootHttpRouter';
+import { HttpBodyParser } from '@backstage/plugin-events-node';
const middleware = MiddlewareFactory.create({
logger: mockServices.logger.mock(),
@@ -287,7 +288,8 @@ describe('HttpPostIngressEventPublisher', () => {
expect(response.body).toEqual(
expect.objectContaining({
error: {
- message: 'Unsupported media type: text/plain',
+ message:
+ 'Unsupported media type: text/plain. You need to provide a custom body parser for this media type using the EventsExtensionPoint.',
name: 'UnsupportedMediaTypeError',
statusCode: 415,
},
@@ -297,6 +299,134 @@ describe('HttpPostIngressEventPublisher', () => {
);
});
+ it('with a text/plain body parser implementation', async () => {
+ const config = new ConfigReader({
+ events: {
+ http: {
+ topics: ['testA'],
+ },
+ },
+ });
+
+ const router = Router();
+ const app = express().use(router);
+ const events = new TestEventsService();
+
+ const bodyParser: HttpBodyParser = async (req, _topic) => {
+ return {
+ bodyParsed: req.body.toString('utf-8'),
+ bodyBuffer: req.body,
+ encoding: 'utf-8',
+ };
+ };
+
+ const publisher = HttpPostIngressEventPublisher.fromConfig({
+ config,
+ events,
+ bodyParsers: {
+ 'text/plain': bodyParser,
+ },
+ logger,
+ });
+ publisher.bind(router);
+ router.use(middleware.error());
+
+ const response = await request(app)
+ .post('/http/testA')
+ .type('text/plain')
+ .timeout(1000)
+ .send('Textual information');
+ expect(response.status).toBe(202);
+ });
+
+ it('with a invalid media type', async () => {
+ const config = new ConfigReader({
+ events: {
+ http: {
+ topics: ['testA'],
+ },
+ },
+ });
+
+ const router = Router();
+ const app = express().use(router);
+ const events = new TestEventsService();
+
+ const publisher = HttpPostIngressEventPublisher.fromConfig({
+ config,
+ events,
+ logger,
+ });
+ publisher.bind(router);
+ router.use(middleware.error());
+
+ const response = await request(app)
+ .post('/http/testA')
+ .type('not-valid-content/plain')
+ .timeout(1000)
+ .send('Textual information');
+ expect(response.status).toBe(415);
+ expect(response.body).toEqual(
+ expect.objectContaining({
+ error: {
+ message:
+ 'Unsupported media type: not-valid-content/plain. You need to provide a custom body parser for this media type using the EventsExtensionPoint.',
+ name: 'UnsupportedMediaTypeError',
+ statusCode: 415,
+ },
+ request: { method: 'POST', url: '/http/testA' },
+ response: { statusCode: 415 },
+ }),
+ );
+ });
+
+ it('with a custom application/json body parser implementation', async () => {
+ const config = new ConfigReader({
+ events: {
+ http: {
+ topics: ['testA'],
+ },
+ },
+ });
+
+ const router = Router();
+ const app = express().use(router);
+ const events = new TestEventsService();
+ const customParse = jest.fn();
+
+ const bodyParser: HttpBodyParser = async (
+ req,
+ _parsedMediaType,
+ _topic,
+ ) => {
+ customParse();
+ return {
+ bodyParsed: JSON.parse(req.body.toString('utf-8')),
+ bodyBuffer: req.body,
+ encoding: 'utf-8',
+ };
+ };
+
+ const publisher = HttpPostIngressEventPublisher.fromConfig({
+ config,
+ events,
+ bodyParsers: {
+ 'application/json': bodyParser,
+ },
+ logger,
+ });
+ publisher.bind(router);
+ router.use(middleware.error());
+
+ const response = await request(app)
+ .post('/http/testA')
+ .type('application/json')
+ .timeout(1000)
+ .send(JSON.stringify({ testA: 'data' }));
+ expect(response.status).toBe(202);
+ expect(customParse).toHaveBeenCalled();
+ });
+
it('with validator', async () => {
const config = new ConfigReader({
events: {
diff --git a/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts b/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts
index a14bb7243e..8ac6542e81 100644
--- a/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts
+++ b/plugins/events-backend/src/service/http/HttpPostIngressEventPublisher.ts
@@ -16,35 +16,18 @@
import { LoggerService } from '@backstage/backend-plugin-api';
import { Config } from '@backstage/config';
-import { CustomErrorBase } from '@backstage/errors';
import {
EventsService,
+ HttpBodyParser,
HttpPostIngressOptions,
RequestValidator,
} from '@backstage/plugin-events-node';
import contentType from 'content-type';
import express from 'express';
import Router from 'express-promise-router';
+import { defaultHttpBodyParsers } from './body-parser';
import { RequestValidationContextImpl } from './validation';
-
-class UnsupportedCharsetError extends CustomErrorBase {
- name = 'UnsupportedCharsetError' as const;
- statusCode = 415 as const;
-
- constructor(charset: string) {
- super(`Unsupported charset: ${charset}`);
- }
-}
-
-class UnsupportedMediaTypeError extends CustomErrorBase {
- name = 'UnsupportedMediaTypeError' as const;
- statusCode = 415 as const;
-
- constructor(mediaType?: string) {
- super(`Unsupported media type: ${mediaType ?? 'unknown'}`);
- }
-}
-
+import { UnsupportedMediaTypeError } from './errors';
/**
* Publishes events received from their origin (e.g., webhook events from an SCM system)
* via HTTP POST endpoint and passes the request body as event payload to the registered subscribers.
@@ -57,6 +40,7 @@ export class HttpPostIngressEventPublisher {
config: Config;
events: EventsService;
ingresses?: { [topic: string]: Omit };
+ bodyParsers?: { [contentType: string]: HttpBodyParser };
logger: LoggerService;
}): HttpPostIngressEventPublisher {
const topics =
@@ -71,7 +55,14 @@ export class HttpPostIngressEventPublisher {
}
});
- return new HttpPostIngressEventPublisher(env.events, env.logger, ingresses);
+ const parsers = { ...defaultHttpBodyParsers, ...env.bodyParsers };
+
+ return new HttpPostIngressEventPublisher(
+ env.events,
+ env.logger,
+ ingresses,
+ parsers,
+ );
}
private constructor(
@@ -80,6 +71,9 @@ export class HttpPostIngressEventPublisher {
private readonly ingresses: {
[topic: string]: Omit;
},
+ private readonly bodyParsers: {
+ [contentType: string]: HttpBodyParser;
+ },
) {}
bind(router: express.Router): void {
@@ -108,32 +102,18 @@ export class HttpPostIngressEventPublisher {
const logger = this.logger;
router.post(path, async (request, response) => {
- const requestBody = request.body;
- if (!Buffer.isBuffer(requestBody)) {
- throw new Error(
- `Failed to retrieve raw body from incoming event for topic ${topic}; not a buffer: ${typeof requestBody}`,
- );
+ const requestContentType = contentType.parse(request);
+ const bodyParser = this.bodyParsers[requestContentType.type ?? ''];
+
+ if (!bodyParser) {
+ throw new UnsupportedMediaTypeError(requestContentType.type);
}
- const bodyBuffer: Buffer = requestBody;
- const parsedContentType = contentType.parse(request);
- if (
- !parsedContentType.type ||
- parsedContentType.type !== 'application/json'
- ) {
- throw new UnsupportedMediaTypeError(parsedContentType.type);
- }
-
- const encoding = parsedContentType.parameters.charset ?? 'utf-8';
- if (!Buffer.isEncoding(encoding)) {
- throw new UnsupportedCharsetError(encoding);
- }
-
- const bodyString = bodyBuffer.toString(encoding);
- const bodyParsed =
- parsedContentType.type === 'application/json'
- ? JSON.parse(bodyString)
- : bodyString;
+ const { bodyParsed, bodyBuffer, encoding } = await bodyParser(
+ request,
+ requestContentType,
+ topic,
+ );
if (validator) {
const requestDetails = {
diff --git a/plugins/events-backend/src/service/http/body-parser/HttpApplicationJsonBodyParser.ts b/plugins/events-backend/src/service/http/body-parser/HttpApplicationJsonBodyParser.ts
new file mode 100644
index 0000000000..fa716a5db0
--- /dev/null
+++ b/plugins/events-backend/src/service/http/body-parser/HttpApplicationJsonBodyParser.ts
@@ -0,0 +1,44 @@
+/*
+ * 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 { HttpBodyParser } from '@backstage/plugin-events-node';
+import { UnsupportedCharsetError } from '../errors';
+
+export const HttpApplicationJsonBodyParser: HttpBodyParser = async (
+ request,
+ parsedMediaType,
+ topic,
+) => {
+ const requestBody = request.body;
+ if (!Buffer.isBuffer(requestBody)) {
+ throw new Error(
+ `Failed to retrieve raw body from incoming event for topic ${topic}; not a buffer: ${typeof requestBody}`,
+ );
+ }
+
+ const bodyBuffer: Buffer = requestBody;
+
+ const encoding = parsedMediaType.parameters.charset ?? 'utf-8';
+ if (!Buffer.isEncoding(encoding)) {
+ throw new UnsupportedCharsetError(encoding);
+ }
+
+ const bodyString = bodyBuffer.toString(encoding);
+ const bodyParsed =
+ parsedMediaType.type === 'application/json'
+ ? JSON.parse(bodyString)
+ : bodyString;
+ return { bodyParsed, bodyBuffer, encoding };
+};
diff --git a/plugins/events-backend/src/service/http/body-parser/index.ts b/plugins/events-backend/src/service/http/body-parser/index.ts
new file mode 100644
index 0000000000..7fc17ef235
--- /dev/null
+++ b/plugins/events-backend/src/service/http/body-parser/index.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 { HttpBodyParser } from '@backstage/plugin-events-node';
+import { HttpApplicationJsonBodyParser } from './HttpApplicationJsonBodyParser';
+
+export const defaultHttpBodyParsers: { [contentType: string]: HttpBodyParser } =
+ {
+ 'application/json': HttpApplicationJsonBodyParser,
+ };
diff --git a/plugins/events-backend/src/service/http/errors.ts b/plugins/events-backend/src/service/http/errors.ts
new file mode 100644
index 0000000000..583f1a6b9b
--- /dev/null
+++ b/plugins/events-backend/src/service/http/errors.ts
@@ -0,0 +1,38 @@
+/*
+ * 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 { CustomErrorBase } from '@backstage/errors';
+
+export class UnsupportedCharsetError extends CustomErrorBase {
+ name = 'UnsupportedCharsetError' as const;
+ statusCode = 415 as const;
+
+ constructor(charset: string) {
+ super(`Unsupported charset: ${charset}`);
+ }
+}
+
+export class UnsupportedMediaTypeError extends CustomErrorBase {
+ name = 'UnsupportedMediaTypeError' as const;
+ statusCode = 415 as const;
+
+ constructor(mediaType?: string) {
+ super(
+ `Unsupported media type: ${
+ mediaType ?? 'unknown'
+ }. You need to provide a custom body parser for this media type using the EventsExtensionPoint.`,
+ );
+ }
+}
diff --git a/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts b/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts
index ade462a920..959341cbc0 100644
--- a/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts
+++ b/plugins/events-backend/src/service/hub/DatabaseEventBusStore.test.ts
@@ -24,7 +24,7 @@ import { DatabaseEventBusStore } from './DatabaseEventBusStore';
const logger = mockServices.logger.mock();
const databases = TestDatabases.create({
- ids: ['POSTGRES_9', 'POSTGRES_13', 'POSTGRES_16'],
+ ids: ['POSTGRES_13', 'POSTGRES_17'],
});
const maybeDescribe =
diff --git a/plugins/events-node/CHANGELOG.md b/plugins/events-node/CHANGELOG.md
index 9261dca0c4..f7322fa635 100644
--- a/plugins/events-node/CHANGELOG.md
+++ b/plugins/events-node/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-events-node
+## 0.4.9-next.2
+
+### Patch Changes
+
+- b95aa77: add `addHttpPostBodyParser` to events extension to allow body parse customization. This feature will enhance flexibility in handling HTTP POST requests in event-related operations.
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 0.4.9-next.1
### Patch Changes
diff --git a/plugins/events-node/package.json b/plugins/events-node/package.json
index 5c8ba8223d..d0fbb4917a 100644
--- a/plugins/events-node/package.json
+++ b/plugins/events-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-events-node",
- "version": "0.4.9-next.1",
+ "version": "0.4.9-next.2",
"description": "The plugin-events-node module for @backstage/plugin-events-backend",
"backstage": {
"role": "node-library",
@@ -55,7 +55,11 @@
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/errors": "workspace:^",
"@backstage/types": "workspace:^",
+ "@types/content-type": "^1.1.8",
+ "@types/express": "^4.17.6",
+ "content-type": "^1.0.5",
"cross-fetch": "^4.0.0",
+ "express": "^4.17.1",
"uri-template": "^2.0.0"
},
"devDependencies": {
diff --git a/plugins/events-node/report-alpha.api.md b/plugins/events-node/report-alpha.api.md
index f61048ac94..ba8b7e9adf 100644
--- a/plugins/events-node/report-alpha.api.md
+++ b/plugins/events-node/report-alpha.api.md
@@ -7,10 +7,13 @@ import { EventBroker } from '@backstage/plugin-events-node';
import { EventPublisher } from '@backstage/plugin-events-node';
import { EventSubscriber } from '@backstage/plugin-events-node';
import { ExtensionPoint } from '@backstage/backend-plugin-api';
+import { HttpBodyParserOptions } from '@backstage/plugin-events-node';
import { HttpPostIngressOptions } from '@backstage/plugin-events-node';
// @alpha (undocumented)
export interface EventsExtensionPoint {
+ // (undocumented)
+ addHttpPostBodyParser(options: HttpBodyParserOptions): void;
// (undocumented)
addHttpPostIngress(options: HttpPostIngressOptions): void;
// @deprecated (undocumented)
diff --git a/plugins/events-node/report.api.md b/plugins/events-node/report.api.md
index 8bc3145dd5..ada3e2dc1a 100644
--- a/plugins/events-node/report.api.md
+++ b/plugins/events-node/report.api.md
@@ -7,6 +7,8 @@ import { AuthService } from '@backstage/backend-plugin-api';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { LifecycleService } from '@backstage/backend-plugin-api';
import { LoggerService } from '@backstage/backend-plugin-api';
+import { ParsedMediaType } from 'content-type';
+import { Request as Request_2 } from 'express';
import { RootConfigService } from '@backstage/backend-plugin-api';
import { ServiceFactory } from '@backstage/backend-plugin-api';
import { ServiceRef } from '@backstage/backend-plugin-api';
@@ -109,6 +111,28 @@ export interface EventSubscriber {
supportsEventTopics(): string[];
}
+// @public (undocumented)
+export type HttpBodyParsed = {
+ bodyParsed: any;
+ bodyBuffer: Buffer;
+ encoding: string;
+};
+
+// @public (undocumented)
+export type HttpBodyParser = (
+ request: Request_2,
+ parsedMediaType: ParsedMediaType,
+ topic: string,
+) => Promise;
+
+// @public (undocumented)
+export interface HttpBodyParserOptions {
+ // (undocumented)
+ contentType: string;
+ // (undocumented)
+ parser: HttpBodyParser;
+}
+
// @public (undocumented)
export interface HttpPostIngressOptions {
// (undocumented)
diff --git a/plugins/events-node/src/api/http/HttpBodyParserOptions.ts b/plugins/events-node/src/api/http/HttpBodyParserOptions.ts
new file mode 100644
index 0000000000..cedaecf28e
--- /dev/null
+++ b/plugins/events-node/src/api/http/HttpBodyParserOptions.ts
@@ -0,0 +1,24 @@
+/*
+ * 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 { HttpBodyParser } from './body-parser';
+
+/**
+ * @public
+ */
+export interface HttpBodyParserOptions {
+ contentType: string;
+ parser: HttpBodyParser;
+}
diff --git a/plugins/events-node/src/api/http/body-parser/HttpBodyParser.ts b/plugins/events-node/src/api/http/body-parser/HttpBodyParser.ts
new file mode 100644
index 0000000000..c0b6a46b95
--- /dev/null
+++ b/plugins/events-node/src/api/http/body-parser/HttpBodyParser.ts
@@ -0,0 +1,35 @@
+/*
+ * 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 { Request } from 'express';
+import { ParsedMediaType } from 'content-type';
+/**
+ * @public
+ */
+export type HttpBodyParsed = {
+ bodyParsed: any;
+ bodyBuffer: Buffer;
+ encoding: string;
+};
+
+/**
+ * @public
+ */
+export type HttpBodyParser = (
+ request: Request,
+ parsedMediaType: ParsedMediaType,
+ topic: string,
+) => Promise;
diff --git a/plugins/events-node/src/api/http/body-parser/index.ts b/plugins/events-node/src/api/http/body-parser/index.ts
new file mode 100644
index 0000000000..e1376841ae
--- /dev/null
+++ b/plugins/events-node/src/api/http/body-parser/index.ts
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
+export * from './HttpBodyParser';
diff --git a/plugins/events-node/src/api/http/index.ts b/plugins/events-node/src/api/http/index.ts
index 9206819a4a..c0908583ad 100644
--- a/plugins/events-node/src/api/http/index.ts
+++ b/plugins/events-node/src/api/http/index.ts
@@ -15,4 +15,6 @@
*/
export type { HttpPostIngressOptions } from './HttpPostIngressOptions';
+export type { HttpBodyParserOptions } from './HttpBodyParserOptions';
export * from './validation';
+export * from './body-parser';
diff --git a/plugins/events-node/src/extensions.ts b/plugins/events-node/src/extensions.ts
index 90add52563..72be2db51f 100644
--- a/plugins/events-node/src/extensions.ts
+++ b/plugins/events-node/src/extensions.ts
@@ -19,6 +19,7 @@ import {
EventBroker,
EventPublisher,
EventSubscriber,
+ HttpBodyParserOptions,
HttpPostIngressOptions,
} from '@backstage/plugin-events-node';
@@ -46,6 +47,8 @@ export interface EventsExtensionPoint {
): void;
addHttpPostIngress(options: HttpPostIngressOptions): void;
+
+ addHttpPostBodyParser(options: HttpBodyParserOptions): void;
}
/**
diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md
index a3918aa963..0d6e6ff09d 100644
--- a/plugins/example-todo-list/CHANGELOG.md
+++ b/plugins/example-todo-list/CHANGELOG.md
@@ -1,5 +1,13 @@
# @internal/plugin-todo-list
+## 1.0.37-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/core-plugin-api@1.10.4
+
## 1.0.37-next.0
### Patch Changes
diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json
index 16787f1a41..b9fc557436 100644
--- a/plugins/example-todo-list/package.json
+++ b/plugins/example-todo-list/package.json
@@ -1,6 +1,6 @@
{
"name": "@internal/plugin-todo-list",
- "version": "1.0.37-next.0",
+ "version": "1.0.37-next.1",
"backstage": {
"role": "frontend-plugin",
"pluginId": "todo-list",
diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md
index 4f05840812..8e4b6cebf8 100644
--- a/plugins/home-react/CHANGELOG.md
+++ b/plugins/home-react/CHANGELOG.md
@@ -1,5 +1,13 @@
# @backstage/plugin-home-react
+## 0.1.24-next.1
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/core-plugin-api@1.10.4
+
## 0.1.24-next.0
### Patch Changes
diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json
index cb9cb2c3a8..0972d84ee5 100644
--- a/plugins/home-react/package.json
+++ b/plugins/home-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-home-react",
- "version": "0.1.24-next.0",
+ "version": "0.1.24-next.1",
"description": "A Backstage plugin that contains react components helps you build a home page",
"backstage": {
"role": "web-library",
diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md
index 385743a6a7..d7c3f97a91 100644
--- a/plugins/home/CHANGELOG.md
+++ b/plugins/home/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/plugin-home
+## 0.8.6-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/core-app-api@1.16.0-next.0
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-compat-api@0.4.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/theme@0.6.4
+ - @backstage/plugin-home-react@0.1.24-next.1
+
## 0.8.6-next.1
### Patch Changes
diff --git a/plugins/home/package.json b/plugins/home/package.json
index 3a77281f3c..4d0c8ec410 100644
--- a/plugins/home/package.json
+++ b/plugins/home/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-home",
- "version": "0.8.6-next.1",
+ "version": "0.8.6-next.2",
"description": "A Backstage plugin that helps you build a home page",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/home/src/homePageComponents/StarredEntities/Content.test.tsx b/plugins/home/src/homePageComponents/StarredEntities/Content.test.tsx
index ad26482961..b2522cda96 100644
--- a/plugins/home/src/homePageComponents/StarredEntities/Content.test.tsx
+++ b/plugins/home/src/homePageComponents/StarredEntities/Content.test.tsx
@@ -50,7 +50,24 @@ describe('StarredEntitiesContent', () => {
mockedApi.toggleStarred('component:default/mock-starred-entity-2');
mockedApi.toggleStarred('component:default/mock-starred-entity-3');
- const mockCatalogApi = catalogApiMock({ entities });
+ const mockCatalogApi = catalogApiMock.mock({
+ getEntitiesByRefs: jest.fn().mockImplementation(async ({ fields }) => {
+ const expectedFields = [
+ 'kind',
+ 'metadata.namespace',
+ 'metadata.name',
+ 'spec.type',
+ 'metadata.title',
+ 'spec.profile.displayName',
+ ];
+ expectedFields.forEach(field => {
+ expect(fields).toContain(field);
+ });
+ return {
+ items: entities,
+ };
+ }),
+ });
const { getByText, queryByText } = await renderInTestApp(
!!e);
}, [catalogApi, starredEntities]);
diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md
index 89b1504307..2a86d12499 100644
--- a/plugins/kubernetes-cluster/CHANGELOG.md
+++ b/plugins/kubernetes-cluster/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/plugin-kubernetes-cluster
+## 0.0.23-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/plugin-kubernetes-common@0.9.4-next.0
+ - @backstage/plugin-kubernetes-react@0.5.5-next.2
+ - @backstage/plugin-permission-react@0.4.31
+
## 0.0.23-next.1
### Patch Changes
diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json
index 1456b9ca46..3ca7dcd445 100644
--- a/plugins/kubernetes-cluster/package.json
+++ b/plugins/kubernetes-cluster/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes-cluster",
- "version": "0.0.23-next.1",
+ "version": "0.0.23-next.2",
"description": "A Backstage plugin that shows details of Kubernetes clusters",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md
index d4b78369b7..8dccce8ce2 100644
--- a/plugins/kubernetes-react/CHANGELOG.md
+++ b/plugins/kubernetes-react/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-kubernetes-react
+## 0.5.5-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-kubernetes-common@0.9.4-next.0
+
## 0.5.5-next.1
### Patch Changes
diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json
index 4261b27e2c..42eefd8abc 100644
--- a/plugins/kubernetes-react/package.json
+++ b/plugins/kubernetes-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes-react",
- "version": "0.5.5-next.1",
+ "version": "0.5.5-next.2",
"description": "Web library for the kubernetes-react plugin",
"backstage": {
"role": "web-library",
diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md
index bed3f31283..ee13cb7f65 100644
--- a/plugins/kubernetes/CHANGELOG.md
+++ b/plugins/kubernetes/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/plugin-kubernetes
+## 0.12.5-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-compat-api@0.4.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/plugin-kubernetes-common@0.9.4-next.0
+ - @backstage/plugin-kubernetes-react@0.5.5-next.2
+ - @backstage/plugin-permission-react@0.4.31
+
## 0.12.5-next.1
### Patch Changes
diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json
index 0cc5c777bf..379e65b66f 100644
--- a/plugins/kubernetes/package.json
+++ b/plugins/kubernetes/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-kubernetes",
- "version": "0.12.5-next.1",
+ "version": "0.12.5-next.2",
"description": "A Backstage plugin that integrates towards Kubernetes",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md
index abbd5b59cd..39a8dda7b4 100644
--- a/plugins/notifications-backend-module-email/CHANGELOG.md
+++ b/plugins/notifications-backend-module-email/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/plugin-notifications-backend-module-email
+## 0.3.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/integration-aws-node@0.1.15
+ - @backstage/types@1.2.1
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-notifications-node@0.2.13-next.2
+
## 0.3.7-next.1
### Patch Changes
diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json
index c4d15d3391..ff486b7e3d 100644
--- a/plugins/notifications-backend-module-email/package.json
+++ b/plugins/notifications-backend-module-email/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-notifications-backend-module-email",
- "version": "0.3.7-next.1",
+ "version": "0.3.7-next.2",
"description": "The email backend module for the notifications plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/notifications-backend/CHANGELOG.md b/plugins/notifications-backend/CHANGELOG.md
index 47dcd6bf2e..94f8ee65e9 100644
--- a/plugins/notifications-backend/CHANGELOG.md
+++ b/plugins/notifications-backend/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/plugin-notifications-backend
+## 0.5.4-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-auth-node@0.6.1-next.1
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-notifications-node@0.2.13-next.2
+ - @backstage/plugin-signals-node@0.1.18-next.2
+
## 0.5.4-next.1
### Patch Changes
diff --git a/plugins/notifications-backend/package.json b/plugins/notifications-backend/package.json
index c96539caf8..1cbd11a933 100644
--- a/plugins/notifications-backend/package.json
+++ b/plugins/notifications-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-notifications-backend",
- "version": "0.5.4-next.1",
+ "version": "0.5.4-next.2",
"backstage": {
"role": "backend-plugin",
"pluginId": "notifications",
diff --git a/plugins/notifications-node/CHANGELOG.md b/plugins/notifications-node/CHANGELOG.md
index b4a20fe9c3..2291723d45 100644
--- a/plugins/notifications-node/CHANGELOG.md
+++ b/plugins/notifications-node/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-notifications-node
+## 0.2.13-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-signals-node@0.1.18-next.2
+
## 0.2.13-next.1
### Patch Changes
diff --git a/plugins/notifications-node/package.json b/plugins/notifications-node/package.json
index e4703e2e07..8277b03975 100644
--- a/plugins/notifications-node/package.json
+++ b/plugins/notifications-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-notifications-node",
- "version": "0.2.13-next.1",
+ "version": "0.2.13-next.2",
"description": "Node.js library for the notifications plugin",
"backstage": {
"role": "node-library",
diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md
index f07540616e..04d83f31f3 100644
--- a/plugins/notifications/CHANGELOG.md
+++ b/plugins/notifications/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/plugin-notifications
+## 0.5.3-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/core-compat-api@0.4.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/errors@1.2.7
+ - @backstage/theme@0.6.4
+ - @backstage/types@1.2.1
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-signals-react@0.0.10
+
## 0.5.3-next.1
### Patch Changes
diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json
index 6ff5b7b767..76a769db2c 100644
--- a/plugins/notifications/package.json
+++ b/plugins/notifications/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-notifications",
- "version": "0.5.3-next.1",
+ "version": "0.5.3-next.2",
"backstage": {
"role": "frontend-plugin",
"pluginId": "notifications",
diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md
index 2b598e177e..34018e412b 100644
--- a/plugins/org-react/CHANGELOG.md
+++ b/plugins/org-react/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-org-react
+## 0.1.36-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+
## 0.1.36-next.1
### Patch Changes
diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json
index 675967452a..170ce814ac 100644
--- a/plugins/org-react/package.json
+++ b/plugins/org-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-org-react",
- "version": "0.1.36-next.1",
+ "version": "0.1.36-next.2",
"backstage": {
"role": "web-library",
"pluginId": "org",
diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md
index f11beec465..e8cbeb48b9 100644
--- a/plugins/org/CHANGELOG.md
+++ b/plugins/org/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/plugin-org
+## 0.6.37-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-compat-api@0.4.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/plugin-catalog-common@1.1.3
+
## 0.6.37-next.1
### Patch Changes
diff --git a/plugins/org/package.json b/plugins/org/package.json
index 37322b36ec..7a93b931f0 100644
--- a/plugins/org/package.json
+++ b/plugins/org/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-org",
- "version": "0.6.37-next.1",
+ "version": "0.6.37-next.2",
"description": "A Backstage plugin that helps you create entity pages for your organization",
"backstage": {
"role": "frontend-plugin",
diff --git a/plugins/org/report-alpha.api.md b/plugins/org/report-alpha.api.md
index f85e7090dd..f6a79a968a 100644
--- a/plugins/org/report-alpha.api.md
+++ b/plugins/org/report-alpha.api.md
@@ -24,11 +24,11 @@ const _default: FrontendPlugin<
name: 'group-profile';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef<
@@ -69,11 +69,11 @@ const _default: FrontendPlugin<
name: 'members-list';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef<
@@ -114,11 +114,11 @@ const _default: FrontendPlugin<
name: 'ownership';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef<
@@ -159,11 +159,11 @@ const _default: FrontendPlugin<
name: 'user-profile';
config: {
filter: EntityPredicate | undefined;
- type: 'full' | 'info' | 'peek' | undefined;
+ type: 'content' | 'summary' | 'info' | undefined;
};
configInput: {
filter?: EntityPredicate | undefined;
- type?: 'full' | 'info' | 'peek' | undefined;
+ type?: 'content' | 'summary' | 'info' | undefined;
};
output:
| ConfigurableExtensionDataRef<
diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json
index ee255293a8..7bd280b4fd 100644
--- a/plugins/permission-node/package.json
+++ b/plugins/permission-node/package.json
@@ -56,7 +56,6 @@
"test": "backstage-cli package test"
},
"dependencies": {
- "@backstage/backend-common": "^0.25.0",
"@backstage/backend-plugin-api": "workspace:^",
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
diff --git a/plugins/permission-node/report.api.md b/plugins/permission-node/report.api.md
index 71298a7d81..fca13ce5f0 100644
--- a/plugins/permission-node/report.api.md
+++ b/plugins/permission-node/report.api.md
@@ -29,7 +29,6 @@ import { PermissionsServiceRequestOptions } from '@backstage/backend-plugin-api'
import { PolicyDecision } from '@backstage/plugin-permission-common';
import { QueryPermissionRequest } from '@backstage/plugin-permission-common';
import { ResourcePermission } from '@backstage/plugin-permission-common';
-import { TokenManager } from '@backstage/backend-common';
import { z } from 'zod';
// @public
@@ -385,8 +384,7 @@ export class ServerPermissionClient implements PermissionsService {
config: Config,
options: {
discovery: DiscoveryService;
- tokenManager?: TokenManager;
- auth?: AuthService;
+ auth: AuthService;
},
): ServerPermissionClient;
}
diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts
index c34a8fe930..fc9f554955 100644
--- a/plugins/permission-node/src/ServerPermissionClient.ts
+++ b/plugins/permission-node/src/ServerPermissionClient.ts
@@ -14,10 +14,6 @@
* limitations under the License.
*/
-import {
- TokenManager,
- createLegacyAuthAdapters,
-} from '@backstage/backend-common';
import {
AuthService,
BackstageCredentials,
@@ -53,28 +49,14 @@ export class ServerPermissionClient implements PermissionsService {
config: Config,
options: {
discovery: DiscoveryService;
- /** @deprecated This option will be removed in the future, provide a the auth option instead */
- tokenManager?: TokenManager;
- auth?: AuthService;
+ auth: AuthService;
},
) {
- const { discovery, tokenManager } = options;
+ const { auth, discovery } = options;
const permissionClient = new PermissionClient({ discovery, config });
const permissionEnabled =
config.getOptionalBoolean('permission.enabled') ?? false;
- if (
- permissionEnabled &&
- tokenManager &&
- (tokenManager as any).isInsecureServerTokenManager
- ) {
- throw new Error(
- 'Service-to-service authentication must be configured before enabling permissions. Read more here https://backstage.io/docs/auth/service-to-service-auth',
- );
- }
-
- const { auth } = createLegacyAuthAdapters(options);
-
return new ServerPermissionClient({
auth,
permissionClient,
diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md
index 3bcee9d120..7374517b1a 100644
--- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-scaffolder-backend-module-azure
+## 0.2.7-next.2
+
+### Patch Changes
+
+- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.2.7-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json
index 2fed1560f5..b08a1677a4 100644
--- a/plugins/scaffolder-backend-module-azure/package.json
+++ b/plugins/scaffolder-backend-module-azure/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-azure",
- "version": "0.2.7-next.1",
+ "version": "0.2.7-next.2",
"description": "The azure module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-azure/report.api.md b/plugins/scaffolder-backend-module-azure/report.api.md
index e15aba0fa4..159bf02a00 100644
--- a/plugins/scaffolder-backend-module-azure/report.api.md
+++ b/plugins/scaffolder-backend-module-azure/report.api.md
@@ -27,7 +27,9 @@ export function createPublishAzureAction(options: {
gitCommitMessage?: string;
gitAuthorName?: string;
gitAuthorEmail?: string;
+ signCommit?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
```
diff --git a/plugins/scaffolder-backend-module-azure/src/actions/azure.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.ts
index 518d2acbf7..acb4754520 100644
--- a/plugins/scaffolder-backend-module-azure/src/actions/azure.ts
+++ b/plugins/scaffolder-backend-module-azure/src/actions/azure.ts
@@ -20,10 +20,10 @@ import {
ScmIntegrationRegistry,
} from '@backstage/integration';
import {
- initRepoAndPush,
- getRepoSourceDirectory,
- parseRepoUrl,
createTemplateAction,
+ getRepoSourceDirectory,
+ initRepoAndPush,
+ parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import { GitRepositoryCreateOptions } from 'azure-devops-node-api/interfaces/GitInterfaces';
import {
@@ -54,6 +54,7 @@ export function createPublishAzureAction(options: {
gitCommitMessage?: string;
gitAuthorName?: string;
gitAuthorEmail?: string;
+ signCommit?: boolean;
}>({
id: 'publish:azure',
examples,
@@ -103,6 +104,11 @@ export function createPublishAzureAction(options: {
type: 'string',
description: 'The token to use for authorization to Azure',
},
+ signCommit: {
+ title: 'Sign commit',
+ type: 'boolean',
+ description: 'Sign commit with configured PGP private key',
+ },
},
},
output: {
@@ -134,6 +140,7 @@ export function createPublishAzureAction(options: {
gitCommitMessage = 'initial commit',
gitAuthorName,
gitAuthorEmail,
+ signCommit,
} = ctx.input;
const { project, repo, host, organization } = parseRepoUrl(
@@ -151,6 +158,7 @@ export function createPublishAzureAction(options: {
const credentialProvider =
DefaultAzureDevOpsCredentialsProvider.fromIntegrations(integrations);
const credentials = await credentialProvider.getCredentials({ url: url });
+ const integrationConfig = integrations.azure.byHost(host);
if (credentials === undefined && ctx.input.token === undefined) {
throw new InputError(
@@ -212,6 +220,15 @@ export function createPublishAzureAction(options: {
password: ctx.input.token ?? credentials!.token,
};
+ const signingKey =
+ integrationConfig?.config.commitSigningKey ??
+ config.getOptionalString('scaffolder.defaultCommitSigningKey');
+ if (signCommit && !signingKey) {
+ throw new Error(
+ 'Signing commits is enabled but no signing key is provided in the configuration',
+ );
+ }
+
const commitResult = await initRepoAndPush({
dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
remoteUrl,
@@ -222,6 +239,7 @@ export function createPublishAzureAction(options: {
? gitCommitMessage
: config.getOptionalString('scaffolder.defaultCommitMessage'),
gitAuthorInfo,
+ signingKey: signCommit ? signingKey : undefined,
});
ctx.output('commitHash', commitResult?.commitHash);
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md
index 600f54c508..4d8a71f12a 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/plugin-scaffolder-backend-module-bitbucket-cloud
+## 0.2.7-next.2
+
+### Patch Changes
+
+- c56a279: Added `bitbucketCloud:branchRestriction:create` to allow users to create bitbucket cloud branch restrictions in templates
+- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder
+- e279c30: Fixing spelling mistake in `jsonschema`
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/plugin-bitbucket-cloud-common@0.2.28-next.0
+
## 0.2.7-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json
index babc74e1ae..ebb0219343 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud",
- "version": "0.2.7-next.1",
+ "version": "0.2.7-next.2",
"description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md
index ad74479b6e..8deda92860 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md
@@ -23,7 +23,12 @@ export const createBitbucketPipelinesRunAction: (options: {
body?: object;
token?: string;
},
- JsonObject
+ {
+ buildNumber: number;
+ repoUrl: string;
+ pipelinesUrl: string;
+ },
+ 'v1'
>;
// @public
@@ -39,8 +44,10 @@ export function createPublishBitbucketCloudAction(options: {
gitCommitMessage?: string;
sourcePath?: string;
token?: string;
+ signCommit?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -58,6 +65,7 @@ export function createPublishBitbucketCloudPullRequestAction(options: {
gitAuthorName?: string;
gitAuthorEmail?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
```
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts
index 00c27f6a54..945aab1216 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts
@@ -18,8 +18,8 @@ import { InputError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import {
createTemplateAction,
- initRepoAndPush,
getRepoSourceDirectory,
+ initRepoAndPush,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
@@ -113,6 +113,7 @@ export function createPublishBitbucketCloudAction(options: {
gitCommitMessage?: string;
sourcePath?: string;
token?: string;
+ signCommit?: boolean;
}>({
id: 'publish:bitbucketCloud',
examples,
@@ -158,6 +159,11 @@ export function createPublishBitbucketCloudAction(options: {
description:
'The token to use for authorization to BitBucket Cloud',
},
+ signCommit: {
+ title: 'Sign commit',
+ type: 'boolean',
+ description: 'Sign commit with configured PGP private key',
+ },
},
},
output: {
@@ -185,6 +191,7 @@ export function createPublishBitbucketCloudAction(options: {
defaultBranch = 'master',
gitCommitMessage,
repoVisibility = 'private',
+ signCommit,
} = ctx.input;
const { workspace, project, repo, host } = parseRepoUrl(
@@ -256,6 +263,15 @@ export function createPublishBitbucketCloudAction(options: {
};
}
+ const signingKey =
+ integrationConfig.config.commitSigningKey ??
+ config.getOptionalString('scaffolder.defaultCommitSigningKey');
+ if (signCommit && !signingKey) {
+ throw new Error(
+ 'Signing commits is enabled but no signing key is provided in the configuration',
+ );
+ }
+
const commitResult = await initRepoAndPush({
dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
remoteUrl,
@@ -266,6 +282,7 @@ export function createPublishBitbucketCloudAction(options: {
gitCommitMessage ||
config.getOptionalString('scaffolder.defaultCommitMessage'),
gitAuthorInfo,
+ signingKey: signCommit ? signingKey : undefined,
});
ctx.output('commitHash', commitResult?.commitHash);
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts
index edabe2a7ba..83d959e67f 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.test.ts
@@ -65,9 +65,11 @@ describe('bitbucket:pipelines:run', () => {
const actionNoCreds = createBitbucketPipelinesRunAction({
integrations: integrationsNoCreds,
});
+
const testContext = Object.assign({}, mockContext, {
input: { workspace, repo_slug },
});
+
await expect(actionNoCreds.handler(testContext)).rejects.toThrow(
/Authorization has not been provided for Bitbucket Cloud/,
);
diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts
index 8525774692..11064f4743 100644
--- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts
+++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloudPipelinesRun.ts
@@ -30,12 +30,19 @@ export const createBitbucketPipelinesRunAction = (options: {
integrations: ScmIntegrationRegistry;
}) => {
const { integrations } = options;
- return createTemplateAction<{
- workspace: string;
- repo_slug: string;
- body?: object;
- token?: string;
- }>({
+ return createTemplateAction<
+ {
+ workspace: string;
+ repo_slug: string;
+ body?: object;
+ token?: string;
+ },
+ {
+ buildNumber: number;
+ repoUrl: string;
+ pipelinesUrl: string;
+ }
+ >({
id,
description: 'Run a bitbucket cloud pipeline',
examples,
@@ -58,7 +65,7 @@ export const createBitbucketPipelinesRunAction = (options: {
type: 'number',
},
repoUrl: {
- title: 'A URL to the pipeline repositry',
+ title: 'A URL to the pipeline repository',
type: 'string',
},
repoContentsUrl: {
diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md
index b86ddc4e18..16544db6b9 100644
--- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-scaffolder-backend-module-bitbucket-server
+## 0.2.7-next.2
+
+### Patch Changes
+
+- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.2.7-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json
index b5feb22b98..dd996891c5 100644
--- a/plugins/scaffolder-backend-module-bitbucket-server/package.json
+++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server",
- "version": "0.2.7-next.1",
+ "version": "0.2.7-next.2",
"description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-bitbucket-server/report.api.md b/plugins/scaffolder-backend-module-bitbucket-server/report.api.md
index e92c85b284..2f4d68c95d 100644
--- a/plugins/scaffolder-backend-module-bitbucket-server/report.api.md
+++ b/plugins/scaffolder-backend-module-bitbucket-server/report.api.md
@@ -29,8 +29,10 @@ export function createPublishBitbucketServerAction(options: {
gitCommitMessage?: string;
gitAuthorName?: string;
gitAuthorEmail?: string;
+ signCommit?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -49,6 +51,7 @@ export function createPublishBitbucketServerPullRequestAction(options: {
gitAuthorName?: string;
gitAuthorEmail?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
```
diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts
index 612d090547..3f50d5f274 100644
--- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts
+++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts
@@ -21,8 +21,8 @@ import {
} from '@backstage/integration';
import {
createTemplateAction,
- initRepoAndPush,
getRepoSourceDirectory,
+ initRepoAndPush,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
@@ -137,6 +137,7 @@ export function createPublishBitbucketServerAction(options: {
gitCommitMessage?: string;
gitAuthorName?: string;
gitAuthorEmail?: string;
+ signCommit?: boolean;
}>({
id: 'publish:bitbucketServer',
description:
@@ -197,6 +198,11 @@ export function createPublishBitbucketServerAction(options: {
type: 'string',
description: `Sets the author email for the commit.`,
},
+ signCommit: {
+ title: 'Sign commit',
+ type: 'boolean',
+ description: 'Sign commit with configured PGP private key',
+ },
},
},
output: {
@@ -227,6 +233,7 @@ export function createPublishBitbucketServerAction(options: {
gitCommitMessage = 'initial commit',
gitAuthorName,
gitAuthorEmail,
+ signCommit,
} = ctx.input;
const { project, repo, host } = parseRepoUrl(repoUrl, integrations);
@@ -279,6 +286,15 @@ export function createPublishBitbucketServerAction(options: {
: config.getOptionalString('scaffolder.defaultAuthor.email'),
};
+ const signingKey =
+ integrationConfig.config.commitSigningKey ??
+ config.getOptionalString('scaffolder.defaultCommitSigningKey');
+ if (signCommit && !signingKey) {
+ throw new Error(
+ 'Signing commits is enabled but no signing key is provided in the configuration',
+ );
+ }
+
const auth = authConfig.token
? {
token: token!,
@@ -298,6 +314,7 @@ export function createPublishBitbucketServerAction(options: {
? gitCommitMessage
: config.getOptionalString('scaffolder.defaultCommitMessage'),
gitAuthorInfo,
+ signingKey: signCommit ? signingKey : undefined,
});
if (enableLFS) {
diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md
index 0a27f73db8..3edb45d23b 100644
--- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-scaffolder-backend-module-bitbucket
+## 0.3.8-next.2
+
+### Patch Changes
+
+- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder
+- Updated dependencies
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.7-next.2
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.7-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.3.8-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json
index ee8bcd052b..5486c1b1c4 100644
--- a/plugins/scaffolder-backend-module-bitbucket/package.json
+++ b/plugins/scaffolder-backend-module-bitbucket/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-bitbucket",
- "version": "0.3.8-next.1",
+ "version": "0.3.8-next.2",
"description": "The bitbucket module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-bitbucket/report.api.md b/plugins/scaffolder-backend-module-bitbucket/report.api.md
index b90f2f8483..dd45ae5c01 100644
--- a/plugins/scaffolder-backend-module-bitbucket/report.api.md
+++ b/plugins/scaffolder-backend-module-bitbucket/report.api.md
@@ -25,7 +25,12 @@ export const createBitbucketPipelinesRunAction: (options: {
body?: object;
token?: string;
},
- JsonObject
+ {
+ buildNumber: number;
+ repoUrl: string;
+ pipelinesUrl: string;
+ },
+ 'v1'
>;
// @public @deprecated
@@ -44,8 +49,10 @@ export function createPublishBitbucketAction(options: {
gitCommitMessage?: string;
gitAuthorName?: string;
gitAuthorEmail?: string;
+ signCommit?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public @deprecated (undocumented)
diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts
index 7a58efa10a..e52eb50976 100644
--- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts
+++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts
@@ -21,8 +21,8 @@ import {
} from '@backstage/integration';
import {
createTemplateAction,
- initRepoAndPush,
getRepoSourceDirectory,
+ initRepoAndPush,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import { Config } from '@backstage/config';
@@ -220,6 +220,7 @@ export function createPublishBitbucketAction(options: {
gitCommitMessage?: string;
gitAuthorName?: string;
gitAuthorEmail?: string;
+ signCommit?: boolean;
}>({
id: 'publish:bitbucket',
description:
@@ -280,6 +281,11 @@ export function createPublishBitbucketAction(options: {
type: 'string',
description: `Sets the default author email for the commit.`,
},
+ signCommit: {
+ title: 'Sign commit',
+ type: 'boolean',
+ description: 'Sign commit with configured PGP private key',
+ },
},
},
output: {
@@ -313,6 +319,7 @@ export function createPublishBitbucketAction(options: {
gitCommitMessage = 'initial commit',
gitAuthorName,
gitAuthorEmail,
+ signCommit,
} = ctx.input;
const { workspace, project, repo, host } = parseRepoUrl(
@@ -380,6 +387,14 @@ export function createPublishBitbucketAction(options: {
? gitAuthorEmail
: config.getOptionalString('scaffolder.defaultAuthor.email'),
};
+ const signingKey =
+ integrationConfig.config.commitSigningKey ??
+ config.getOptionalString('scaffolder.defaultCommitSigningKey');
+ if (signCommit && !signingKey) {
+ throw new Error(
+ 'Signing commits is enabled but no signing key is provided in the configuration',
+ );
+ }
let auth;
@@ -409,6 +424,7 @@ export function createPublishBitbucketAction(options: {
? gitCommitMessage
: config.getOptionalString('scaffolder.defaultCommitMessage'),
gitAuthorInfo,
+ signingKey: signCommit ? signingKey : undefined,
});
if (enableLFS && host !== 'bitbucket.org') {
diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md
index 9d16edd097..efbabd7d75 100644
--- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-scaffolder-backend-module-confluence-to-markdown
+## 0.3.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.3.7-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json
index 0c65f2f539..16c241cc70 100644
--- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json
+++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown",
- "version": "0.3.7-next.1",
+ "version": "0.3.7-next.2",
"description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md b/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md
index 2dd5e244dd..64584ad4b5 100644
--- a/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md
+++ b/plugins/scaffolder-backend-module-confluence-to-markdown/report.api.md
@@ -24,6 +24,7 @@ export const createConfluenceToMarkdownAction: (options: {
confluenceUrls: string[];
repoUrl: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
```
diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md
index 99de1d28d5..2a08023971 100644
--- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/plugin-scaffolder-backend-module-cookiecutter
+## 0.3.8-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/backend-defaults@0.8.2-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 0.3.8-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json
index 69b972b46c..706c1d8340 100644
--- a/plugins/scaffolder-backend-module-cookiecutter/package.json
+++ b/plugins/scaffolder-backend-module-cookiecutter/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-cookiecutter",
- "version": "0.3.8-next.1",
+ "version": "0.3.8-next.2",
"description": "A module for the scaffolder backend that lets you template projects using cookiecutter",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-cookiecutter/report.api.md b/plugins/scaffolder-backend-module-cookiecutter/report.api.md
index 36339ea722..4f963ce818 100644
--- a/plugins/scaffolder-backend-module-cookiecutter/report.api.md
+++ b/plugins/scaffolder-backend-module-cookiecutter/report.api.md
@@ -54,6 +54,7 @@ export function createFetchCookiecutterAction(options: {
extensions?: string[];
imageName?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
```
diff --git a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md
index 85cc405ecf..28baa5b983 100644
--- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/plugin-scaffolder-backend-module-gcp
+## 0.2.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.2.7-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json
index c9430baea7..2c71d05ca1 100644
--- a/plugins/scaffolder-backend-module-gcp/package.json
+++ b/plugins/scaffolder-backend-module-gcp/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-gcp",
- "version": "0.2.7-next.1",
+ "version": "0.2.7-next.2",
"description": "The GCP Bucket module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md
index 910b3f5b51..2a7c4e7b86 100644
--- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-scaffolder-backend-module-gerrit
+## 0.2.7-next.2
+
+### Patch Changes
+
+- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.2.7-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json
index 4e5b9f87b7..a77d362fda 100644
--- a/plugins/scaffolder-backend-module-gerrit/package.json
+++ b/plugins/scaffolder-backend-module-gerrit/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-gerrit",
- "version": "0.2.7-next.1",
+ "version": "0.2.7-next.2",
"description": "The gerrit module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-gerrit/report.api.md b/plugins/scaffolder-backend-module-gerrit/report.api.md
index a3f23d7aa7..937df6f435 100644
--- a/plugins/scaffolder-backend-module-gerrit/report.api.md
+++ b/plugins/scaffolder-backend-module-gerrit/report.api.md
@@ -22,8 +22,10 @@ export function createPublishGerritAction(options: {
gitAuthorName?: string;
gitAuthorEmail?: string;
sourcePath?: string;
+ signCommit?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -38,8 +40,10 @@ export function createPublishGerritReviewAction(options: {
gitCommitMessage?: string;
gitAuthorName?: string;
gitAuthorEmail?: string;
+ signCommit?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts
index db0ae2ba70..23f28ae934 100644
--- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts
+++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts
@@ -24,8 +24,8 @@ import {
} from '@backstage/integration';
import {
createTemplateAction,
- initRepoAndPush,
getRepoSourceDirectory,
+ initRepoAndPush,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
import { examples } from './gerrit.examples';
@@ -99,6 +99,7 @@ export function createPublishGerritAction(options: {
gitAuthorName?: string;
gitAuthorEmail?: string;
sourcePath?: string;
+ signCommit?: boolean;
}>({
id: 'publish:gerrit',
supportsDryRun: true,
@@ -143,6 +144,11 @@ export function createPublishGerritAction(options: {
type: 'string',
description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`,
},
+ signCommit: {
+ title: 'Sign commit',
+ type: 'boolean',
+ description: 'Sign commit with configured PGP private key',
+ },
},
},
output: {
@@ -172,6 +178,7 @@ export function createPublishGerritAction(options: {
gitAuthorEmail,
gitCommitMessage = 'initial commit',
sourcePath,
+ signCommit,
} = ctx.input;
const { repo, host, owner, workspace } = parseRepoUrl(
repoUrl,
@@ -233,6 +240,15 @@ export function createPublishGerritAction(options: {
email: gitEmail,
};
+ const signingKey =
+ integrationConfig.config.commitSigningKey ??
+ config.getOptionalString('scaffolder.defaultCommitSigningKey');
+ if (signCommit && !signingKey) {
+ throw new Error(
+ 'Signing commits is enabled but no signing key is provided in the configuration',
+ );
+ }
+
const commitResult = await initRepoAndPush({
dir: getRepoSourceDirectory(ctx.workspacePath, sourcePath),
remoteUrl,
@@ -241,6 +257,7 @@ export function createPublishGerritAction(options: {
logger: ctx.logger,
commitMessage: generateCommitMessage(config, gitCommitMessage),
gitAuthorInfo,
+ signingKey: signCommit ? signingKey : undefined,
});
ctx.output('remoteUrl', remoteUrl);
diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts
index b4f844b08f..b4ab3afef7 100644
--- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts
+++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts
@@ -19,8 +19,8 @@ import { InputError } from '@backstage/errors';
import { Config } from '@backstage/config';
import { ScmIntegrationRegistry } from '@backstage/integration';
import {
- createTemplateAction,
commitAndPushRepo,
+ createTemplateAction,
getRepoSourceDirectory,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
@@ -48,6 +48,7 @@ export function createPublishGerritReviewAction(options: {
gitCommitMessage?: string;
gitAuthorName?: string;
gitAuthorEmail?: string;
+ signCommit?: boolean;
}>({
id: 'publish:gerrit:review',
description: 'Creates a new Gerrit review.',
@@ -88,6 +89,11 @@ export function createPublishGerritReviewAction(options: {
type: 'string',
description: `Sets the default author email for the commit.`,
},
+ signCommit: {
+ title: 'Sign commit',
+ type: 'boolean',
+ description: 'Sign commit with configured PGP private key',
+ },
},
},
output: {
@@ -112,6 +118,7 @@ export function createPublishGerritReviewAction(options: {
gitAuthorName,
gitAuthorEmail,
gitCommitMessage,
+ signCommit,
} = ctx.input;
const { host, repo } = parseRepoUrl(repoUrl, integrations);
@@ -139,6 +146,14 @@ export function createPublishGerritReviewAction(options: {
? gitAuthorEmail
: config.getOptionalString('scaffolder.defaultAuthor.email'),
};
+ const signingKey =
+ integrationConfig.config.commitSigningKey ??
+ config.getOptionalString('scaffolder.defaultCommitSigningKey');
+ if (signCommit && !signingKey) {
+ throw new Error(
+ 'Signing commits is enabled but no signing key is provided in the configuration',
+ );
+ }
const changeId = generateGerritChangeId();
const commitMessage = `${gitCommitMessage}\n\nChange-Id: ${changeId}`;
@@ -150,6 +165,7 @@ export function createPublishGerritReviewAction(options: {
gitAuthorInfo,
branch,
remoteRef: `refs/for/${branch}`,
+ signingKey: signCommit ? signingKey : undefined,
});
const repoContentsUrl = `${integrationConfig.config.gitilesBaseUrl}/${repo}/+/refs/heads/${branch}`;
diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md
index ebf8690a10..b5b47ec622 100644
--- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-scaffolder-backend-module-gitea
+## 0.2.7-next.2
+
+### Patch Changes
+
+- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.2.7-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json
index 310d2524d1..7cc94b06bc 100644
--- a/plugins/scaffolder-backend-module-gitea/package.json
+++ b/plugins/scaffolder-backend-module-gitea/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-gitea",
- "version": "0.2.7-next.1",
+ "version": "0.2.7-next.2",
"description": "The gitea module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-gitea/report.api.md b/plugins/scaffolder-backend-module-gitea/report.api.md
index 8456b7183c..dede7f013a 100644
--- a/plugins/scaffolder-backend-module-gitea/report.api.md
+++ b/plugins/scaffolder-backend-module-gitea/report.api.md
@@ -23,8 +23,10 @@ export function createPublishGiteaAction(options: {
gitAuthorName?: string;
gitAuthorEmail?: string;
sourcePath?: string;
+ signCommit?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts
index 6b29ff2a4c..4321de2167 100644
--- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts
+++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts
@@ -219,6 +219,7 @@ export function createPublishGiteaAction(options: {
gitAuthorName?: string;
gitAuthorEmail?: string;
sourcePath?: string;
+ signCommit?: boolean;
}>({
id: 'publish:gitea',
description:
@@ -268,6 +269,11 @@ export function createPublishGiteaAction(options: {
type: 'string',
description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`,
},
+ signCommit: {
+ title: 'Sign commit',
+ type: 'boolean',
+ description: 'Sign commit with configured PGP private key',
+ },
},
},
output: {
@@ -298,6 +304,7 @@ export function createPublishGiteaAction(options: {
gitAuthorEmail,
gitCommitMessage = 'initial commit',
sourcePath,
+ signCommit,
} = ctx.input;
const { repo, host, owner } = parseRepoUrl(repoUrl, integrations);
@@ -338,6 +345,16 @@ export function createPublishGiteaAction(options: {
? gitAuthorEmail
: config.getOptionalString('scaffolder.defaultAuthor.email'),
};
+
+ const signingKey =
+ integrationConfig.config.commitSigningKey ??
+ config.getOptionalString('scaffolder.defaultCommitSigningKey');
+ if (signCommit && !signingKey) {
+ throw new Error(
+ 'Signing commits is enabled but no signing key is provided in the configuration',
+ );
+ }
+
// The owner to be used should be either the org name or user authenticated with the gitea server
const remoteUrl = `${integrationConfig.config.baseUrl}/${owner}/${repo}.git`;
const commitResult = await initRepoAndPush({
diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md
index 6cd45a3f88..d5f476d936 100644
--- a/plugins/scaffolder-backend-module-github/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-scaffolder-backend-module-github
+## 0.6.1-next.2
+
+### Patch Changes
+
+- 9391f58: Pass `undefined` to some parameters for `createOrUpdateEnvironment` as these values are not always supported in different plans of GitHub
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.6.1-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json
index 613ca84ff1..9ca633e659 100644
--- a/plugins/scaffolder-backend-module-github/package.json
+++ b/plugins/scaffolder-backend-module-github/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-github",
- "version": "0.6.1-next.1",
+ "version": "0.6.1-next.2",
"description": "The github module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md
index e2d124eb6f..f1b7f98a30 100644
--- a/plugins/scaffolder-backend-module-github/report.api.md
+++ b/plugins/scaffolder-backend-module-github/report.api.md
@@ -30,7 +30,8 @@ export function createGithubActionsDispatchAction(options: {
};
token?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -45,7 +46,8 @@ export function createGithubAutolinksAction(options: {
isAlphanumeric?: boolean;
token?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -81,7 +83,8 @@ export function createGithubBranchProtectionAction(options: {
requiredLinearHistory?: boolean;
token?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -96,7 +99,8 @@ export function createGithubDeployKeyAction(options: {
privateKeySecretName?: string;
token?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -125,7 +129,8 @@ export function createGithubEnvironmentAction(options: {
preventSelfReview?: boolean;
reviewers?: string[];
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -139,7 +144,8 @@ export function createGithubIssuesLabelAction(options: {
labels: string[];
token?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -154,7 +160,8 @@ export function createGithubPagesEnableAction(options: {
sourcePath?: '/' | '/docs';
token?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -255,7 +262,8 @@ export function createGithubRepoCreateAction(options: {
};
subscribe?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -299,7 +307,8 @@ export function createGithubRepoPushAction(options: {
requiredLinearHistory?: boolean;
requireLastPushApproval?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -318,7 +327,8 @@ export function createGithubWebhookAction(options: {
insecureSsl?: boolean;
token?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -404,7 +414,8 @@ export function createPublishGithubAction(options: {
};
subscribe?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -431,7 +442,8 @@ export const createPublishGithubPullRequestAction: (
forceEmptyGitAuthor?: boolean;
createWhenEmpty?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md
index bd8c6d338b..0269ab61d4 100644
--- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md
@@ -1,5 +1,20 @@
# @backstage/plugin-scaffolder-backend-module-gitlab
+## 0.8.1-next.2
+
+### Patch Changes
+
+- 0df33ea: fix: Creating a repository in a user namespace would always lead to an error
+- ac58f84: Made gitlab:issue:edit action idempotent.
+- a75e18f: Change the if statement in the catch block to match what the new version of Gitbeaker will return
+- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.8.1-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json
index 4c52bc9ee0..a33c3f5d8e 100644
--- a/plugins/scaffolder-backend-module-gitlab/package.json
+++ b/plugins/scaffolder-backend-module-gitlab/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-gitlab",
- "version": "0.8.1-next.1",
+ "version": "0.8.1-next.2",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "scaffolder",
diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md
index d50104e98e..cdb6065a3b 100644
--- a/plugins/scaffolder-backend-module-gitlab/report.api.md
+++ b/plugins/scaffolder-backend-module-gitlab/report.api.md
@@ -26,7 +26,8 @@ export const createGitlabGroupEnsureExistsAction: (options: {
},
{
groupId?: number | undefined;
- }
+ },
+ 'v1'
>;
// @public
@@ -55,7 +56,8 @@ export const createGitlabIssueAction: (options: {
issueUrl: string;
issueId: number;
issueIid: number;
- }
+ },
+ 'v1'
>;
// @public
@@ -73,7 +75,8 @@ export const createGitlabProjectAccessTokenAction: (options: {
},
{
access_token: string;
- }
+ },
+ 'v1'
>;
// @public
@@ -91,7 +94,8 @@ export const createGitlabProjectDeployTokenAction: (options: {
{
user: string;
deploy_token: string;
- }
+ },
+ 'v1'
>;
// @public
@@ -110,7 +114,8 @@ export const createGitlabProjectVariableAction: (options: {
environmentScope?: string | undefined;
variableProtected?: boolean | undefined;
},
- JsonObject
+ any,
+ 'v1'
>;
// @public
@@ -126,7 +131,8 @@ export const createGitlabRepoPushAction: (options: {
token?: string;
commitAction?: 'create' | 'delete' | 'update';
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -144,6 +150,7 @@ export function createPublishGitlabAction(options: {
gitCommitMessage?: string;
gitAuthorName?: string;
gitAuthorEmail?: string;
+ signCommit?: boolean;
setUserAsOwner?: boolean;
topics?: string[];
settings?: {
@@ -176,7 +183,8 @@ export function createPublishGitlabAction(options: {
environment_scope?: string;
}>;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -199,7 +207,8 @@ export const createPublishGitlabMergeRequestAction: (options: {
reviewers?: string[];
assignReviewersFromApprovalRules?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -216,7 +225,8 @@ export const createTriggerGitlabPipelineAction: (options: {
},
{
pipelineUrl: string;
- }
+ },
+ 'v1'
>;
// @public
@@ -252,7 +262,8 @@ export const editGitlabIssueAction: (options: {
issueUrl: string;
issueId: number;
issueIid: number;
- }
+ },
+ 'v1'
>;
// @public
diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts
index e255652f15..1e3ac40d35 100644
--- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts
+++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts
@@ -44,6 +44,7 @@ const mockGitlabClient = {
},
Users: {
showCurrentUser: jest.fn(),
+ allProjects: jest.fn(),
},
ProjectMembers: {
add: jest.fn(),
@@ -74,6 +75,7 @@ describe('publish:gitlab', () => {
host: 'gitlab.com',
token: 'tokenlols',
apiBaseUrl: 'https://api.gitlab.com',
+ commitSigningKey: 'test-signing-key',
},
{
host: 'hosted.gitlab.com',
@@ -211,7 +213,7 @@ describe('publish:gitlab', () => {
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
});
- it('should call the correct Gitlab APIs when the owner is an organization', async () => {
+ it('should call the correct Gitlab APIs when the owner is a group', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
@@ -232,16 +234,18 @@ describe('publish:gitlab', () => {
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
});
- it('should call the correct Gitlab APIs when the owner is not an organization', async () => {
+ it('should call the correct Gitlab APIs when the owner is a user', async () => {
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: null });
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
- mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
+ mockGitlabClient.Groups.allProjects.mockRejectedValue({});
+ mockGitlabClient.Users.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
});
await action.handler(mockContext);
+ expect(mockGitlabClient.Groups.allProjects).not.toHaveBeenCalled();
expect(mockGitlabClient.Namespaces.show).toHaveBeenCalledWith('owner');
expect(mockGitlabClient.Projects.create).toHaveBeenCalledWith({
namespaceId: 12345,
@@ -253,9 +257,12 @@ describe('publish:gitlab', () => {
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
});
- it('should not call the creation Gitlab APIs when the repository already exists', async () => {
+ it('should not call the creation Gitlab APIs when the repository already exists in a group', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
- mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
mockGitlabClient.Groups.allProjects.mockResolvedValue([
{
path: 'repo',
@@ -278,9 +285,41 @@ describe('publish:gitlab', () => {
expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
});
+ it('should not call the creation Gitlab APIs when the repository already exists in a user namespace', async () => {
+ mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 12345,
+ kind: 'user',
+ });
+ mockGitlabClient.Users.allProjects.mockResolvedValue([
+ {
+ path: 'repo',
+ http_url_to_repo: 'http://mockurl.git',
+ },
+ {
+ path: 'repo-name',
+ http_url_to_repo: 'http://mockurl.git',
+ },
+ ]);
+
+ await action.handler({
+ ...mockContext,
+ input: { ...mockContext.input, skipExisting: true },
+ });
+
+ expect(mockGitlabClient.Groups.allProjects).not.toHaveBeenCalled();
+ expect(mockGitlabClient.Namespaces.show).toHaveBeenCalledWith('owner');
+ expect(mockGitlabClient.Projects.create).not.toHaveBeenCalled();
+ expect(mockGitlabClient.Branches.create).not.toHaveBeenCalled();
+ expect(mockGitlabClient.ProtectedBranches.protect).not.toHaveBeenCalled();
+ });
+
it('should call the creation Gitlab APIs when the repository does not yet exists', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
- mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
mockGitlabClient.Groups.allProjects.mockResolvedValue([
{
path: 'repo-name',
@@ -305,7 +344,10 @@ describe('publish:gitlab', () => {
it('should call the correct Gitlab APIs when using project settings with override of visibility and topics', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
- mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -327,7 +369,10 @@ describe('publish:gitlab', () => {
it('should call the correct Gitlab APIs for branches and protectd branches when branch settings provided', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
- mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
id: 123456,
@@ -368,7 +413,10 @@ describe('publish:gitlab', () => {
it('should call the correct Gitlab APIs for variables when their configuration is provided', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
- mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
id: 123456,
@@ -401,7 +449,10 @@ describe('publish:gitlab', () => {
it('should call initRepoAndPush with the correct values', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
- mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -420,7 +471,7 @@ describe('publish:gitlab', () => {
});
});
- it('should not call initRepoAndPush when sourcePath is false', async () => {
+ it('should call initRepoAndPush with the signing key', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
@@ -428,6 +479,34 @@ describe('publish:gitlab', () => {
http_url_to_repo: 'http://mockurl.git',
});
+ await action.handler({
+ ...mockContext,
+ input: { ...mockContext.input, signCommit: true },
+ });
+
+ expect(initRepoAndPush).toHaveBeenCalledWith({
+ dir: mockContext.workspacePath,
+ defaultBranch: 'master',
+ remoteUrl: 'http://mockurl.git',
+ auth: { username: 'oauth2', password: 'tokenlols' },
+ logger: mockContext.logger,
+ commitMessage: 'initial commit',
+ gitAuthorInfo: {},
+ signingKey: 'test-signing-key',
+ });
+ });
+
+ it('should not call initRepoAndPush when sourcePath is false', async () => {
+ mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
+ mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
+ mockGitlabClient.Projects.create.mockResolvedValue({
+ http_url_to_repo: 'http://mockurl.git',
+ });
+
await action.handler({
...mockContext,
input: { ...mockContext.input, sourcePath: false },
@@ -446,7 +525,10 @@ describe('publish:gitlab', () => {
it('should call initRepoAndPush with the correct default branch', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
- mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -502,7 +584,10 @@ describe('publish:gitlab', () => {
});
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
- mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -549,7 +634,10 @@ describe('publish:gitlab', () => {
});
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
- mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -570,7 +658,10 @@ describe('publish:gitlab', () => {
it('should call output with the remoteUrl and repoContentsUrl and projectId', async () => {
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
- mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
http_url_to_repo: 'http://mockurl.git',
@@ -610,7 +701,10 @@ describe('publish:gitlab', () => {
config: customAuthorConfig,
});
- mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 });
+ mockGitlabClient.Namespaces.show.mockResolvedValue({
+ id: 1234,
+ kind: 'group',
+ });
mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 });
mockGitlabClient.Groups.allProjects.mockResolvedValue([]);
mockGitlabClient.Projects.create.mockResolvedValue({
diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts
index b2641e6656..753da2e253 100644
--- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts
+++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts
@@ -16,13 +16,13 @@
import { InputError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
-import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
-import { Gitlab, VariableType } from '@gitbeaker/rest';
import {
- initRepoAndPush,
+ createTemplateAction,
getRepoSourceDirectory,
+ initRepoAndPush,
parseRepoUrl,
} from '@backstage/plugin-scaffolder-node';
+import { Gitlab, VariableType } from '@gitbeaker/rest';
import { Config } from '@backstage/config';
import { examples } from './gitlab.examples';
@@ -49,6 +49,7 @@ export function createPublishGitlabAction(options: {
gitCommitMessage?: string;
gitAuthorName?: string;
gitAuthorEmail?: string;
+ signCommit?: boolean;
setUserAsOwner?: boolean;
/** @deprecated in favour of settings.topics field */
topics?: string[];
@@ -122,6 +123,11 @@ export function createPublishGitlabAction(options: {
type: 'string',
description: `Sets the default author email for the commit.`,
},
+ signCommit: {
+ title: 'Sign commit',
+ type: 'boolean',
+ description: 'Sign commit with configured PGP private key',
+ },
sourcePath: {
title: 'Source Path',
description:
@@ -349,6 +355,7 @@ export function createPublishGitlabAction(options: {
branches = [],
projectVariables = [],
skipExisting = false,
+ signCommit,
} = ctx.input;
const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
@@ -379,13 +386,15 @@ export function createPublishGitlabAction(options: {
});
let targetNamespaceId;
-
+ let targetNamespaceKind;
try {
const namespaceResponse = (await client.Namespaces.show(owner)) as {
id: number;
+ kind: string;
};
targetNamespaceId = namespaceResponse.id;
+ targetNamespaceKind = namespaceResponse.kind;
} catch (e) {
if (e.cause?.response?.status === 404) {
throw new InputError(
@@ -401,13 +410,16 @@ export function createPublishGitlabAction(options: {
if (!targetNamespaceId) {
targetNamespaceId = userId;
+ targetNamespaceKind = 'user';
}
- const existingProjects = await client.Groups.allProjects(owner, {
- search: repo,
- });
+ const existingProjects =
+ targetNamespaceKind === 'user'
+ ? await client.Users.allProjects(owner, { search: repo })
+ : await client.Groups.allProjects(owner, { search: repo });
+
const existingProject = existingProjects.find(
- searchPathElem => searchPathElem.path === repo,
+ project => project.path === repo,
);
if (!skipExisting || (skipExisting && !existingProject)) {
@@ -447,6 +459,15 @@ export function createPublishGitlabAction(options: {
? gitAuthorEmail
: config.getOptionalString('scaffolder.defaultAuthor.email'),
};
+ const signingKey =
+ integrationConfig.config.commitSigningKey ??
+ config.getOptionalString('scaffolder.defaultCommitSigningKey');
+ if (signCommit && !signingKey) {
+ throw new Error(
+ 'Signing commits is enabled but no signing key is provided in the configuration',
+ );
+ }
+
const shouldSkipPublish =
typeof ctx.input.sourcePath === 'boolean' && !ctx.input.sourcePath;
if (!shouldSkipPublish) {
@@ -469,6 +490,7 @@ export function createPublishGitlabAction(options: {
? gitCommitMessage
: config.getOptionalString('scaffolder.defaultCommitMessage'),
gitAuthorInfo,
+ signingKey: signCommit ? signingKey : undefined,
});
if (branches) {
diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts
index 6537c42315..f347aaadd0 100644
--- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts
+++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts
@@ -151,19 +151,24 @@ export const createGitlabRepoPushAction = (options: {
execute_filemode: file.executable,
}));
- let branchExists = false;
- try {
- await api.Branches.show(repoID, branchName);
- branchExists = true;
- } catch (e: any) {
- if (e.cause?.response?.status !== 404) {
- throw new InputError(
- `Failed to check status of branch '${branchName}'. Please make sure that branch already exists or Backstage has permissions to create one. ${getErrorMessage(
- e,
- )}`,
- );
- }
- }
+ const branchExists = await ctx.checkpoint({
+ key: `branch.exists.${repoID}.${branchName}`,
+ fn: async () => {
+ try {
+ await api.Branches.show(repoID, branchName);
+ return true;
+ } catch (e: any) {
+ if (e.cause?.response?.status !== 404) {
+ throw new InputError(
+ `Failed to check status of branch '${branchName}'. Please make sure that branch already exists or Backstage has permissions to create one. ${getErrorMessage(
+ e,
+ )}`,
+ );
+ }
+ }
+ return false;
+ },
+ });
if (!branchExists) {
// create a branch using the default branch as ref
@@ -181,15 +186,22 @@ export const createGitlabRepoPushAction = (options: {
}
try {
- const commit = await api.Commits.create(
- repoID,
- branchName,
- ctx.input.commitMessage,
- actions,
- );
+ const commitId = await ctx.checkpoint({
+ key: `commit.create.${repoID}.${branchName}`,
+ fn: async () => {
+ const commit = await api.Commits.create(
+ repoID,
+ branchName,
+ ctx.input.commitMessage,
+ actions,
+ );
+ return commit.id;
+ },
+ });
+
ctx.output('projectid', repoID);
ctx.output('projectPath', repoID);
- ctx.output('commitHash', commit.id);
+ ctx.output('commitHash', commitId);
} catch (e) {
throw new InputError(
`Committing the changes to ${branchName} failed. Please check that none of the files created by the template already exists. ${getErrorMessage(
diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md
index 85a914a998..a5d73e15d1 100644
--- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-scaffolder-backend-module-notifications
+## 0.1.8-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/plugin-notifications-common@0.0.8
+ - @backstage/plugin-notifications-node@0.2.13-next.2
+
## 0.1.8-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json
index cb5af0c3a5..9dfdfc5e05 100644
--- a/plugins/scaffolder-backend-module-notifications/package.json
+++ b/plugins/scaffolder-backend-module-notifications/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-notifications",
- "version": "0.1.8-next.1",
+ "version": "0.1.8-next.2",
"description": "The notifications backend module for the scaffolder plugin.",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-notifications/report.api.md b/plugins/scaffolder-backend-module-notifications/report.api.md
index 1c2bcf5f64..cb796e5033 100644
--- a/plugins/scaffolder-backend-module-notifications/report.api.md
+++ b/plugins/scaffolder-backend-module-notifications/report.api.md
@@ -23,7 +23,8 @@ export function createSendNotificationAction(options: {
scope?: string;
optional?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md
index 0ad330fc44..8b6c700405 100644
--- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/plugin-scaffolder-backend-module-rails
+## 0.5.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+
## 0.5.7-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json
index 49f4f57329..1fe65455e3 100644
--- a/plugins/scaffolder-backend-module-rails/package.json
+++ b/plugins/scaffolder-backend-module-rails/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-rails",
- "version": "0.5.7-next.1",
+ "version": "0.5.7-next.2",
"description": "A module for the scaffolder backend that lets you template projects using Rails",
"backstage": {
"role": "backend-plugin-module",
diff --git a/plugins/scaffolder-backend-module-rails/report.api.md b/plugins/scaffolder-backend-module-rails/report.api.md
index 846e2fe77f..0d7618852d 100644
--- a/plugins/scaffolder-backend-module-rails/report.api.md
+++ b/plugins/scaffolder-backend-module-rails/report.api.md
@@ -49,7 +49,8 @@ export function createFetchRailsAction(options: {
values: JsonObject;
imageName?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md
index a38d33d6d3..df4b05bc06 100644
--- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-scaffolder-backend-module-sentry
+## 0.2.7-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+
## 0.2.7-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json
index 4a4398f955..c5604e3679 100644
--- a/plugins/scaffolder-backend-module-sentry/package.json
+++ b/plugins/scaffolder-backend-module-sentry/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-sentry",
- "version": "0.2.7-next.1",
+ "version": "0.2.7-next.2",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "scaffolder",
diff --git a/plugins/scaffolder-backend-module-sentry/report.api.md b/plugins/scaffolder-backend-module-sentry/report.api.md
index ff31bd62c3..973ef9844c 100644
--- a/plugins/scaffolder-backend-module-sentry/report.api.md
+++ b/plugins/scaffolder-backend-module-sentry/report.api.md
@@ -19,7 +19,8 @@ export function createSentryCreateProjectAction(options: {
slug?: string;
authToken?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md
index eff0d028d2..640a554001 100644
--- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md
+++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-scaffolder-backend-module-yeoman
+## 0.4.8-next.2
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/plugin-scaffolder-node-test-utils@0.2.0-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/types@1.2.1
+
## 0.4.8-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json
index b423b92b49..d1625abc96 100644
--- a/plugins/scaffolder-backend-module-yeoman/package.json
+++ b/plugins/scaffolder-backend-module-yeoman/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend-module-yeoman",
- "version": "0.4.8-next.1",
+ "version": "0.4.8-next.2",
"backstage": {
"role": "backend-plugin-module",
"pluginId": "scaffolder",
diff --git a/plugins/scaffolder-backend-module-yeoman/report.api.md b/plugins/scaffolder-backend-module-yeoman/report.api.md
index ac262abcf6..57498bbe3a 100644
--- a/plugins/scaffolder-backend-module-yeoman/report.api.md
+++ b/plugins/scaffolder-backend-module-yeoman/report.api.md
@@ -14,7 +14,8 @@ export function createRunYeomanAction(): TemplateAction<
args?: string[];
options?: JsonObject;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md
index 8829813b77..900d1ef3b6 100644
--- a/plugins/scaffolder-backend/CHANGELOG.md
+++ b/plugins/scaffolder-backend/CHANGELOG.md
@@ -1,5 +1,44 @@
# @backstage/plugin-scaffolder-backend
+## 1.31.0-next.2
+
+### Minor Changes
+
+- 36677bb: Support new `createTemplateAction` type, and convert `catalog:fetch` action to new way of defining actions.
+- 2b1e50d: use CreatedTemplate[Filter|Global*] as canonical template extensions in scaffolder plugin
+
+### Patch Changes
+
+- e0b226b: build(deps): bump `esbuild` from 0.24.2 to 0.25.0
+- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder
+- 59dcf37: Fixed bug in fs:delete causing no files to be deleted on windows machines
+- Updated dependencies
+ - @backstage/plugin-scaffolder-backend-module-gitlab@0.8.1-next.2
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.7-next.2
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/plugin-scaffolder-backend-module-github@0.6.1-next.2
+ - @backstage/backend-defaults@0.8.2-next.2
+ - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.7-next.2
+ - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.8-next.2
+ - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.7-next.2
+ - @backstage/plugin-scaffolder-backend-module-azure@0.2.7-next.2
+ - @backstage/plugin-scaffolder-backend-module-gitea@0.2.7-next.2
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/plugin-events-node@0.4.9-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/config@1.3.2
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-auth-node@0.6.1-next.1
+ - @backstage/plugin-bitbucket-cloud-common@0.2.28-next.0
+ - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.6-next.1
+ - @backstage/plugin-catalog-node@1.16.1-next.1
+ - @backstage/plugin-permission-common@0.8.4
+ - @backstage/plugin-permission-node@0.8.9-next.1
+ - @backstage/plugin-scaffolder-common@1.5.10-next.0
+
## 1.30.1-next.1
### Patch Changes
diff --git a/plugins/scaffolder-backend/config.d.ts b/plugins/scaffolder-backend/config.d.ts
index a391f97264..ae91c905b3 100644
--- a/plugins/scaffolder-backend/config.d.ts
+++ b/plugins/scaffolder-backend/config.d.ts
@@ -27,6 +27,11 @@ export interface Config {
email?: string;
};
+ /**
+ * Default PGP signing key for signing commits.
+ * @visibility secret
+ */
+ defaultCommitSigningKey?: string;
/**
* The commit message used when new components are created.
*/
diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json
index 9e5fae8e3c..7107cecf82 100644
--- a/plugins/scaffolder-backend/package.json
+++ b/plugins/scaffolder-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-backend",
- "version": "1.30.1-next.1",
+ "version": "1.31.0-next.2",
"description": "The Backstage backend plugin that helps you create new things",
"backstage": {
"role": "backend-plugin",
@@ -97,7 +97,7 @@
"globby": "^11.0.0",
"isbinaryfile": "^5.0.0",
"isolated-vm": "^5.0.1",
- "jsonschema": "^1.2.6",
+ "jsonschema": "^1.5.0",
"knex": "^3.0.0",
"lodash": "^4.17.21",
"logform": "^2.3.2",
diff --git a/plugins/scaffolder-backend/report.api.md b/plugins/scaffolder-backend/report.api.md
index 18813154f2..37afe780a9 100644
--- a/plugins/scaffolder-backend/report.api.md
+++ b/plugins/scaffolder-backend/report.api.md
@@ -30,6 +30,7 @@ import { createPublishGerritAction as createPublishGerritAction_2 } from '@backs
import { createPublishGerritReviewAction as createPublishGerritReviewAction_2 } from '@backstage/plugin-scaffolder-backend-module-gerrit';
import { createPublishGithubAction as createPublishGithubAction_2 } from '@backstage/plugin-scaffolder-backend-module-github';
import { createPublishGitlabAction as createPublishGitlabAction_2 } from '@backstage/plugin-scaffolder-backend-module-gitlab';
+import { createTemplateAction as createTemplateAction_2 } from '@backstage/plugin-scaffolder-node';
import { DatabaseService } from '@backstage/backend-plugin-api';
import { DiscoveryService } from '@backstage/backend-plugin-api';
import { Duration } from 'luxon';
@@ -54,7 +55,6 @@ import { RESOURCE_TYPE_SCAFFOLDER_ACTION } from '@backstage/plugin-scaffolder-co
import { RESOURCE_TYPE_SCAFFOLDER_TEMPLATE } from '@backstage/plugin-scaffolder-common/alpha';
import { ScaffolderEntitiesProcessor as ScaffolderEntitiesProcessor_2 } from '@backstage/plugin-catalog-backend-module-scaffolder-entity-model';
import { SchedulerService } from '@backstage/backend-plugin-api';
-import { Schema } from 'jsonschema';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { ScmIntegrations } from '@backstage/integration';
import { SerializedTask as SerializedTask_2 } from '@backstage/plugin-scaffolder-node';
@@ -71,14 +71,12 @@ import { TaskSpec } from '@backstage/plugin-scaffolder-common';
import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common';
import { TaskStatus as TaskStatus_2 } from '@backstage/plugin-scaffolder-node';
import { TemplateAction as TemplateAction_2 } from '@backstage/plugin-scaffolder-node';
-import { TemplateActionOptions } from '@backstage/plugin-scaffolder-node';
import { TemplateEntityStepV1beta3 } from '@backstage/plugin-scaffolder-common';
import { TemplateFilter as TemplateFilter_2 } from '@backstage/plugin-scaffolder-node';
import { TemplateGlobal as TemplateGlobal_2 } from '@backstage/plugin-scaffolder-node';
import { TemplateParametersV1beta3 } from '@backstage/plugin-scaffolder-common';
import { UrlReaderService } from '@backstage/backend-plugin-api';
import { WorkspaceProvider } from '@backstage/plugin-scaffolder-node/alpha';
-import { ZodType } from 'zod';
// @public @deprecated (undocumented)
export type ActionContext = ActionContext_2;
@@ -125,7 +123,8 @@ export function createCatalogRegisterAction(options: {
catalogInfoPath?: string;
optional?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -134,17 +133,12 @@ export function createCatalogWriteAction(): TemplateAction_2<
entity: Record;
filePath?: string | undefined;
},
- JsonObject
+ any,
+ 'v1'
>;
// @public
-export function createDebugLogAction(): TemplateAction_2<
- {
- message?: string;
- listWorkspace?: boolean | 'with-filenames' | 'with-contents';
- },
- JsonObject
->;
+export function createDebugLogAction(): TemplateAction_2;
// @public
export function createFetchCatalogEntityAction(options: {
@@ -152,16 +146,17 @@ export function createFetchCatalogEntityAction(options: {
auth?: AuthService;
}): TemplateAction_2<
{
+ entityRef?: string | undefined;
+ entityRefs?: string[] | undefined;
optional?: boolean | undefined;
defaultKind?: string | undefined;
defaultNamespace?: string | undefined;
- entityRef?: string | undefined;
- entityRefs?: string[] | undefined;
},
{
- entities?: any[] | undefined;
entity?: any;
- }
+ entities?: any[] | undefined;
+ },
+ 'v2'
>;
// @public
@@ -174,7 +169,8 @@ export function createFetchPlainAction(options: {
targetPath?: string;
token?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -187,7 +183,8 @@ export function createFetchPlainFileAction(options: {
targetPath: string;
token?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -210,7 +207,8 @@ export function createFetchTemplateAction(options: {
lstripBlocks?: boolean;
token?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -230,7 +228,8 @@ export function createFetchTemplateFileAction(options: {
lstripBlocks?: boolean;
token?: string;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
@@ -238,14 +237,15 @@ export const createFilesystemDeleteAction: () => TemplateAction_2<
{
files: string[];
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public
export const createFilesystemReadDirAction: () => TemplateAction_2<
{
+ recursive: boolean;
paths: string[];
- recursive?: boolean | undefined;
},
{
files: {
@@ -258,7 +258,8 @@ export const createFilesystemReadDirAction: () => TemplateAction_2<
path: string;
fullPath: string;
}[];
- }
+ },
+ 'v1'
>;
// @public
@@ -270,7 +271,8 @@ export const createFilesystemRenameAction: () => TemplateAction_2<
overwrite?: boolean;
}>;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public @deprecated (undocumented)
@@ -346,7 +348,8 @@ export const createPublishGithubPullRequestAction: (
forceEmptyGitAuthor?: boolean;
createWhenEmpty?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public @deprecated (undocumented)
@@ -372,45 +375,20 @@ export const createPublishGitlabMergeRequestAction: (options: {
reviewers?: string[];
assignReviewersFromApprovalRules?: boolean;
},
- JsonObject
+ JsonObject,
+ 'v1'
>;
// @public @deprecated
export function createRouter(options: RouterOptions): Promise;
// @public @deprecated (undocumented)
-export const createTemplateAction: <
- TInputParams extends JsonObject = JsonObject,
- TOutputParams extends JsonObject = JsonObject,
- TInputSchema extends Schema | ZodType = {},
- TOutputSchema extends Schema | ZodType = {},
- TActionInput extends JsonObject = TInputSchema extends ZodType<
- any,
- any,
- infer IReturn
- >
- ? IReturn
- : TInputParams,
- TActionOutput extends JsonObject = TOutputSchema extends ZodType<
- any,
- any,
- infer IReturn_1
- >
- ? IReturn_1
- : TOutputParams,
->(
- action: TemplateActionOptions<
- TActionInput,
- TActionOutput,
- TInputSchema,
- TOutputSchema
- >,
-) => TemplateAction_2;
+export const createTemplateAction: typeof createTemplateAction_2;
// @public
export function createWaitAction(options?: {
maxWaitTime?: Duration | HumanDuration;
-}): TemplateAction_2;
+}): TemplateAction_2;
// @public
export type CreateWorkerOptions = {
@@ -547,7 +525,7 @@ export const fetchContents: typeof fetchContents_2;
// @public @deprecated
export interface RouterOptions {
// (undocumented)
- actions?: TemplateAction_2[];
+ actions?: TemplateAction_2[];
// (undocumented)
additionalTemplateFilters?:
| Record
@@ -866,11 +844,11 @@ export type TemplateAction =
// @public
export class TemplateActionRegistry {
// (undocumented)
- get(actionId: string): TemplateAction_2;
+ get(actionId: string): TemplateAction_2;
// (undocumented)
- list(): TemplateAction_2[];
+ list(): TemplateAction_2[];
// (undocumented)
- register(action: TemplateAction_2): void;
+ register(action: TemplateAction_2): void;
}
// @public @deprecated (undocumented)
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts
index f00d7afad1..11d6234083 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts
@@ -23,7 +23,7 @@ import { TemplateAction } from '@backstage/plugin-scaffolder-node';
export class TemplateActionRegistry {
private readonly actions = new Map();
- register(action: TemplateAction) {
+ register(action: TemplateAction) {
if (this.actions.has(action.id)) {
throw new ConflictError(
`Template action with ID '${action.id}' has already been registered`,
@@ -33,7 +33,7 @@ export class TemplateActionRegistry {
this.actions.set(action.id, action);
}
- get(actionId: string): TemplateAction {
+ get(actionId: string): TemplateAction {
const action = this.actions.get(actionId);
if (!action) {
throw new NotFoundError(
@@ -43,7 +43,7 @@ export class TemplateActionRegistry {
return action;
}
- list(): TemplateAction[] {
+ list(): TemplateAction[] {
return [...this.actions.values()];
}
}
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts
index 824ca4deb3..e21930ed78 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/fetch.ts
@@ -16,7 +16,6 @@
import { CatalogApi } from '@backstage/catalog-client';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
-import { z } from 'zod';
import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model';
import { examples } from './fetch.examples';
import { AuthService } from '@backstage/backend-plugin-api';
@@ -41,48 +40,54 @@ export function createFetchCatalogEntityAction(options: {
examples,
supportsDryRun: true,
schema: {
- input: z.object({
- entityRef: z
- .string({
- description: 'Entity reference of the entity to get',
- })
- .optional(),
- entityRefs: z
- .array(z.string(), {
- description: 'Entity references of the entities to get',
- })
- .optional(),
- optional: z
- .boolean({
- description:
- 'Allow the entity or entities to optionally exist. Default: false',
- })
- .optional(),
- defaultKind: z.string({ description: 'The default kind' }).optional(),
- defaultNamespace: z
- .string({ description: 'The default namespace' })
- .optional(),
- }),
- output: z.object({
- entity: z
- .any({
- description:
- 'Object containing same values used in the Entity schema. Only when used with `entityRef` parameter.',
- })
- .optional(),
- entities: z
- .array(
- z.any({
+ input: {
+ entityRef: z =>
+ z
+ .string({
+ description: 'Entity reference of the entity to get',
+ })
+ .optional(),
+ entityRefs: z =>
+ z
+ .array(z.string(), {
+ description: 'Entity references of the entities to get',
+ })
+ .optional(),
+ optional: z =>
+ z
+ .boolean({
description:
- 'Array containing objects with same values used in the Entity schema. Only when used with `entityRefs` parameter.',
- }),
- )
- .optional(),
- }),
+ 'Allow the entity or entities to optionally exist. Default: false',
+ })
+ .optional(),
+ defaultKind: z =>
+ z.string({ description: 'The default kind' }).optional(),
+ defaultNamespace: z =>
+ z.string({ description: 'The default namespace' }).optional(),
+ },
+ output: {
+ entity: z =>
+ z
+ .any({
+ description:
+ 'Object containing same values used in the Entity schema. Only when used with `entityRef` parameter.',
+ })
+ .optional(),
+ entities: z =>
+ z
+ .array(
+ z.any({
+ description:
+ 'Array containing objects with same values used in the Entity schema. Only when used with `entityRefs` parameter.',
+ }),
+ )
+ .optional(),
+ },
},
async handler(ctx) {
const { entityRef, entityRefs, optional, defaultKind, defaultNamespace } =
ctx.input;
+
if (!entityRef && !entityRefs) {
if (optional) {
return;
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts
index f037323c04..b0b16c6d12 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.test.ts
@@ -144,4 +144,25 @@ describe('fs:delete', () => {
expect(fileExists).toBe(false);
});
});
+
+ it('should handle windows style file paths', async () => {
+ const files = ['unit-test-a.js', 'unit-test-b.js'];
+
+ files.forEach(file => {
+ const filePath = resolvePath(workspacePath, file);
+ const fileExists = fs.existsSync(filePath);
+ expect(fileExists).toBe(true);
+ });
+
+ await action.handler({
+ ...mockContext,
+ input: { files: files.map(file => `.\\${file}`) },
+ });
+
+ files.forEach(file => {
+ const filePath = resolvePath(workspacePath, file);
+ const fileExists = fs.existsSync(filePath);
+ expect(fileExists).toBe(false);
+ });
+ });
});
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts
index 5a06c7d2aa..b2f9cddea4 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts
@@ -53,7 +53,11 @@ export const createFilesystemDeleteAction = () => {
}
for (const file of ctx.input.files) {
- const safeFilepath = resolveSafeChildPath(ctx.workspacePath, file);
+ // globby cannot handle backslash file separators
+ const safeFilepath = resolveSafeChildPath(
+ ctx.workspacePath,
+ file,
+ ).replace(/\\/g, '/');
const resolvedPaths = await globby(safeFilepath, {
cwd: ctx.workspacePath,
absolute: true,
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
index 8614f99388..2d6ed2d57c 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
@@ -104,33 +104,37 @@ describe('NunjucksWorkflowRunner', () => {
fakeActionHandler = jest.fn();
fakeTaskLog = jest.fn();
- actionRegistry.register({
- id: 'jest-mock-action',
- description: 'Mock action for testing',
- handler: fakeActionHandler,
- });
-
- actionRegistry.register({
- id: 'jest-validated-action',
- description: 'Mock action for testing',
- supportsDryRun: true,
- handler: fakeActionHandler,
- schema: {
- input: {
- type: 'object',
- required: ['foo'],
- properties: {
- foo: {
- type: 'number',
- },
- },
- },
- },
- });
+ actionRegistry.register(
+ createTemplateAction({
+ id: 'jest-mock-action',
+ description: 'Mock action for testing',
+ handler: fakeActionHandler,
+ }),
+ );
actionRegistry.register(
createTemplateAction({
- id: 'jest-zod-validated-action',
+ id: 'jest-validated-action',
+ description: 'Mock action for testing',
+ supportsDryRun: true,
+ handler: fakeActionHandler,
+ schema: {
+ input: {
+ type: 'object',
+ required: ['foo'],
+ properties: {
+ foo: {
+ type: 'number',
+ },
+ },
+ },
+ },
+ }),
+ );
+
+ actionRegistry.register(
+ createTemplateAction({
+ id: 'jest-legacy-zod-validated-action',
description: 'Mock action for testing',
handler: fakeActionHandler,
supportsDryRun: true,
@@ -142,52 +146,80 @@ describe('NunjucksWorkflowRunner', () => {
}) as TemplateAction,
);
- actionRegistry.register({
- id: 'output-action',
- description: 'Mock action for testing',
- handler: async ctx => {
- ctx.output('mock', 'backstage');
- ctx.output('shouldRun', true);
- },
- });
+ actionRegistry.register(
+ createTemplateAction({
+ id: 'jest-zod-validated-action',
+ description: 'Mock ac',
+ supportsDryRun: true,
+ schema: {
+ input: {
+ foo: zod => zod.number(),
+ },
+ output: {
+ test: zod => zod.string(),
+ },
+ },
+ handler: fakeActionHandler,
+ }),
+ );
- actionRegistry.register({
- id: 'checkpoints-action',
- description: 'Mock action with checkpoints',
- handler: async ctx => {
- const key1 = await ctx.checkpoint({
- key: 'key1',
- fn: async () => 'updated',
- });
- const key2 = await ctx.checkpoint({
- key: 'key2',
- fn: async () => 'updated',
- });
- const key3 = await ctx.checkpoint({
- key: 'key3',
- fn: async () => 'updated',
- });
+ actionRegistry.register(
+ createTemplateAction({
+ id: 'output-action',
+ description: 'Mock action for testing',
+ handler: async ctx => {
+ ctx.output('mock', 'backstage');
+ ctx.output('shouldRun', true);
+ },
+ }),
+ );
- const key4 = await ctx.checkpoint({
- key: 'key4',
- fn: () => {},
- });
+ actionRegistry.register(
+ createTemplateAction({
+ id: 'checkpoints-action',
+ description: 'Mock action with checkpoints',
+ schema: {
+ output: z.object({
+ key1: z.string(),
+ key2: z.string(),
+ key3: z.string(),
+ key4: z.string(),
+ key5: z.string(),
+ }),
+ },
+ handler: async ctx => {
+ const key1 = await ctx.checkpoint({
+ key: 'key1',
+ fn: async () => 'updated',
+ });
+ const key2 = await ctx.checkpoint({
+ key: 'key2',
+ fn: async () => 'updated',
+ });
+ const key3 = await ctx.checkpoint({
+ key: 'key3',
+ fn: async () => 'updated',
+ });
- const key5 = await ctx.checkpoint({
- key: 'key5',
- fn: async () => {},
- });
+ const key4 = await ctx.checkpoint({
+ key: 'key4',
+ fn: () => {},
+ });
- ctx.output('key1', key1);
- ctx.output('key2', key2);
- ctx.output('key3', key3);
+ const key5 = await ctx.checkpoint({
+ key: 'key5',
+ fn: async () => {},
+ });
- // @ts-expect-error - this is void return
- ctx.output('key4', key4);
- // @ts-expect-error - this is void return
- ctx.output('key5', key5);
- },
- });
+ ctx.output('key1', key1);
+ ctx.output('key2', key2);
+ ctx.output('key3', key3);
+
+ ctx.output('key4', key4);
+ ctx.output('key5', key5);
+ },
+ }),
+ );
mockedPermissionApi.authorizeConditional.mockResolvedValue([
{ result: AuthorizeResult.ALLOW },
@@ -244,6 +276,25 @@ describe('NunjucksWorkflowRunner', () => {
);
});
+ it('should throw an error if the action has legacy zod schema and the input does not match', async () => {
+ const task = createMockTaskWithSpec({
+ apiVersion: 'scaffolder.backstage.io/v1beta3',
+ parameters: {},
+ output: {},
+ steps: [
+ {
+ id: 'test',
+ name: 'name',
+ action: 'jest-legacy-zod-validated-action',
+ },
+ ],
+ });
+
+ await expect(runner.execute(task)).rejects.toThrow(
+ /Invalid input passed to action jest-legacy-zod-validated-action, instance requires property \"foo\"/,
+ );
+ });
+
it('should run the action when the zod validation passes', async () => {
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
@@ -264,6 +315,26 @@ describe('NunjucksWorkflowRunner', () => {
expect(fakeActionHandler).toHaveBeenCalledTimes(1);
});
+ it('should run the action when the zod validation passes with legacy zod', async () => {
+ const task = createMockTaskWithSpec({
+ apiVersion: 'scaffolder.backstage.io/v1beta3',
+ parameters: {},
+ output: {},
+ steps: [
+ {
+ id: 'test',
+ name: 'name',
+ action: 'jest-legacy-zod-validated-action',
+ input: { foo: 1 },
+ },
+ ],
+ });
+
+ await runner.execute(task);
+
+ expect(fakeActionHandler).toHaveBeenCalledTimes(1);
+ });
+
it('should run the action when the validation passes', async () => {
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts
index 41425affc9..da47bfd563 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts
@@ -38,13 +38,13 @@ export function generateExampleOutput(schema: Schema): unknown {
return Object.fromEntries(
Object.entries(schema.properties ?? {}).map(([key, value]) => [
key,
- generateExampleOutput(value),
+ generateExampleOutput(value as Schema),
]),
);
} else if (schema.type === 'array') {
const [firstSchema] = [schema.items]?.flat();
if (firstSchema) {
- return [generateExampleOutput(firstSchema)];
+ return [generateExampleOutput(firstSchema as Schema)];
}
return [];
} else if (schema.type === 'string') {
diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts
index 35782ea441..1186e7d4f6 100644
--- a/plugins/scaffolder-backend/src/service/router.ts
+++ b/plugins/scaffolder-backend/src/service/router.ts
@@ -165,7 +165,7 @@ export interface RouterOptions {
database: DatabaseService;
catalogClient: CatalogApi;
scheduler?: SchedulerService;
- actions?: TemplateAction[];
+ actions?: TemplateAction[];
/**
* @deprecated taskWorkers is deprecated in favor of concurrentTasksLimit option with a single TaskWorker
* @defaultValue 1
diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md
index f33c5fee2b..4f11504092 100644
--- a/plugins/scaffolder-node-test-utils/CHANGELOG.md
+++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-scaffolder-node-test-utils
+## 0.2.0-next.2
+
+### Minor Changes
+
+- 36677bb: Use update `createTemplateAction` kinds
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-scaffolder-node@0.8.0-next.2
+ - @backstage/backend-test-utils@1.3.1-next.2
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/types@1.2.1
+
## 0.1.20-next.1
### Patch Changes
diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json
index 3ed56e700b..9a3cd185ae 100644
--- a/plugins/scaffolder-node-test-utils/package.json
+++ b/plugins/scaffolder-node-test-utils/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-node-test-utils",
- "version": "0.1.20-next.1",
+ "version": "0.2.0-next.2",
"backstage": {
"role": "node-library",
"pluginId": "scaffolder",
diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md
index cafea8aa43..a6ca28f04e 100644
--- a/plugins/scaffolder-node/CHANGELOG.md
+++ b/plugins/scaffolder-node/CHANGELOG.md
@@ -1,5 +1,89 @@
# @backstage/plugin-scaffolder-node
+## 0.8.0-next.2
+
+### Minor Changes
+
+- 1a58846: **DEPRECATION**: We've deprecated the old way of defining actions using `createTemplateAction` with raw `JSONSchema` and type parameters, as well as using `zod` through an import. You can now use the new format to define `createTemplateActions` with `zod` provided by the framework. This change also removes support for `logStream` in the `context` as well as moving the `logger` to an instance of `LoggerService`.
+
+ Before:
+
+ ```ts
+ createTemplateAction<{ repoUrl: string }, { test: string }>({
+ id: 'test',
+ schema: {
+ input: {
+ type: 'object',
+ required: ['repoUrl'],
+ properties: {
+ repoUrl: { type: 'string' },
+ },
+ },
+ output: {
+ type: 'object',
+ required: ['test'],
+ properties: {
+ test: { type: 'string' },
+ },
+ },
+ },
+ handler: async ctx => {
+ ctx.logStream.write('blob');
+ },
+ });
+
+ // or
+
+ createTemplateAction({
+ id: 'test',
+ schema: {
+ input: z.object({
+ repoUrl: z.string(),
+ }),
+ output: z.object({
+ test: z.string(),
+ }),
+ },
+ handler: async ctx => {
+ ctx.logStream.write('something');
+ },
+ });
+ ```
+
+ After:
+
+ ```ts
+ createTemplateAction({
+ id: 'test',
+ schema: {
+ input: {
+ repoUrl: d => d.string(),
+ },
+ output: {
+ test: d => d.string(),
+ },
+ },
+ handler: async ctx => {
+ // you can just use ctx.logger.log('...'), or if you really need a log stream you can do this:
+ const logStream = new PassThrough();
+ logStream.on('data', chunk => {
+ ctx.logger.info(chunk.toString());
+ });
+ },
+ });
+ ```
+
+### Patch Changes
+
+- 4f8b5b6: Allow signing git commits using configured private PGP key in scaffolder
+- Updated dependencies
+ - @backstage/integration@1.16.2-next.0
+ - @backstage/backend-plugin-api@1.2.1-next.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/errors@1.2.7
+ - @backstage/types@1.2.1
+ - @backstage/plugin-scaffolder-common@1.5.10-next.0
+
## 0.7.1-next.1
### Patch Changes
diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json
index d7f8fc042b..953d150243 100644
--- a/plugins/scaffolder-node/package.json
+++ b/plugins/scaffolder-node/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-node",
- "version": "0.7.1-next.1",
+ "version": "0.8.0-next.2",
"description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend",
"backstage": {
"role": "node-library",
@@ -60,11 +60,12 @@
"@backstage/integration": "workspace:^",
"@backstage/plugin-scaffolder-common": "workspace:^",
"@backstage/types": "workspace:^",
+ "@isomorphic-git/pgp-plugin": "^0.0.7",
"concat-stream": "^2.0.0",
"fs-extra": "^11.2.0",
"globby": "^11.0.0",
"isomorphic-git": "^1.23.0",
- "jsonschema": "^1.2.6",
+ "jsonschema": "^1.5.0",
"p-limit": "^3.1.0",
"tar": "^6.1.12",
"winston": "^3.2.1",
diff --git a/plugins/scaffolder-node/report-alpha.api.md b/plugins/scaffolder-node/report-alpha.api.md
index 8ecc54f031..fa9e022bb4 100644
--- a/plugins/scaffolder-node/report-alpha.api.md
+++ b/plugins/scaffolder-node/report-alpha.api.md
@@ -122,7 +122,7 @@ export const restoreWorkspace: (opts: {
// @alpha
export interface ScaffolderActionsExtensionPoint {
// (undocumented)
- addActions(...actions: TemplateAction[]): void;
+ addActions(...actions: TemplateAction[]): void;
}
// @alpha
diff --git a/plugins/scaffolder-node/report.api.md b/plugins/scaffolder-node/report.api.md
index 333b47a6d1..6dabcbf346 100644
--- a/plugins/scaffolder-node/report.api.md
+++ b/plugins/scaffolder-node/report.api.md
@@ -4,6 +4,7 @@
```ts
import { BackstageCredentials } from '@backstage/backend-plugin-api';
+import { Expand } from '@backstage/types';
import { JsonObject } from '@backstage/types';
import { JsonValue } from '@backstage/types';
import { Logger } from 'winston';
@@ -24,34 +25,63 @@ import { z } from 'zod';
export type ActionContext<
TActionInput extends JsonObject,
TActionOutput extends JsonObject = JsonObject,
-> = {
- logger: Logger;
- logStream: Writable;
- secrets?: TaskSecrets;
- workspacePath: string;
- input: TActionInput;
- checkpoint(opts: {
- key: string;
- fn: () => Promise | T;
- }): Promise;
- output(
- name: keyof TActionOutput,
- value: TActionOutput[keyof TActionOutput],
- ): void;
- createTemporaryDirectory(): Promise;
- getInitiatorCredentials(): Promise;
- task: {
- id: string;
- };
- templateInfo?: TemplateInfo;
- isDryRun?: boolean;
- user?: {
- entity?: UserEntity;
- ref?: string;
- };
- signal?: AbortSignal;
- each?: JsonObject;
-};
+ TSchemaType extends 'v1' | 'v2' = 'v1',
+> = TSchemaType extends 'v2'
+ ? {
+ logger: LoggerService;
+ secrets?: TaskSecrets;
+ workspacePath: string;
+ input: TActionInput;
+ checkpoint(opts: {
+ key: string;
+ fn: () => Promise | T;
+ }): Promise;
+ output(
+ name: keyof TActionOutput,
+ value: TActionOutput[keyof TActionOutput],
+ ): void;
+ createTemporaryDirectory(): Promise;
+ getInitiatorCredentials(): Promise;
+ task: {
+ id: string;
+ };
+ templateInfo?: TemplateInfo;
+ isDryRun?: boolean;
+ user?: {
+ entity?: UserEntity;
+ ref?: string;
+ };
+ signal?: AbortSignal;
+ each?: JsonObject;
+ }
+ : {
+ logger: Logger;
+ logStream: Writable;
+ secrets?: TaskSecrets;
+ workspacePath: string;
+ input: TActionInput;
+ checkpoint(opts: {
+ key: string;
+ fn: () => Promise | T;
+ }): Promise;
+ output(
+ name: keyof TActionOutput,
+ value: TActionOutput[keyof TActionOutput],
+ ): void;
+ createTemporaryDirectory(): Promise;
+ getInitiatorCredentials(): Promise;
+ task: {
+ id: string;
+ };
+ templateInfo?: TemplateInfo;
+ isDryRun?: boolean;
+ user?: {
+ entity?: UserEntity;
+ ref?: string;
+ };
+ signal?: AbortSignal;
+ each?: JsonObject;
+ };
// @public (undocumented)
export function addFiles(options: {
@@ -106,6 +136,7 @@ export function commitAndPushBranch(options: {
branch?: string;
remoteRef?: string;
remote?: string;
+ signingKey?: string;
}): Promise<{
commitHash: string;
}>;
@@ -129,6 +160,7 @@ export function commitAndPushRepo(input: {
};
branch?: string;
remoteRef?: string;
+ signingKey?: string;
}): Promise<{
commitHash: string;
}>;
@@ -148,34 +180,71 @@ export function createBranch(options: {
logger?: Logger | undefined;
}): Promise;
-// @public
-export const createTemplateAction: <
+// @public @deprecated (undocumented)
+export function createTemplateAction<
TInputParams extends JsonObject = JsonObject,
TOutputParams extends JsonObject = JsonObject,
- TInputSchema extends Schema | z.ZodType = {},
- TOutputSchema extends Schema | z.ZodType = {},
- TActionInput extends JsonObject = TInputSchema extends z.ZodType<
- any,
- any,
- infer IReturn
- >
- ? IReturn
- : TInputParams,
- TActionOutput extends JsonObject = TOutputSchema extends z.ZodType<
- any,
- any,
- infer IReturn_1
- >
- ? IReturn_1
- : TOutputParams,
+ TInputSchema extends JsonObject = JsonObject,
+ TOutputSchema extends JsonObject = JsonObject,
+ TActionInput extends JsonObject = TInputParams,
+ TActionOutput extends JsonObject = TOutputParams,
>(
action: TemplateActionOptions<
TActionInput,
TActionOutput,
TInputSchema,
- TOutputSchema
+ TOutputSchema,
+ 'v1'
>,
-) => TemplateAction;
+): TemplateAction;
+
+// @public @deprecated (undocumented)
+export function createTemplateAction<
+ TInputParams extends JsonObject = JsonObject,
+ TOutputParams extends JsonObject = JsonObject,
+ TInputSchema extends z.ZodType = z.ZodType,
+ TOutputSchema extends z.ZodType = z.ZodType,
+ TActionInput extends JsonObject = z.infer,
+ TActionOutput extends JsonObject = z.infer,
+>(
+ action: TemplateActionOptions<
+ TActionInput,
+ TActionOutput,
+ TInputSchema,
+ TOutputSchema,
+ 'v1'
+ >,
+): TemplateAction;
+
+// @public
+export function createTemplateAction<
+ TInputSchema extends {
+ [key in string]: (zImpl: typeof z) => z.ZodType;
+ },
+ TOutputSchema extends {
+ [key in string]: (zImpl: typeof z) => z.ZodType;
+ },
+>(
+ action: TemplateActionOptions<
+ {
+ [key in keyof TInputSchema]: z.infer>;
+ },
+ {
+ [key in keyof TOutputSchema]: z.infer>;
+ },
+ TInputSchema,
+ TOutputSchema,
+ 'v2'
+ >,
+): TemplateAction<
+ FlattenOptionalProperties<{
+ [key in keyof TInputSchema]: z.output>;
+ }>,
+ FlattenOptionalProperties<{
+ [key in keyof TOutputSchema]: z.output>;
+ }>,
+ 'v2'
+>;
// @public
export function deserializeDirectoryContents(
@@ -242,6 +311,7 @@ export function initRepoAndPush(input: {
name?: string;
email?: string;
};
+ signingKey?: string;
}): Promise<{
commitHash: string;
}>;
@@ -440,6 +510,7 @@ export type TaskStatus =
export type TemplateAction<
TActionInput extends JsonObject = JsonObject,
TActionOutput extends JsonObject = JsonObject,
+ TSchemaType extends 'v1' | 'v2' = 'v1',
> = {
id: string;
description?: string;
@@ -452,15 +523,28 @@ export type TemplateAction<
input?: Schema;
output?: Schema;
};
- handler: (ctx: ActionContext) => Promise;
+ handler: (
+ ctx: ActionContext,
+ ) => Promise;
};
// @public (undocumented)
export type TemplateActionOptions<
TActionInput extends JsonObject = {},
TActionOutput extends JsonObject = {},
- TInputSchema extends Schema | z.ZodType = {},
- TOutputSchema extends Schema | z.ZodType = {},
+ TInputSchema extends
+ | JsonObject
+ | z.ZodType
+ | {
+ [key in string]: (zImpl: typeof z) => z.ZodType;
+ } = JsonObject,
+ TOutputSchema extends
+ | JsonObject
+ | z.ZodType
+ | {
+ [key in string]: (zImpl: typeof z) => z.ZodType;
+ } = JsonObject,
+ TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2',
> = {
id: string;
description?: string;
@@ -470,7 +554,9 @@ export type TemplateActionOptions<
input?: TInputSchema;
output?: TOutputSchema;
};
- handler: (ctx: ActionContext) => Promise;
+ handler: (
+ ctx: ActionContext,
+ ) => Promise;
};
// @public (undocumented)
diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts
new file mode 100644
index 0000000000..43d5d2f660
--- /dev/null
+++ b/plugins/scaffolder-node/src/actions/createTemplateAction.test.ts
@@ -0,0 +1,131 @@
+/*
+ * 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 { createTemplateAction } from './createTemplateAction';
+import { z } from 'zod';
+
+describe('createTemplateAction', () => {
+ it('should allow creating with jsonschema and use the old deprecated types', () => {
+ const action = createTemplateAction<{ repoUrl: string }, { test: string }>({
+ id: 'test',
+ schema: {
+ input: {
+ type: 'object',
+ required: ['repoUrl'],
+ properties: {
+ repoUrl: { type: 'string' },
+ },
+ },
+ output: {
+ type: 'object',
+ required: ['test'],
+ properties: {
+ test: { type: 'string' },
+ },
+ },
+ },
+ handler: async ctx => {
+ // @ts-expect-error - repoUrl is string
+ const a: number = ctx.input.repoUrl;
+
+ const b: string = ctx.input.repoUrl;
+ expect(b).toBeDefined();
+
+ const stream = ctx.logStream;
+ expect(stream).toBeDefined();
+
+ ctx.output('test', 'value');
+
+ // @ts-expect-error - not valid output type
+ ctx.output('test', 4);
+
+ // @ts-expect-error - not valid output name
+ ctx.output('test2', 'value');
+ },
+ });
+
+ expect(action).toBeDefined();
+ });
+
+ it('should allow creating with zod and use the old deprecated types', () => {
+ const action = createTemplateAction({
+ id: 'test',
+ schema: {
+ input: z.object({
+ repoUrl: z.string(),
+ }),
+ output: z.object({
+ test: z.string(),
+ }),
+ },
+ handler: async ctx => {
+ // @ts-expect-error - repoUrl is string
+ const a: number = ctx.input.repoUrl;
+
+ const b: string = ctx.input.repoUrl;
+ expect(b).toBeDefined();
+
+ const stream = ctx.logStream;
+ expect(stream).toBeDefined();
+
+ ctx.output('test', 'value');
+
+ // @ts-expect-error - not valid output type
+ ctx.output('test', 4);
+
+ // @ts-expect-error - not valid output name
+ ctx.output('test2', 'value');
+ },
+ });
+
+ expect(action).toBeDefined();
+ });
+
+ it('should allow creating with new first class zod support', () => {
+ const action = createTemplateAction({
+ id: 'test',
+ schema: {
+ input: {
+ repoUrl: d => d.string(),
+ },
+ output: {
+ test: d => d.string(),
+ },
+ },
+ handler: async ctx => {
+ // @ts-expect-error - repoUrl is string
+ const a: number = ctx.input.repoUrl;
+
+ const b: string = ctx.input.repoUrl;
+ expect(b).toBeDefined();
+
+ // @ts-expect-error - logStream is not available
+ const stream = ctx.logStream;
+
+ expect(stream).toBeDefined();
+
+ ctx.output('test', 'value');
+
+ // @ts-expect-error - not valid output type
+ ctx.output('test', 4);
+
+ // @ts-expect-error - not valid output name
+ ctx.output('test2', 'value');
+ },
+ });
+
+ expect(action).toBeDefined();
+ });
+});
diff --git a/plugins/scaffolder-node/src/actions/createTemplateAction.ts b/plugins/scaffolder-node/src/actions/createTemplateAction.ts
index 4721df7de5..ce38f3b4e1 100644
--- a/plugins/scaffolder-node/src/actions/createTemplateAction.ts
+++ b/plugins/scaffolder-node/src/actions/createTemplateAction.ts
@@ -16,9 +16,8 @@
import { ActionContext, TemplateAction } from './types';
import { z } from 'zod';
-import { Schema } from 'jsonschema';
-import zodToJsonSchema from 'zod-to-json-schema';
-import { JsonObject } from '@backstage/types';
+import { Expand, JsonObject } from '@backstage/types';
+import { parseSchemas } from './util';
/** @public */
export type TemplateExample = {
@@ -30,8 +29,15 @@ export type TemplateExample = {
export type TemplateActionOptions<
TActionInput extends JsonObject = {},
TActionOutput extends JsonObject = {},
- TInputSchema extends Schema | z.ZodType = {},
- TOutputSchema extends Schema | z.ZodType = {},
+ TInputSchema extends
+ | JsonObject
+ | z.ZodType
+ | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject,
+ TOutputSchema extends
+ | JsonObject
+ | z.ZodType
+ | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject,
+ TSchemaType extends 'v1' | 'v2' = 'v1' | 'v2',
> = {
id: string;
description?: string;
@@ -41,25 +47,112 @@ export type TemplateActionOptions<
input?: TInputSchema;
output?: TOutputSchema;
};
- handler: (ctx: ActionContext) => Promise;
+ handler: (
+ ctx: ActionContext,
+ ) => Promise;
};
+/**
+ * @ignore
+ */
+type FlattenOptionalProperties = Expand<
+ {
+ [K in keyof T as undefined extends T[K] ? never : K]: T[K];
+ } & {
+ [K in keyof T as undefined extends T[K] ? K : never]?: T[K];
+ }
+>;
+
+/**
+ * @public
+ * @deprecated migrate to using the new built in zod schema definitions for schemas
+ */
+export function createTemplateAction<
+ TInputParams extends JsonObject = JsonObject,
+ TOutputParams extends JsonObject = JsonObject,
+ TInputSchema extends JsonObject = JsonObject,
+ TOutputSchema extends JsonObject = JsonObject,
+ TActionInput extends JsonObject = TInputParams,
+ TActionOutput extends JsonObject = TOutputParams,
+>(
+ action: TemplateActionOptions<
+ TActionInput,
+ TActionOutput,
+ TInputSchema,
+ TOutputSchema,
+ 'v1'
+ >,
+): TemplateAction;
+/**
+ * @public
+ * @deprecated migrate to using the new built in zod schema definitions for schemas
+ */
+export function createTemplateAction<
+ TInputParams extends JsonObject = JsonObject,
+ TOutputParams extends JsonObject = JsonObject,
+ TInputSchema extends z.ZodType = z.ZodType,
+ TOutputSchema extends z.ZodType = z.ZodType,
+ TActionInput extends JsonObject = z.infer,
+ TActionOutput extends JsonObject = z.infer,
+>(
+ action: TemplateActionOptions<
+ TActionInput,
+ TActionOutput,
+ TInputSchema,
+ TOutputSchema,
+ 'v1'
+ >,
+): TemplateAction;
/**
* This function is used to create new template actions to get type safety.
* Will convert zod schemas to json schemas for use throughout the system.
* @public
*/
-export const createTemplateAction = <
+export function createTemplateAction<
+ TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
+ TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType },
+>(
+ action: TemplateActionOptions<
+ {
+ [key in keyof TInputSchema]: z.infer>;
+ },
+ {
+ [key in keyof TOutputSchema]: z.infer>;
+ },
+ TInputSchema,
+ TOutputSchema,
+ 'v2'
+ >,
+): TemplateAction<
+ FlattenOptionalProperties<{
+ [key in keyof TInputSchema]: z.output>;
+ }>,
+ FlattenOptionalProperties<{
+ [key in keyof TOutputSchema]: z.output>;
+ }>,
+ 'v2'
+>;
+export function createTemplateAction<
TInputParams extends JsonObject = JsonObject,
TOutputParams extends JsonObject = JsonObject,
- TInputSchema extends Schema | z.ZodType = {},
- TOutputSchema extends Schema | z.ZodType = {},
+ TInputSchema extends
+ | JsonObject
+ | z.ZodType
+ | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject,
+ TOutputSchema extends
+ | JsonObject
+ | z.ZodType
+ | { [key in string]: (zImpl: typeof z) => z.ZodType } = JsonObject,
TActionInput extends JsonObject = TInputSchema extends z.ZodType<
any,
any,
infer IReturn
>
? IReturn
+ : TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }
+ ? Expand<{
+ [key in keyof TInputSchema]: z.infer>;
+ }>
: TInputParams,
TActionOutput extends JsonObject = TOutputSchema extends z.ZodType<
any,
@@ -67,6 +160,10 @@ export const createTemplateAction = <
infer IReturn
>
? IReturn
+ : TOutputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }
+ ? Expand<{
+ [key in keyof TOutputSchema]: z.infer>;
+ }>
: TOutputParams,
>(
action: TemplateActionOptions<
@@ -75,23 +172,23 @@ export const createTemplateAction = <
TInputSchema,
TOutputSchema
>,
-): TemplateAction => {
- const inputSchema =
- action.schema?.input && 'safeParseAsync' in action.schema.input
- ? zodToJsonSchema(action.schema.input)
- : action.schema?.input;
-
- const outputSchema =
- action.schema?.output && 'safeParseAsync' in action.schema.output
- ? zodToJsonSchema(action.schema.output)
- : action.schema?.output;
+): TemplateAction<
+ TActionInput,
+ TActionOutput,
+ TInputSchema extends { [key in string]: (zImpl: typeof z) => z.ZodType }
+ ? 'v2'
+ : 'v1'
+> {
+ const { inputSchema, outputSchema } = parseSchemas(
+ action as TemplateActionOptions,
+ );
return {
...action,
schema: {
...action.schema,
- input: inputSchema as TInputSchema,
- output: outputSchema as TOutputSchema,
+ input: inputSchema,
+ output: outputSchema,
},
};
-};
+}
diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.test.ts b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts
index 2e8eb9366f..d70ddf3bc0 100644
--- a/plugins/scaffolder-node/src/actions/gitHelpers.test.ts
+++ b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts
@@ -16,12 +16,12 @@
import { Git } from '../scm';
import {
- commitAndPushRepo,
- initRepoAndPush,
- commitAndPushBranch,
addFiles,
- createBranch,
cloneRepo,
+ commitAndPushBranch,
+ commitAndPushRepo,
+ createBranch,
+ initRepoAndPush,
} from './gitHelpers';
import { mockServices } from '@backstage/backend-test-utils';
import { loggerToWinstonLogger } from './loggerToWinstonLogger';
@@ -224,6 +224,32 @@ describe('commitAndPushRepo', () => {
});
});
+ it('creates commit with signing key', async () => {
+ await commitAndPushRepo({
+ dir: '/test/repo/dir/',
+ auth: {
+ username: 'test-user',
+ password: 'test-password',
+ },
+ logger: loggerToWinstonLogger(mockServices.logger.mock()),
+ commitMessage: 'commit message',
+ signingKey: 'test-signing-key',
+ });
+ expect(mockedGit.commit).toHaveBeenCalledWith({
+ dir: '/test/repo/dir/',
+ message: 'commit message',
+ author: {
+ name: 'Scaffolder',
+ email: 'scaffolder@backstage.io',
+ },
+ committer: {
+ name: 'Scaffolder',
+ email: 'scaffolder@backstage.io',
+ },
+ signingKey: 'test-signing-key',
+ });
+ });
+
it('pushes to the remote', () => {
expect(mockedGit.push).toHaveBeenCalledWith({
dir: '/test/repo/dir/',
diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.ts b/plugins/scaffolder-node/src/actions/gitHelpers.ts
index e24c0cf025..8922975274 100644
--- a/plugins/scaffolder-node/src/actions/gitHelpers.ts
+++ b/plugins/scaffolder-node/src/actions/gitHelpers.ts
@@ -31,6 +31,7 @@ export async function initRepoAndPush(input: {
defaultBranch?: string;
commitMessage?: string;
gitAuthorInfo?: { name?: string; email?: string };
+ signingKey?: string;
}): Promise<{ commitHash: string }> {
const {
dir,
@@ -40,6 +41,7 @@ export async function initRepoAndPush(input: {
defaultBranch = 'master',
commitMessage = 'Initial commit',
gitAuthorInfo,
+ signingKey,
} = input;
const git = Git.fromAuth({
...auth,
@@ -64,6 +66,7 @@ export async function initRepoAndPush(input: {
message: commitMessage,
author: authorInfo,
committer: authorInfo,
+ signingKey,
});
await git.push({
@@ -89,6 +92,7 @@ export async function commitAndPushRepo(input: {
gitAuthorInfo?: { name?: string; email?: string };
branch?: string;
remoteRef?: string;
+ signingKey?: string;
}): Promise<{ commitHash: string }> {
const {
dir,
@@ -98,6 +102,7 @@ export async function commitAndPushRepo(input: {
gitAuthorInfo,
branch = 'master',
remoteRef,
+ signingKey,
} = input;
const git = Git.fromAuth({
@@ -120,6 +125,7 @@ export async function commitAndPushRepo(input: {
message: commitMessage,
author: authorInfo,
committer: authorInfo,
+ signingKey,
});
await git.push({
@@ -213,6 +219,7 @@ export async function commitAndPushBranch(options: {
branch?: string;
remoteRef?: string;
remote?: string;
+ signingKey?: string;
}): Promise<{ commitHash: string }> {
const {
dir,
@@ -223,6 +230,7 @@ export async function commitAndPushBranch(options: {
branch = 'master',
remoteRef,
remote = 'origin',
+ signingKey,
} = options;
const git = Git.fromAuth({
...auth,
@@ -240,6 +248,7 @@ export async function commitAndPushBranch(options: {
message: commitMessage,
author: authorInfo,
committer: authorInfo,
+ signingKey,
});
await git.push({
diff --git a/plugins/scaffolder-node/src/actions/types.ts b/plugins/scaffolder-node/src/actions/types.ts
index 171d3d82db..92a2c7464a 100644
--- a/plugins/scaffolder-node/src/actions/types.ts
+++ b/plugins/scaffolder-node/src/actions/types.ts
@@ -21,8 +21,10 @@ import { TaskSecrets } from '../tasks';
import { TemplateInfo } from '@backstage/plugin-scaffolder-common';
import { UserEntity } from '@backstage/catalog-model';
import { Schema } from 'jsonschema';
-import { BackstageCredentials } from '@backstage/backend-plugin-api';
-
+import {
+ BackstageCredentials,
+ LoggerService,
+} from '@backstage/backend-plugin-api';
/**
* ActionContext is passed into scaffolder actions.
* @public
@@ -30,77 +32,143 @@ import { BackstageCredentials } from '@backstage/backend-plugin-api';
export type ActionContext<
TActionInput extends JsonObject,
TActionOutput extends JsonObject = JsonObject,
-> = {
- // TODO(blam): move this to LoggerService
- logger: Logger;
- /** @deprecated - use `ctx.logger` instead */
- logStream: Writable;
- secrets?: TaskSecrets;
- workspacePath: string;
- input: TActionInput;
- checkpoint(opts: {
- key: string;
- fn: () => Promise | T;
- }): Promise;
- output(
- name: keyof TActionOutput,
- value: TActionOutput[keyof TActionOutput],
- ): void;
+ TSchemaType extends 'v1' | 'v2' = 'v1',
+> = TSchemaType extends 'v2'
+ ? {
+ logger: LoggerService;
+ secrets?: TaskSecrets;
+ workspacePath: string;
+ input: TActionInput;
+ checkpoint(opts: {
+ key: string;
+ fn: () => Promise | T;
+ }): Promise;
+ output(
+ name: keyof TActionOutput,
+ value: TActionOutput[keyof TActionOutput],
+ ): void;
+ /**
+ * Creates a temporary directory for use by the action, which is then cleaned up automatically.
+ */
+ createTemporaryDirectory(): Promise;
- /**
- * Creates a temporary directory for use by the action, which is then cleaned up automatically.
- */
- createTemporaryDirectory(): Promise;
+ /**
+ * Get the credentials for the current request
+ */
+ getInitiatorCredentials(): Promise;
- /**
- * Get the credentials for the current request
- */
- getInitiatorCredentials(): Promise;
+ /**
+ * Task information
+ */
+ task: {
+ id: string;
+ };
- /**
- * Task information
- */
- task: {
- id: string;
- };
+ templateInfo?: TemplateInfo;
- templateInfo?: TemplateInfo;
+ /**
+ * Whether this action invocation is a dry-run or not.
+ * This will only ever be true if the actions as marked as supporting dry-runs.
+ */
+ isDryRun?: boolean;
- /**
- * Whether this action invocation is a dry-run or not.
- * This will only ever be true if the actions as marked as supporting dry-runs.
- */
- isDryRun?: boolean;
+ /**
+ * The user which triggered the action.
+ */
+ user?: {
+ /**
+ * The decorated entity from the Catalog
+ */
+ entity?: UserEntity;
+ /**
+ * An entity ref for the author of the task
+ */
+ ref?: string;
+ };
- /**
- * The user which triggered the action.
- */
- user?: {
- /**
- * The decorated entity from the Catalog
- */
- entity?: UserEntity;
- /**
- * An entity ref for the author of the task
- */
- ref?: string;
- };
+ /**
+ * Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
+ */
+ signal?: AbortSignal;
- /**
- * Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
- */
- signal?: AbortSignal;
+ /**
+ * Optional value of each invocation
+ */
+ each?: JsonObject;
+ }
+ : /** @deprecated **/
+ {
+ // TODO(blam): move this to LoggerService
+ logger: Logger;
+ /** @deprecated - use `ctx.logger` instead */
+ logStream: Writable;
+ secrets?: TaskSecrets;
+ workspacePath: string;
+ input: TActionInput;
+ checkpoint(opts: {
+ key: string;
+ fn: () => Promise | T;
+ }): Promise;
+ output(
+ name: keyof TActionOutput,
+ value: TActionOutput[keyof TActionOutput],
+ ): void;
- /**
- * Optional value of each invocation
- */
- each?: JsonObject;
-};
+ /**
+ * Creates a temporary directory for use by the action, which is then cleaned up automatically.
+ */
+ createTemporaryDirectory(): Promise;
+
+ /**
+ * Get the credentials for the current request
+ */
+ getInitiatorCredentials(): Promise;
+
+ /**
+ * Task information
+ */
+ task: {
+ id: string;
+ };
+
+ templateInfo?: TemplateInfo;
+
+ /**
+ * Whether this action invocation is a dry-run or not.
+ * This will only ever be true if the actions as marked as supporting dry-runs.
+ */
+ isDryRun?: boolean;
+
+ /**
+ * The user which triggered the action.
+ */
+ user?: {
+ /**
+ * The decorated entity from the Catalog
+ */
+ entity?: UserEntity;
+ /**
+ * An entity ref for the author of the task
+ */
+ ref?: string;
+ };
+
+ /**
+ * Implement the signal to make your custom step abortable https://developer.mozilla.org/en-US/docs/Web/API/AbortController/signal
+ */
+ signal?: AbortSignal;
+
+ /**
+ * Optional value of each invocation
+ */
+ each?: JsonObject;
+ };
/** @public */
export type TemplateAction<
TActionInput extends JsonObject = JsonObject,
TActionOutput extends JsonObject = JsonObject,
+ TSchemaType extends 'v1' | 'v2' = 'v1',
> = {
id: string;
description?: string;
@@ -110,5 +178,7 @@ export type TemplateAction<
input?: Schema;
output?: Schema;
};
- handler: (ctx: ActionContext) => Promise;
+ handler: (
+ ctx: ActionContext,
+ ) => Promise;
};
diff --git a/plugins/scaffolder-node/src/actions/util.ts b/plugins/scaffolder-node/src/actions/util.ts
index 5e4ce60a1a..f1775bb318 100644
--- a/plugins/scaffolder-node/src/actions/util.ts
+++ b/plugins/scaffolder-node/src/actions/util.ts
@@ -18,6 +18,10 @@ import { InputError } from '@backstage/errors';
import { isChildPath } from '@backstage/backend-plugin-api';
import { join as joinPath, normalize as normalizePath } from 'path';
import { ScmIntegrationRegistry } from '@backstage/integration';
+import { TemplateActionOptions } from './createTemplateAction';
+import zodToJsonSchema from 'zod-to-json-schema';
+import { z } from 'zod';
+import { Schema } from 'jsonschema';
/**
* @public
@@ -124,3 +128,60 @@ function checkRequiredParams(repoUrl: URL, ...params: string[]) {
}
}
}
+
+const isZodSchema = (schema: unknown): schema is z.ZodType => {
+ return typeof schema === 'object' && !!schema && 'safeParseAsync' in schema;
+};
+
+const isNativeZodSchema = (
+ schema: unknown,
+): schema is { [key in string]: (zImpl: typeof z) => z.ZodType } => {
+ return (
+ typeof schema === 'object' &&
+ !!schema &&
+ Object.values(schema).every(v => typeof v === 'function')
+ );
+};
+
+export const parseSchemas = (
+ action: TemplateActionOptions,
+): { inputSchema?: Schema; outputSchema?: Schema } => {
+ if (!action.schema) {
+ return { inputSchema: undefined, outputSchema: undefined };
+ }
+
+ if (isZodSchema(action.schema.input)) {
+ return {
+ inputSchema: zodToJsonSchema(action.schema.input) as Schema,
+ outputSchema: isZodSchema(action.schema.output)
+ ? (zodToJsonSchema(action.schema.output) as Schema)
+ : undefined,
+ };
+ }
+
+ if (isNativeZodSchema(action.schema.input)) {
+ const input = z.object(
+ Object.fromEntries(
+ Object.entries(action.schema.input).map(([k, v]) => [k, v(z)]),
+ ),
+ );
+
+ return {
+ inputSchema: zodToJsonSchema(input) as Schema,
+ outputSchema: isNativeZodSchema(action.schema.output)
+ ? (zodToJsonSchema(
+ z.object(
+ Object.fromEntries(
+ Object.entries(action.schema.output).map(([k, v]) => [k, v(z)]),
+ ),
+ ),
+ ) as Schema)
+ : undefined,
+ };
+ }
+
+ return {
+ inputSchema: action.schema.input,
+ outputSchema: action.schema.output,
+ };
+};
diff --git a/plugins/scaffolder-node/src/alpha/index.ts b/plugins/scaffolder-node/src/alpha/index.ts
index 1b573d0650..d97aa54557 100644
--- a/plugins/scaffolder-node/src/alpha/index.ts
+++ b/plugins/scaffolder-node/src/alpha/index.ts
@@ -34,7 +34,7 @@ export * from './globals';
* @alpha
*/
export interface ScaffolderActionsExtensionPoint {
- addActions(...actions: TemplateAction[]): void;
+ addActions(...actions: TemplateAction[]): void;
}
/**
diff --git a/plugins/scaffolder-node/src/scm/git.test.ts b/plugins/scaffolder-node/src/scm/git.test.ts
index 4a472a675a..dfbdd9cd16 100644
--- a/plugins/scaffolder-node/src/scm/git.test.ts
+++ b/plugins/scaffolder-node/src/scm/git.test.ts
@@ -16,6 +16,12 @@
jest.mock('isomorphic-git');
jest.mock('isomorphic-git/http/node');
jest.mock('fs-extra');
+jest.mock('@isomorphic-git/pgp-plugin', () => ({
+ ...jest.requireActual('@isomorphic-git/pgp-plugin'),
+ pgp: {
+ sign: jest.fn().mockResolvedValue({ signature: 'sign' }),
+ },
+}));
import * as isomorphic from 'isomorphic-git';
import { Git } from './git';
@@ -140,8 +146,9 @@ describe('Git', () => {
name: 'comitter',
email: 'test@backstage.io',
};
+ const signingKey = 'test-signing-key';
- await git.commit({ dir, message, author, committer });
+ await git.commit({ dir, message, author, committer, signingKey });
expect(isomorphic.commit).toHaveBeenCalledWith({
fs,
@@ -149,6 +156,8 @@ describe('Git', () => {
message,
author,
committer,
+ signingKey,
+ onSign: expect.any(Function),
});
});
});
diff --git a/plugins/scaffolder-node/src/scm/git.ts b/plugins/scaffolder-node/src/scm/git.ts
index 760be73114..42926fd901 100644
--- a/plugins/scaffolder-node/src/scm/git.ts
+++ b/plugins/scaffolder-node/src/scm/git.ts
@@ -15,14 +15,16 @@
*/
import git, {
- ProgressCallback,
- MergeResult,
- ReadCommitResult,
AuthCallback,
+ MergeResult,
+ ProgressCallback,
+ ReadCommitResult,
} from 'isomorphic-git';
import http from 'isomorphic-git/http/node';
import fs from 'fs-extra';
import { LoggerService } from '@backstage/backend-plugin-api';
+// @ts-ignore
+import { pgp } from '@isomorphic-git/pgp-plugin';
function isAuthCallbackOptions(
options: StaticAuthOptions | AuthCallbackOptions,
@@ -137,12 +139,21 @@ export class Git {
message: string;
author: { name: string; email: string };
committer: { name: string; email: string };
+ signingKey?: string;
}): Promise {
- const { dir, message, author, committer } = options;
+ const { dir, message, author, committer, signingKey } = options;
this.config.logger?.info(
`Committing file to repo {dir=${dir},message=${message}}`,
);
- return git.commit({ fs, dir, message, author, committer });
+ return git.commit({
+ fs,
+ dir,
+ message,
+ author,
+ committer,
+ signingKey,
+ onSign: signingKey ? pgp.sign : undefined,
+ });
}
/** https://isomorphic-git.org/docs/en/clone */
@@ -241,8 +252,9 @@ export class Git {
ours?: string;
author: { name: string; email: string };
committer: { name: string; email: string };
+ signingKey?: string;
}): Promise {
- const { dir, theirs, ours, author, committer } = options;
+ const { dir, theirs, ours, author, committer, signingKey } = options;
this.config.logger?.info(
`Merging branch '${theirs}' into '${ours}' for repository {dir=${dir}}`,
);
@@ -255,6 +267,8 @@ export class Git {
theirs,
author,
committer,
+ signingKey,
+ onSign: signingKey ? pgp.sign : undefined,
});
}
diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md
index f58e3b88ac..53c8d0858f 100644
--- a/plugins/scaffolder-react/CHANGELOG.md
+++ b/plugins/scaffolder-react/CHANGELOG.md
@@ -1,5 +1,25 @@
# @backstage/plugin-scaffolder-react
+## 1.14.6-next.2
+
+### Patch Changes
+
+- 48aab13: Add i18n support for scaffolder-react plugin
+- 3db64ba: Disable the submit button on creating
+- 34ea3f5: Updated dependency `flatted` to `3.3.3`.
+- Updated dependencies
+ - @backstage/frontend-plugin-api@0.10.0-next.2
+ - @backstage/plugin-catalog-react@1.16.0-next.2
+ - @backstage/core-components@0.16.5-next.1
+ - @backstage/catalog-client@1.9.1
+ - @backstage/catalog-model@1.7.3
+ - @backstage/core-plugin-api@1.10.4
+ - @backstage/theme@0.6.4
+ - @backstage/types@1.2.1
+ - @backstage/version-bridge@1.0.11
+ - @backstage/plugin-permission-react@0.4.31
+ - @backstage/plugin-scaffolder-common@1.5.10-next.0
+
## 1.14.6-next.1
### Patch Changes
diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json
index a500ac1b49..6fc4fa34b9 100644
--- a/plugins/scaffolder-react/package.json
+++ b/plugins/scaffolder-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder-react",
- "version": "1.14.6-next.1",
+ "version": "1.14.6-next.2",
"description": "A frontend library that helps other Backstage plugins interact with the Scaffolder",
"backstage": {
"role": "web-library",
@@ -81,7 +81,7 @@
"ajv": "^8.0.1",
"ajv-errors": "^3.0.0",
"classnames": "^2.2.6",
- "flatted": "3.3.2",
+ "flatted": "3.3.3",
"humanize-duration": "^3.25.1",
"immer": "^9.0.6",
"json-schema": "^0.4.0",
diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx
index 5f3dfdfa1d..c1d24ac6c6 100644
--- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx
+++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.test.tsx
@@ -15,17 +15,16 @@
*/
import { renderInTestApp } from '@backstage/test-utils';
import { JsonValue } from '@backstage/types';
+import type { RJSFValidationError } from '@rjsf/utils';
import { act, fireEvent, waitFor } from '@testing-library/react';
import React, { useEffect } from 'react';
+import { FieldExtensionComponentProps } from '../../../extensions';
import { LayoutTemplate } from '../../../layouts';
import { SecretsContextProvider } from '../../../secrets';
import { TemplateParameterSchema } from '../../../types';
import { Stepper } from './Stepper';
-import type { RJSFValidationError } from '@rjsf/utils';
-import { FieldExtensionComponentProps } from '../../../extensions';
-
describe('Stepper', () => {
it('should render the step titles for each step of the manifest', async () => {
const manifest: TemplateParameterSchema = {
@@ -762,7 +761,10 @@ describe('Stepper', () => {
],
};
- const onCreate = jest.fn();
+ // `onCreate` must be async to mock the submit button disabled behavior
+ const onCreate = jest.fn(
+ () => new Promise(resolve => setTimeout(resolve, 0)),
+ );
const { getByRole } = await renderInTestApp(
@@ -783,10 +785,18 @@ describe('Stepper', () => {
fireEvent.click(getByRole('button', { name: 'Review' }));
});
+ fireEvent.click(getByRole('button', { name: 'Create' }));
+
+ await waitFor(() =>
+ expect(getByRole('button', { name: 'Create' })).toBeDisabled(),
+ );
+
await act(async () => {
fireEvent.click(getByRole('button', { name: 'Create' }));
});
+ expect(onCreate).toHaveBeenCalledTimes(1);
+
expect(onCreate).toHaveBeenCalledWith(
expect.objectContaining({
thing: { repoOrg: 'backstage' },
diff --git a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx
index 9ca37e284a..7508985a5c 100644
--- a/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx
+++ b/plugins/scaffolder-react/src/next/components/Stepper/Stepper.tsx
@@ -115,7 +115,7 @@ export type StepperProps = {
*/
export const Stepper = (stepperProps: StepperProps) => {
const { t } = useTranslationRef(scaffolderReactTranslationRef);
- const { layouts = [], components = {}, ...props } = stepperProps;
+ const { layouts = [], components = {}, onCreate, ...props } = stepperProps;
const {
ReviewStateComponent = ReviewState,
ReviewStepComponent,
@@ -220,10 +220,17 @@ export const Stepper = (stepperProps: StepperProps) => {
const mergedUiSchema = merge({}, propUiSchema, currentStep?.uiSchema);
- const handleCreate = useCallback(() => {
- props.onCreate(stepsState);
+ const [isCreating, setIsCreating] = useState(false);
+
+ const handleCreate = useCallback(async () => {
+ setIsCreating(true);
analytics.captureEvent('click', `${createLabel}`);
- }, [props, stepsState, analytics, createLabel]);
+ try {
+ await onCreate(stepsState);
+ } finally {
+ setIsCreating(false);
+ }
+ }, [analytics, createLabel, onCreate, stepsState]);
return (
<>
@@ -318,6 +325,7 @@ export const Stepper = (stepperProps: StepperProps) => {
{backLabel}