From c985173cdfccfe0276e090e83145d6e3acf365e6 Mon Sep 17 00:00:00 2001 From: Danylo Hotvianskyi Date: Fri, 11 Jul 2025 17:40:41 +0200 Subject: [PATCH 01/71] feat(scafolder): scaffolder-backend-module-github implement github:issues:create Signed-off-by: Danylo Hotvianskyi --- .../actions/githubIssuesCreate.examples.ts | 58 ++++++ .../src/actions/githubIssuesCreate.test.ts | 144 +++++++++++++++ .../src/actions/githubIssuesCreate.ts | 166 ++++++++++++++++++ .../src/actions/index.ts | 1 + .../src/module.ts | 5 + 5 files changed, 374 insertions(+) create mode 100644 plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.examples.ts create mode 100644 plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.test.ts create mode 100644 plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.examples.ts new file mode 100644 index 0000000000..17f6c8078b --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.examples.ts @@ -0,0 +1,58 @@ +import { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import * as yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'Create a simple issue', + example: yaml.stringify({ + steps: [ + { + action: 'github:issues:create', + name: 'Create issue', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + title: 'Bug report', + body: 'Found a bug that needs to be fixed', + }, + }, + ], + }), + }, + { + description: 'Create an issue with labels and assignees', + example: yaml.stringify({ + steps: [ + { + action: 'github:issues:create', + name: 'Create issue with metadata', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + title: 'Feature request', + body: 'This is a new feature request', + labels: ['enhancement', 'needs-review'], + assignees: ['octocat'], + milestone: 1, + }, + }, + ], + }), + }, + { + description: 'Create an issue with specific token', + example: yaml.stringify({ + steps: [ + { + action: 'github:issues:create', + name: 'Create issue with token', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + title: 'Documentation update', + body: 'Update the documentation for the new API', + labels: ['documentation'], + token: 'gph_YourGitHubToken', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.test.ts new file mode 100644 index 0000000000..440df36915 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.test.ts @@ -0,0 +1,144 @@ +import { createGithubIssuesCreateAction } from './githubIssuesCreate'; +import { + ScmIntegrations, + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, +} from '@backstage/integration'; +import { createMockActionContext } from '@backstage/plugin-scaffolder-node-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { getOctokitOptions } from '../util'; + +jest.mock('../util', () => { + return { + getOctokitOptions: jest.fn(), + }; +}); + +import { Octokit } from 'octokit'; + +const octokitMock = Octokit as unknown as jest.Mock; +const mockOctokit = { + rest: { + issues: { + create: jest.fn(), + }, + }, +}; +jest.mock('octokit', () => ({ + Octokit: jest.fn(), +})); + +describe('github:issues:create', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'tokenlols' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const getOctokitOptionsMock = getOctokitOptions as jest.Mock; + const integrations = ScmIntegrations.fromConfig(config); + let githubCredentialsProvider: GithubCredentialsProvider; + let action: TemplateAction; + + const mockContext = createMockActionContext({ + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + title: 'Test Issue', + body: 'This is a test issue', + labels: ['bug', 'test'], + assignees: ['octocat'], + milestone: 1, + }, + }); + + beforeEach(() => { + jest.resetAllMocks(); + octokitMock.mockImplementation(() => mockOctokit); + mockOctokit.rest.issues.create.mockResolvedValue({ + data: { + html_url: 'https://github.com/owner/repo/issues/1', + number: 1, + }, + }); + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubIssuesCreateAction({ + integrations, + githubCredentialsProvider, + }); + }); + + it('should pass context logger to Octokit client', async () => { + await action.handler(mockContext); + + expect(octokitMock).toHaveBeenCalledWith( + expect.objectContaining({ log: mockContext.logger }), + ); + }); + + it('should call the githubApi for creating issue', async () => { + await action.handler(mockContext); + expect(mockOctokit.rest.issues.create).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + title: 'Test Issue', + body: 'This is a test issue', + labels: ['bug', 'test'], + assignees: ['octocat'], + milestone: 1, + }); + expect(getOctokitOptionsMock.mock.calls[0][0].token).toBeUndefined(); + }); + + it('should call the githubApi for creating issue with token', async () => { + await action.handler({ + ...mockContext, + input: { ...mockContext.input, token: 'gph_YourGitHubToken' }, + }); + expect(mockOctokit.rest.issues.create).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + title: 'Test Issue', + body: 'This is a test issue', + labels: ['bug', 'test'], + assignees: ['octocat'], + milestone: 1, + }); + expect(getOctokitOptionsMock.mock.calls[0][0].token).toEqual( + 'gph_YourGitHubToken', + ); + }); + + it('should output issue URL and number', async () => { + await action.handler(mockContext); + expect(mockContext.output).toHaveBeenCalledWith( + 'issueUrl', + 'https://github.com/owner/repo/issues/1', + ); + expect(mockContext.output).toHaveBeenCalledWith('issueNumber', 1); + }); + + it('should create issue with minimal input', async () => { + const minimalContext = createMockActionContext({ + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + title: 'Simple Issue', + }, + }); + + await action.handler(minimalContext); + expect(mockOctokit.rest.issues.create).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + title: 'Simple Issue', + body: undefined, + labels: undefined, + assignees: undefined, + milestone: undefined, + }); + }); +}); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts new file mode 100644 index 0000000000..757e4ac675 --- /dev/null +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts @@ -0,0 +1,166 @@ +import { + GithubCredentialsProvider, + ScmIntegrationRegistry, +} from '@backstage/integration'; +import { + createTemplateAction, + parseRepoUrl, +} from '@backstage/plugin-scaffolder-node'; +import { assertError, InputError } from '@backstage/errors'; +import { Octokit } from 'octokit'; +import { getOctokitOptions } from '../util'; +import { examples } from './githubIssuesCreate.examples'; + +/** + * Creates an issue on GitHub + * @public + */ +export function createGithubIssuesCreateAction(options: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; +}) { + const { integrations, githubCredentialsProvider } = options; + + return createTemplateAction({ + id: 'github:issues:create', + description: 'Creates an issue on GitHub.', + examples, + supportsDryRun: true, + schema: { + input: { + repoUrl: z => + z.string({ + description: + 'Accepts the format `github.com?repo=reponame&owner=owner` where `reponame` is the repository name and `owner` is an organization or username', + }), + title: z => + z.string({ + description: 'The title of the issue', + }), + body: z => + z + .string({ + description: 'The contents of the issue', + }) + .optional(), + assignees: z => + z + .array(z.string(), { + description: + 'Logins for Users to assign to this issue. NOTE: Only users with push access can set assignees for new issues. Assignees are silently dropped otherwise.', + }) + .optional(), + milestone: z => + z + .union([z.string(), z.number()], { + description: + 'The number of the milestone to associate this issue with. NOTE: Only users with push access can set the milestone for new issues. The milestone is silently dropped otherwise.', + }) + .optional(), + labels: z => + z + .array(z.string(), { + description: + 'Labels to associate with this issue. NOTE: Only users with push access can set labels for new issues. Labels are silently dropped otherwise.', + }) + .optional(), + token: z => + z + .string({ + description: + 'The `GITHUB_TOKEN` to use for authorization to GitHub', + }) + .optional(), + }, + output: { + issueUrl: z => + z.string({ + description: 'The URL of the created issue', + }), + issueNumber: z => + z.number({ + description: 'The number of the created issue', + }), + }, + }, + async handler(ctx) { + const { + repoUrl, + title, + body, + assignees, + milestone, + labels, + token: providedToken, + } = ctx.input; + + const { host, owner, repo } = parseRepoUrl(repoUrl, integrations); + ctx.logger.info(`Creating issue "${title}" on repo ${repo}`); + + if (!owner) { + throw new InputError('Invalid repository owner provided in repoUrl'); + } + + const octokitOptions = await getOctokitOptions({ + integrations, + credentialsProvider: githubCredentialsProvider, + host, + owner, + repo, + token: providedToken, + }); + + const client = new Octokit({ + ...octokitOptions, + log: ctx.logger, + }); + + if (ctx.isDryRun) { + ctx.logger.info(`Performing dry run of creating issue "${title}"`); + ctx.output('issueUrl', `https://github.com/${owner}/${repo}/issues/42`); + ctx.output('issueNumber', 42); + ctx.logger.info(`Dry run complete`); + return; + } + + try { + const issue = await ctx.checkpoint({ + key: `github.issues.create.${owner}.${repo}.${title}`, + fn: async () => { + const response = await client.rest.issues.create({ + owner, + repo, + title, + body, + assignees, + milestone, + labels, + }); + + return { + html_url: response.data.html_url, + number: response.data.number, + }; + }, + }); + + if (!issue) { + throw new Error('Failed to create issue'); + } + + ctx.output('issueUrl', issue.html_url); + ctx.output('issueNumber', issue.number); + + ctx.logger.info( + `Successfully created issue #${issue.number}: ${issue.html_url}`, + ); + } catch (e) { + assertError(e); + ctx.logger.warn( + `Failed: creating issue '${title}' on repo: '${repo}', ${e.message}`, + ); + throw e; + } + }, + }); +} diff --git a/plugins/scaffolder-backend-module-github/src/actions/index.ts b/plugins/scaffolder-backend-module-github/src/actions/index.ts index 7cf4109e6b..21732d7908 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/index.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/index.ts @@ -16,6 +16,7 @@ export { createGithubActionsDispatchAction } from './githubActionsDispatch'; export { createGithubIssuesLabelAction } from './githubIssuesLabel'; +export { createGithubIssuesCreateAction } from './githubIssuesCreate'; export { createGithubRepoCreateAction } from './githubRepoCreate'; export { createGithubRepoPushAction } from './githubRepoPush'; export { createGithubWebhookAction } from './githubWebhook'; diff --git a/plugins/scaffolder-backend-module-github/src/module.ts b/plugins/scaffolder-backend-module-github/src/module.ts index 5812a22560..67bda58fc4 100644 --- a/plugins/scaffolder-backend-module-github/src/module.ts +++ b/plugins/scaffolder-backend-module-github/src/module.ts @@ -27,6 +27,7 @@ import { createGithubDeployKeyAction, createGithubEnvironmentAction, createGithubIssuesLabelAction, + createGithubIssuesCreateAction, createGithubRepoCreateAction, createGithubRepoPushAction, createGithubWebhookAction, @@ -82,6 +83,10 @@ export const githubModule = createBackendModule({ integrations, githubCredentialsProvider, }), + createGithubIssuesCreateAction({ + integrations, + githubCredentialsProvider, + }), createGithubRepoCreateAction({ integrations, githubCredentialsProvider, From f0f06b43542dec7533ae5c4d9cdb7b9a654139bc Mon Sep 17 00:00:00 2001 From: Danylo Hotvianskyi Date: Fri, 11 Jul 2025 18:12:07 +0200 Subject: [PATCH 02/71] add changeset Signed-off-by: Danylo Hotvianskyi --- .changeset/stupid-pans-flash.md | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .changeset/stupid-pans-flash.md diff --git a/.changeset/stupid-pans-flash.md b/.changeset/stupid-pans-flash.md new file mode 100644 index 0000000000..bc857a243a --- /dev/null +++ b/.changeset/stupid-pans-flash.md @@ -0,0 +1,35 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': minor +--- + +Adding a new scaffolder action `github:issues:create` following the reference of `github:issues:label` with dryRun testing possibility + +It can be used like this + +``` + steps: + - id: create-simple-issue + name: Create Simple Issue + action: github:issues:create + input: + repoUrl: ${{ parameters.repoUrl }} + title: "[${{ parameters.projectName }}] Simple Bug Report" + body: | + ## Bug Description + This is a simple bug report created by the scaffolder template. + + ### Steps to Reproduce + 1. Run the application + 2. Navigate to the main page + 3. Click on the problematic button + + ### Expected Behavior + The button should work correctly. + + ### Actual Behavior + The button does not respond to clicks. + output: + links: + - title: Simple Issue + url: ${{ steps['create-simple-issue'].output.issueUrl }} +``` From 16ee5911ebc5d3051b25652695f241e99df12d89 Mon Sep 17 00:00:00 2001 From: Danylo Hotvianskyi Date: Fri, 11 Jul 2025 18:27:29 +0200 Subject: [PATCH 03/71] Add notices Signed-off-by: Danylo Hotvianskyi --- .../actions/githubIssuesCreate.examples.ts | 15 ++++++++++++++ .../src/actions/githubIssuesCreate.test.ts | 16 +++++++++++++++ .../src/actions/githubIssuesCreate.ts | 20 +++++++++++++++++-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.examples.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.examples.ts index 17f6c8078b..7d767f0337 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.examples.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.examples.ts @@ -1,3 +1,18 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { TemplateExample } from '@backstage/plugin-scaffolder-node'; import * as yaml from 'yaml'; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.test.ts index 440df36915..474acf14d7 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.test.ts @@ -1,3 +1,19 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { createGithubIssuesCreateAction } from './githubIssuesCreate'; import { ScmIntegrations, diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts index 757e4ac675..5c5703e20d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubIssuesCreate.ts @@ -1,3 +1,19 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import { GithubCredentialsProvider, ScmIntegrationRegistry, @@ -109,7 +125,7 @@ export function createGithubIssuesCreateAction(options: { repo, token: providedToken, }); - + const client = new Octokit({ ...octokitOptions, log: ctx.logger, @@ -157,7 +173,7 @@ export function createGithubIssuesCreateAction(options: { } catch (e) { assertError(e); ctx.logger.warn( - `Failed: creating issue '${title}' on repo: '${repo}', ${e.message}`, + `Failed: creating issue '${title}' on repo: '${repo}', ${e.message}`, ); throw e; } From d5f4dd1c9078a0903e8578649e30ad6024ddf629 Mon Sep 17 00:00:00 2001 From: Danylo Hotvianskyi Date: Fri, 11 Jul 2025 18:55:13 +0200 Subject: [PATCH 04/71] Changes in public API Signed-off-by: Danylo Hotvianskyi --- .../report.api.md | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index 2941be7702..a2dcc3ec4e 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -136,6 +136,27 @@ export function createGithubEnvironmentAction(options: { 'v2' >; +// @public +export function createGithubIssuesCreateAction(options: { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; +}): TemplateAction< + { + repoUrl: string; + title: string; + body?: string | undefined; + assignees?: string[] | undefined; + milestone?: string | number | undefined; + labels?: string[] | undefined; + token?: string | undefined; + }, + { + issueUrl: string; + issueNumber: number; + }, + 'v2' +>; + // @public export function createGithubIssuesLabelAction(options: { integrations: ScmIntegrationRegistry; From 5ae6d9dd4bd709a44bc61bcc326585ccfff27ab2 Mon Sep 17 00:00:00 2001 From: JounQin Date: Tue, 29 Jul 2025 17:33:05 +0800 Subject: [PATCH 05/71] feat(core-app-api): support no en languages Signed-off-by: JounQin --- .changeset/tasty-yaks-kiss.md | 5 +++++ .../AppLanguageApi/AppLanguageSelector.test.ts | 17 ++++++++++++++++- .../AppLanguageApi/AppLanguageSelector.ts | 4 +--- 3 files changed, 22 insertions(+), 4 deletions(-) create mode 100644 .changeset/tasty-yaks-kiss.md diff --git a/.changeset/tasty-yaks-kiss.md b/.changeset/tasty-yaks-kiss.md new file mode 100644 index 0000000000..125b77a870 --- /dev/null +++ b/.changeset/tasty-yaks-kiss.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +feat: support no en languages diff --git a/packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.test.ts b/packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.test.ts index 702d5e642a..eeeb11634d 100644 --- a/packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.test.ts +++ b/packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.test.ts @@ -144,11 +144,26 @@ describe('AppLanguageSelector', () => { AppLanguageSelector.createWithStorage({ availableLanguages: ['de'], }), - ).toThrow("Supported languages must include 'en'"); + ).toThrow( + "Initial language must be one of the supported languages, got 'en'", + ); const selector = AppLanguageSelector.createWithStorage(baseOptions); expect(() => selector.setLanguage('sv')).toThrow( "Failed to change language to 'sv', available languages are 'en', 'de'", ); }); + + it('should support no en languages', () => { + const selector = AppLanguageSelector.createWithStorage({ + availableLanguages: ['de'], + defaultLanguage: 'de', + }); + + expect(selector.getLanguage()).toEqual({ language: 'de' }); + + expect(() => selector.setLanguage('en')).toThrow( + "Failed to change language to 'en', available languages are 'de'", + ); + }); }); diff --git a/packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.ts b/packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.ts index 93f3d6517c..04dcfe9ebb 100644 --- a/packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.ts +++ b/packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.ts @@ -44,11 +44,9 @@ export class AppLanguageSelector implements AppLanguageApi { )}'`, ); } - if (!languages.includes(DEFAULT_LANGUAGE)) { - throw new Error(`Supported languages must include '${DEFAULT_LANGUAGE}'`); - } const initialLanguage = options?.defaultLanguage ?? DEFAULT_LANGUAGE; + if (!languages.includes(initialLanguage)) { throw new Error( `Initial language must be one of the supported languages, got '${initialLanguage}'`, From 4316c11c3d105b0e8f2d0066e967efbea40769d4 Mon Sep 17 00:00:00 2001 From: mario ma Date: Wed, 30 Jul 2025 17:55:39 +0800 Subject: [PATCH 06/71] feat: Catalog table columns support i18n Signed-off-by: mario ma --- .changeset/lovely-frogs-share.md | 6 +++ plugins/catalog-react/report-alpha.api.md | 46 ++++++++++++++++++ plugins/catalog-react/report.api.md | 4 +- plugins/catalog-react/src/alpha/index.ts | 1 + .../EntityTable/TitleColumn.test.tsx | 25 ++++++++++ .../components/EntityTable/TitleColumn.tsx | 47 +++++++++++++++++++ .../src/components/EntityTable/columns.tsx | 19 ++++---- plugins/catalog-react/src/translation.ts | 14 ++++++ .../src/components/CatalogTable/columns.tsx | 25 +++++----- 9 files changed, 164 insertions(+), 23 deletions(-) create mode 100644 .changeset/lovely-frogs-share.md create mode 100644 plugins/catalog-react/src/components/EntityTable/TitleColumn.test.tsx create mode 100644 plugins/catalog-react/src/components/EntityTable/TitleColumn.tsx diff --git a/.changeset/lovely-frogs-share.md b/.changeset/lovely-frogs-share.md new file mode 100644 index 0000000000..5c630f301e --- /dev/null +++ b/.changeset/lovely-frogs-share.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-catalog': patch +--- + +Catalog table columns support i18n diff --git a/plugins/catalog-react/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md index 1db7a8e5e4..ebdf9f0e79 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -86,6 +86,18 @@ export const catalogReactTranslationRef: TranslationRef< readonly 'userListPicker.personalFilter.title': 'Personal'; readonly 'userListPicker.personalFilter.ownedLabel': 'Owned'; readonly 'userListPicker.personalFilter.starredLabel': 'Starred'; + readonly 'entityTableColumnTitle.name': 'Name'; + readonly 'entityTableColumnTitle.type': 'Type'; + readonly 'entityTableColumnTitle.label': 'Label'; + readonly 'entityTableColumnTitle.title': 'Title'; + readonly 'entityTableColumnTitle.description': 'Description'; + readonly 'entityTableColumnTitle.domain': 'Domain'; + readonly 'entityTableColumnTitle.namespace': 'Namespace'; + readonly 'entityTableColumnTitle.lifecycle': 'Lifecycle'; + readonly 'entityTableColumnTitle.owner': 'Owner'; + readonly 'entityTableColumnTitle.system': 'System'; + readonly 'entityTableColumnTitle.targets': 'Targets'; + readonly 'entityTableColumnTitle.tags': 'Tags'; } >; @@ -517,6 +529,40 @@ export type EntityPredicateValue = $contains: EntityPredicateExpression; }; +// @alpha (undocumented) +export const EntityTableColumnTitle: ({ + translationKey, +}: EntityTableColumnTitleProps) => + | 'Domain' + | 'System' + | 'Name' + | 'Description' + | 'Lifecycle' + | 'Namespace' + | 'Owner' + | 'Tags' + | 'Type' + | 'Targets' + | 'Title' + | 'Label'; + +// @alpha (undocumented) +export type EntityTableColumnTitleProps = { + translationKey: + | 'name' + | 'system' + | 'owner' + | 'type' + | 'lifecycle' + | 'namespace' + | 'description' + | 'tags' + | 'targets' + | 'title' + | 'label' + | 'domain'; +}; + // @alpha export function isOwnerOf(owner: Entity, entity: Entity): boolean; diff --git a/plugins/catalog-react/report.api.md b/plugins/catalog-react/report.api.md index bfebb2b13a..df32c51416 100644 --- a/plugins/catalog-react/report.api.md +++ b/plugins/catalog-react/report.api.md @@ -171,7 +171,7 @@ export const columnFactories: Readonly<{ defaultKind?: string; }): TableColumn; createEntityRelationColumn(options: { - title: string; + title: string | JSX.Element; relation: string; defaultKind?: string; filter?: { @@ -575,7 +575,7 @@ export const EntityTable: { defaultKind?: string; }): TableColumn; createEntityRelationColumn(options: { - title: string; + title: string | JSX.Element; relation: string; defaultKind?: string; filter?: { diff --git a/plugins/catalog-react/src/alpha/index.ts b/plugins/catalog-react/src/alpha/index.ts index 4ff4dbf0dd..f5909c9aa2 100644 --- a/plugins/catalog-react/src/alpha/index.ts +++ b/plugins/catalog-react/src/alpha/index.ts @@ -20,3 +20,4 @@ export * from './predicates'; export { catalogReactTranslationRef } from '../translation'; export { isOwnerOf } from '../utils/isOwnerOf'; export { useEntityPermission } from '../hooks/useEntityPermission'; +export * from '../components/EntityTable/TitleColumn'; diff --git a/plugins/catalog-react/src/components/EntityTable/TitleColumn.test.tsx b/plugins/catalog-react/src/components/EntityTable/TitleColumn.test.tsx new file mode 100644 index 0000000000..e08741d274 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityTable/TitleColumn.test.tsx @@ -0,0 +1,25 @@ +/* + * 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 { screen } from '@testing-library/react'; +import { EntityTableColumnTitle } from './TitleColumn'; +import { renderInTestApp } from '@backstage/test-utils'; + +describe('', () => { + it('renders the translated title for the given key', async () => { + await renderInTestApp(); + expect(screen.getByText('Name')).toBeInTheDocument(); + }); +}); diff --git a/plugins/catalog-react/src/components/EntityTable/TitleColumn.tsx b/plugins/catalog-react/src/components/EntityTable/TitleColumn.tsx new file mode 100644 index 0000000000..36bce84882 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityTable/TitleColumn.tsx @@ -0,0 +1,47 @@ +/* + * 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 { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { catalogReactTranslationRef } from '../../translation'; + +/** + * @alpha + */ +export type EntityTableColumnTitleProps = { + translationKey: + | 'name' + | 'system' + | 'owner' + | 'type' + | 'lifecycle' + | 'namespace' + | 'description' + | 'tags' + | 'targets' + | 'title' + | 'label' + | 'domain'; +}; + +/** + * @alpha + */ +export const EntityTableColumnTitle = ({ + translationKey, +}: EntityTableColumnTitleProps) => { + const { t } = useTranslationRef(catalogReactTranslationRef); + return t(`entityTableColumnTitle.${translationKey}`); +}; diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index 00e1e5ad43..94b1141057 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -27,8 +27,7 @@ import { EntityRefLinks, humanizeEntityRef, } from '../EntityRefLink'; - -// TODO: column title support i18n +import { EntityTableColumnTitle } from './TitleColumn'; /** @public */ export const columnFactories = Object.freeze({ @@ -46,7 +45,7 @@ export const columnFactories = Object.freeze({ } return { - title: 'Name', + title: , highlight: true, customFilterAndSearch(filter, entity) { // TODO: We could implement this more efficiently, like searching over @@ -72,7 +71,7 @@ export const columnFactories = Object.freeze({ }; }, createEntityRelationColumn(options: { - title: string; + title: string | JSX.Element; relation: string; defaultKind?: string; filter?: { kind: string }; @@ -109,14 +108,14 @@ export const columnFactories = Object.freeze({ }, createOwnerColumn(): TableColumn { return this.createEntityRelationColumn({ - title: 'Owner', + title: , relation: RELATION_OWNED_BY, defaultKind: 'group', }); }, createDomainColumn(): TableColumn { return this.createEntityRelationColumn({ - title: 'Domain', + title: , relation: RELATION_PART_OF, defaultKind: 'domain', filter: { @@ -126,7 +125,7 @@ export const columnFactories = Object.freeze({ }, createSystemColumn(): TableColumn { return this.createEntityRelationColumn({ - title: 'System', + title: , relation: RELATION_PART_OF, defaultKind: 'system', filter: { @@ -136,7 +135,7 @@ export const columnFactories = Object.freeze({ }, createMetadataDescriptionColumn(): TableColumn { return { - title: 'Description', + title: , field: 'metadata.description', render: entity => ( (): TableColumn { return { - title: 'Lifecycle', + title: , field: 'spec.lifecycle', }; }, createSpecTypeColumn(): TableColumn { return { - title: 'Type', + title: , field: 'spec.type', }; }, diff --git a/plugins/catalog-react/src/translation.ts b/plugins/catalog-react/src/translation.ts index 7f9661380f..6e831886cd 100644 --- a/plugins/catalog-react/src/translation.ts +++ b/plugins/catalog-react/src/translation.ts @@ -124,5 +124,19 @@ export const catalogReactTranslationRef = createTranslationRef({ }, orgFilterAllLabel: 'All', }, + entityTableColumnTitle: { + name: 'Name', + system: 'System', + owner: 'Owner', + type: 'Type', + lifecycle: 'Lifecycle', + namespace: 'Namespace', + description: 'Description', + tags: 'Tags', + targets: 'Targets', + title: 'Title', + label: 'Label', + domain: 'Domain', + }, }, }); diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx index 05674f3e1b..65094bc568 100644 --- a/plugins/catalog/src/components/CatalogTable/columns.tsx +++ b/plugins/catalog/src/components/CatalogTable/columns.tsx @@ -23,6 +23,7 @@ import { CatalogTableRow } from './types'; import { OverflowTooltip, TableColumn } from '@backstage/core-components'; import { Entity } from '@backstage/catalog-model'; import { JsonArray } from '@backstage/types'; +import { EntityTableColumnTitle } from '@backstage/plugin-catalog-react/alpha'; // The columnFactories symbol is not directly exported, but through the // CatalogTable.columns field. @@ -41,7 +42,7 @@ export const columnFactories = Object.freeze({ } return { - title: 'Name', + title: , field: 'resolved.entityRef', highlight: true, customSort({ entity: entity1 }, { entity: entity2 }) { @@ -59,7 +60,7 @@ export const columnFactories = Object.freeze({ }, createSystemColumn(): TableColumn { return { - title: 'System', + title: , field: 'resolved.partOfSystemRelationTitle', customFilterAndSearch: (query, row) => { if (!row.resolved.partOfSystemRelations) { @@ -83,7 +84,7 @@ export const columnFactories = Object.freeze({ }, createOwnerColumn(): TableColumn { return { - title: 'Owner', + title: , field: 'resolved.ownedByRelationsTitle', render: ({ resolved }) => ( { return { - title: 'Targets', + title: , field: 'entity.spec.targets', customFilterAndSearch: (query, row) => { let targets: JsonArray = []; @@ -132,7 +133,7 @@ export const columnFactories = Object.freeze({ } = { hidden: false }, ): TableColumn { return { - title: 'Type', + title: , field: 'entity.spec.type', hidden: options.hidden, width: 'auto', @@ -140,13 +141,13 @@ export const columnFactories = Object.freeze({ }, createSpecLifecycleColumn(): TableColumn { return { - title: 'Lifecycle', + title: , field: 'entity.spec.lifecycle', }; }, createMetadataDescriptionColumn(): TableColumn { return { - title: 'Description', + title: , field: 'entity.metadata.description', render: ({ entity }) => ( { return { - title: 'Tags', + title: , field: 'entity.metadata.tags', cellStyle: { padding: '0px 16px 0px 20px', @@ -185,7 +186,7 @@ export const columnFactories = Object.freeze({ hidden?: boolean; }): TableColumn { return { - title: 'Title', + title: , field: 'entity.metadata.title', hidden: options?.hidden, searchable: true, @@ -202,7 +203,9 @@ export const columnFactories = Object.freeze({ } return { - title: options?.title || 'Label', + title: options?.title || ( + + ), field: 'entity.metadata.labels', cellStyle: { padding: '0px 16px 0px 20px', @@ -235,7 +238,7 @@ export const columnFactories = Object.freeze({ }, createNamespaceColumn(): TableColumn { return { - title: 'Namespace', + title: , field: 'entity.metadata.namespace', width: 'auto', }; From 88225a8cf3ede83dc9b932ae560bb9cf111e3707 Mon Sep 17 00:00:00 2001 From: mario ma Date: Fri, 1 Aug 2025 15:27:21 +0800 Subject: [PATCH 07/71] fix: api report Signed-off-by: mario ma --- plugins/catalog-react/report-alpha.api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-react/report-alpha.api.md b/plugins/catalog-react/report-alpha.api.md index ebdf9f0e79..132cf5eac7 100644 --- a/plugins/catalog-react/report-alpha.api.md +++ b/plugins/catalog-react/report-alpha.api.md @@ -533,17 +533,17 @@ export type EntityPredicateValue = export const EntityTableColumnTitle: ({ translationKey, }: EntityTableColumnTitleProps) => + | 'Title' | 'Domain' | 'System' - | 'Name' - | 'Description' | 'Lifecycle' | 'Namespace' | 'Owner' | 'Tags' | 'Type' + | 'Name' + | 'Description' | 'Targets' - | 'Title' | 'Label'; // @alpha (undocumented) From ea80e76e7c997ecacd57b551ce01fbc9b390e6cb Mon Sep 17 00:00:00 2001 From: Patrick McDonnell Date: Fri, 1 Aug 2025 13:35:47 +0200 Subject: [PATCH 08/71] perf: use GitLab API simple=true parameter when not filtering forks Reduces response size by conditionally using simple=true in GitLab list projects API calls. Only applied when skipForkedRepos is false since fork detection requires the forked_from_project field excluded by simple. Signed-off-by: Patrick McDonnell --- .changeset/cuddly-lemons-cheat.md | 5 + .../src/GitLabDiscoveryProcessor.test.ts | 107 +++++++++++++++ .../src/GitLabDiscoveryProcessor.ts | 5 + .../src/lib/client.test.ts | 49 +++++++ .../src/lib/client.ts | 1 + .../GitlabDiscoveryEntityProvider.test.ts | 127 ++++++++++++++++++ .../GitlabDiscoveryEntityProvider.ts | 5 + 7 files changed, 299 insertions(+) create mode 100644 .changeset/cuddly-lemons-cheat.md diff --git a/.changeset/cuddly-lemons-cheat.md b/.changeset/cuddly-lemons-cheat.md new file mode 100644 index 0000000000..aa4ee786ff --- /dev/null +++ b/.changeset/cuddly-lemons-cheat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +When possible, requests a more limited set of results from the Gitlab projects API, which can reduce the amount of network traffic required to sync with Gitlab. diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts index d75d0e224e..21cbd24232 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts @@ -59,6 +59,7 @@ function setupFakeServer( page: number; include_subgroups: boolean; archived: boolean; + simple?: boolean; }) => { data: GitLabProject[]; nextPage?: number; @@ -78,10 +79,12 @@ function setupFakeServer( const page = req.url.searchParams.get('page'); const include_subgroups = req.url.searchParams.get('include_subgroups'); const archived = req.url.searchParams.get('archived'); + const simple = req.url.searchParams.get('simple'); const response = listProjectsCallback({ page: parseInt(page!, 10), include_subgroups: include_subgroups === 'true', archived: archived === 'true', + simple: simple === 'true', }); // Filter the fake results based on the `last_activity_after` parameter @@ -471,6 +474,110 @@ describe('GitlabDiscoveryProcessor', () => { }); expect(result2).toHaveLength(1); }); + + it('sets simple=true when skipForkedRepos is false', async () => { + const processor = getProcessor({ + options: { skipForkedRepos: false }, + }); + setupFakeServer( + PROJECTS_URL, + request => { + return { + data: [ + { + id: 1, + archived: false, + default_branch: 'main', + last_activity_at: '2021-08-05T11:03:05.774Z', + web_url: 'https://gitlab.fake/1', + path_with_namespace: '1', + }, + ], + }; + }, + request => { + // Verify that simple=true is set in the request + expect(request.url.searchParams.get('simple')).toBe('true'); + }, + ); + + const result: any[] = []; + await processor.readLocation(PROJECT_LOCATION, false, e => { + result.push(e); + }); + expect(result).toHaveLength(1); + }); + + it('does not set simple when skipForkedRepos is true', async () => { + const processor = getProcessor({ + options: { skipForkedRepos: true }, + }); + setupFakeServer( + PROJECTS_URL, + request => { + return { + data: [ + { + id: 1, + archived: false, + default_branch: 'main', + last_activity_at: '2021-08-05T11:03:05.774Z', + web_url: 'https://gitlab.fake/1', + path_with_namespace: '1', + // Include forked_from_project to test fork filtering + forked_from_project: { + id: 100, + name: 'original-project', + }, + }, + ], + }; + }, + request => { + // Verify that simple parameter is not set + expect(request.url.searchParams.get('simple')).toBeNull(); + }, + ); + + const result: any[] = []; + await processor.readLocation(PROJECT_LOCATION, false, e => { + result.push(e); + }); + // Should be empty because forked repo is skipped + expect(result).toHaveLength(0); + }); + + it('sets default parameters correctly (archived=false, simple=true)', async () => { + const processor = getProcessor(); // Uses defaults: skipForkedRepos=false, includeArchivedRepos=false + setupFakeServer( + PROJECTS_URL, + request => { + return { + data: [ + { + id: 1, + archived: false, + default_branch: 'main', + last_activity_at: '2021-08-05T11:03:05.774Z', + web_url: 'https://gitlab.fake/1', + path_with_namespace: '1', + }, + ], + }; + }, + request => { + // Verify default parameters: archived=false and simple=true + expect(request.url.searchParams.get('archived')).toBe('false'); + expect(request.url.searchParams.get('simple')).toBe('true'); + }, + ); + + const result: any[] = []; + await processor.readLocation(PROJECT_LOCATION, false, e => { + result.push(e); + }); + expect(result).toHaveLength(1); + }); }); describe('handles failure', () => { diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts index ab31a44bbc..126bb07f03 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts @@ -115,6 +115,11 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { // that the options doesn't include the key so that the API doesn't receive an empty query parameter. ...(lastActivity && { last_activity_after: lastActivity }), ...(!this.includeArchivedRepos && { archived: false }), + // Only use simple=true when we don't need to skip forked repos. + // The simple=true parameter reduces response size by returning fewer fields, + // but it excludes the 'forked_from_project' field which is required for fork detection. + // Therefore, we can only optimize with simple=true when skipForkedRepos is false. + ...(!this.skipForkedRepos && { simple: true }), }; const projects = paginated(options => client.listProjects(options), opts); diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts index daea71060f..9b3bac6399 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.test.ts @@ -187,6 +187,55 @@ describe('GitLabClient', () => { expect(allProjects).toHaveLength(mock.all_projects_response.length); }); + + it('should pass simple parameter to API when provided', async () => { + const client = new GitLabClient({ + config: readGitLabIntegrationConfig( + new ConfigReader(mock.config_self_managed), + ), + logger: mockServices.logger.mock(), + }); + + // Mock the pagedRequest method to verify parameters + const mockPagedRequest = jest.fn().mockResolvedValue({ + items: [], + nextPage: undefined, + }); + (client as any).pagedRequest = mockPagedRequest; + + await client.listProjects({ simple: true }); + + expect(mockPagedRequest).toHaveBeenCalledWith('/projects', { + simple: true, + }); + }); + + it('should pass simple parameter to group projects API when provided', async () => { + const client = new GitLabClient({ + config: readGitLabIntegrationConfig( + new ConfigReader(mock.config_self_managed), + ), + logger: mockServices.logger.mock(), + }); + + // Mock the pagedRequest method to verify parameters + const mockPagedRequest = jest.fn().mockResolvedValue({ + items: [], + nextPage: undefined, + }); + (client as any).pagedRequest = mockPagedRequest; + + await client.listProjects({ group: 'test-group', simple: true }); + + expect(mockPagedRequest).toHaveBeenCalledWith( + '/groups/test-group/projects', + { + group: 'test-group', + simple: true, + include_subgroups: true, + }, + ); + }); }); describe('listUsers', () => { diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts index e58227839e..3178e2c0d1 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts @@ -43,6 +43,7 @@ interface ListProjectOptions extends CommonListOptions { group?: string; membership?: boolean; topics?: string; + simple?: boolean; } interface UserListOptions extends CommonListOptions { diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts index dd7a4f0d6d..b03f2c4b5b 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -147,6 +147,21 @@ describe('GitlabDiscoveryEntityProvider - refresh', () => { 'GitlabDiscoveryEntityProvider:test-id', ); + // Mock the GitLabClient listProjects method to verify default parameters + const originalListProjects = (provider as any).gitLabClient.listProjects; + const mockListProjects = jest.fn().mockImplementation(async options => { + // Verify default parameters: archived=false and simple=true (since skipForkedRepos=false by default) + expect(options).toMatchObject({ + group: 'group1', + per_page: 50, + archived: false, + simple: true, // Should be set since skipForkedRepos defaults to false + }); + // Call the original method to maintain test behavior + return originalListProjects.call((provider as any).gitLabClient, options); + }); + (provider as any).gitLabClient.listProjects = mockListProjects; + await provider.connect(entityProviderConnection); const taskDef = schedule.getTasks()[0]; @@ -559,3 +574,115 @@ describe('GitlabDiscoveryEntityProvider - events', () => { }); // EventSupportChange: stop add tests >>> }); + +describe('GitlabDiscoveryEntityProvider - simple parameter', () => { + it('should pass simple=true when skipForkedRepos is false', async () => { + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'example.com', + apiBaseUrl: 'https://example.com/api/v4', + token: 'test-token', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'example.com', + group: 'test-group', + skipForkedRepos: false, + }, + }, + }, + }, + }); + + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + + const provider = GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + // Mock the GitLabClient listProjects method to verify parameters + const mockListProjects = jest.fn().mockResolvedValue({ + items: [], + nextPage: undefined, + }); + + (provider as any).gitLabClient.listProjects = mockListProjects; + + await provider.connect(entityProviderConnection); + await provider.refresh(logger); + + expect(mockListProjects).toHaveBeenCalledWith({ + group: 'test-group', + page: undefined, + per_page: 50, + archived: false, + simple: true, // Should be set when skipForkedRepos is false + }); + }); + + it('should not pass simple when skipForkedRepos is true', async () => { + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'example.com', + apiBaseUrl: 'https://example.com/api/v4', + token: 'test-token', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'example.com', + group: 'test-group', + skipForkedRepos: true, + }, + }, + }, + }, + }); + + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + + const provider = GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + // Mock the GitLabClient listProjects method to verify parameters + const mockListProjects = jest.fn().mockResolvedValue({ + items: [], + nextPage: undefined, + }); + + (provider as any).gitLabClient.listProjects = mockListProjects; + + await provider.connect(entityProviderConnection); + await provider.refresh(logger); + + expect(mockListProjects).toHaveBeenCalledWith({ + group: 'test-group', + page: undefined, + per_page: 50, + archived: false, + // simple should not be present when skipForkedRepos is true + }); + }); +}); diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts index 8bf749c275..243ffd5287 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts @@ -216,6 +216,11 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { ...(!this.config.includeArchivedRepos && { archived: false }), ...(this.config.membership && { membership: true }), ...(this.config.topics && { topics: this.config.topics }), + // Only use simple=true when we don't need to skip forked repos. + // The simple=true parameter reduces response size by returning fewer fields, + // but it excludes the 'forked_from_project' field which is required for fork detection. + // Therefore, we can only optimize with simple=true when skipForkedRepos is false. + ...(!this.config.skipForkedRepos && { simple: true }), }, ); From f8db72be543bf81956822b2d0f19101aae5f647b Mon Sep 17 00:00:00 2001 From: Patrick McDonnell Date: Fri, 1 Aug 2025 13:46:50 +0200 Subject: [PATCH 09/71] fix linter error Signed-off-by: Patrick McDonnell --- .../src/GitLabDiscoveryProcessor.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts index 21cbd24232..774da46553 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts @@ -481,7 +481,7 @@ describe('GitlabDiscoveryProcessor', () => { }); setupFakeServer( PROJECTS_URL, - request => { + _ => { return { data: [ { @@ -514,7 +514,7 @@ describe('GitlabDiscoveryProcessor', () => { }); setupFakeServer( PROJECTS_URL, - request => { + _ => { return { data: [ { @@ -551,7 +551,7 @@ describe('GitlabDiscoveryProcessor', () => { const processor = getProcessor(); // Uses defaults: skipForkedRepos=false, includeArchivedRepos=false setupFakeServer( PROJECTS_URL, - request => { + _ => { return { data: [ { From ddf1de03f85b98be124cf7ed95e50fc5fbcfba8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 25 Aug 2025 11:10:14 +0200 Subject: [PATCH 10/71] Update .changeset/stupid-pans-flash.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/stupid-pans-flash.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/stupid-pans-flash.md b/.changeset/stupid-pans-flash.md index bc857a243a..3e8c2333dd 100644 --- a/.changeset/stupid-pans-flash.md +++ b/.changeset/stupid-pans-flash.md @@ -2,7 +2,7 @@ '@backstage/plugin-scaffolder-backend-module-github': minor --- -Adding a new scaffolder action `github:issues:create` following the reference of `github:issues:label` with dryRun testing possibility +Adding a new scaffolder action `github:issues:create` following the reference of `github:issues:label` with `dryRun` testing possibility It can be used like this From a0b604cb6a50765450c3675405994f94137e38fd Mon Sep 17 00:00:00 2001 From: Owen Shartle Date: Mon, 25 Aug 2025 15:39:29 -0400 Subject: [PATCH 11/71] Adding handling which checks if the current entity (the catalog entity being loaded) has an annotation for an external entity's TechDocs. If it does then we will redirect there rather than allowing a 404 (mic drop). This helps keep older URLs routing to the updated locations. Adding changesets. Adding test coverage for external TechDocs entitiy redirect. Signed-off-by: Owen Shartle --- .changeset/eighty-numbers-act.md | 5 + .changeset/hungry-carrots-grow.md | 5 + .changeset/lovely-actors-love.md | 5 + .../well-known-annotations.md | 2 +- docs/features/techdocs/FAQ.md | 4 + .../documented-component/catalog-info.yaml | 14 ++ .../docs/inner-component-docs/index.md | 5 + .../examples/documented-component/mkdocs.yml | 2 + plugins/techdocs-react/src/helpers.ts | 4 +- .../TechDocsReaderPage.test.tsx | 131 +++++++++++++++++- .../TechDocsReaderPage/TechDocsReaderPage.tsx | 98 ++++++++++--- 11 files changed, 255 insertions(+), 20 deletions(-) create mode 100644 .changeset/eighty-numbers-act.md create mode 100644 .changeset/hungry-carrots-grow.md create mode 100644 .changeset/lovely-actors-love.md create mode 100644 plugins/techdocs-backend/examples/documented-component/docs/inner-component-docs/index.md diff --git a/.changeset/eighty-numbers-act.md b/.changeset/eighty-numbers-act.md new file mode 100644 index 0000000000..183d857d92 --- /dev/null +++ b/.changeset/eighty-numbers-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': patch +--- + +Update to documentation regarding TechDocs redirects. diff --git a/.changeset/hungry-carrots-grow.md b/.changeset/hungry-carrots-grow.md new file mode 100644 index 0000000000..748ace8d94 --- /dev/null +++ b/.changeset/hungry-carrots-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +Adding redirect handling for TechDocs URLs that reference entities that now reference an external entity for TechDocs. Including tests and documentation. diff --git a/.changeset/lovely-actors-love.md b/.changeset/lovely-actors-love.md new file mode 100644 index 0000000000..f645cc5b3f --- /dev/null +++ b/.changeset/lovely-actors-love.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-backend': minor +--- + +Adding new entity that specifies an external entity in the techdocs-entity annotation. diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index cdd93b50ac..d8a2e1d232 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -126,7 +126,7 @@ metadata: The value of this annotation informs of the path to this component's TechDocs within an external entity that owns the TechDocs. In conjunction with [backstage.io/techdocs-entity](#backstageiotechdocs-entity) this allows for deep linking into the TechDocs of -another entity, not just linking to the root of another entities TechDocs. +another entity, not just linking to the root of another entity's TechDocs. ### backstage.io/view-url, backstage.io/edit-url diff --git a/docs/features/techdocs/FAQ.md b/docs/features/techdocs/FAQ.md index 117471f749..323489ddab 100644 --- a/docs/features/techdocs/FAQ.md +++ b/docs/features/techdocs/FAQ.md @@ -57,3 +57,7 @@ your `mkdocs.yml` files per If the host name of your source code hosting URL does not include `github` or `gitlab`, an `integrations` entry in your `app-config.yaml` pointed at your source code provider is also needed (only the `host` key is necessary). + +#### What happens when you navigate to a TechDocs URL for an entity uses the `backstage.io/techdocs-entity` annotation? + +If you navigate to a TechDocs URL in the format `docs/{namespace}/{kind}/{name}` for an entity that has the `backstage.io/techdocs-entity` annotation (instead of the `backstage.io/techdocs-ref` annotation), then Backstage will redirect to the TechDocs page of the entity referenced in the value of that annotation. diff --git a/plugins/techdocs-backend/examples/documented-component/catalog-info.yaml b/plugins/techdocs-backend/examples/documented-component/catalog-info.yaml index 6b30213a60..4d3b71eb2a 100644 --- a/plugins/techdocs-backend/examples/documented-component/catalog-info.yaml +++ b/plugins/techdocs-backend/examples/documented-component/catalog-info.yaml @@ -10,3 +10,17 @@ spec: type: service lifecycle: experimental owner: user:guest +--- +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: techdocs-entity-documented-component + title: Example Entity Documented By TechDocs Entity Annotation + description: A Service with TechDocs documentation via the `backstage.io/techdocs-entity` annotation. + annotations: + backstage.io/techdocs-entity: component:default/documented-component + backstage.io/techdocs-entity-path: /inner-component-docs +spec: + type: service + lifecycle: experimental + owner: user:guest \ No newline at end of file diff --git a/plugins/techdocs-backend/examples/documented-component/docs/inner-component-docs/index.md b/plugins/techdocs-backend/examples/documented-component/docs/inner-component-docs/index.md new file mode 100644 index 0000000000..8cb6498c55 --- /dev/null +++ b/plugins/techdocs-backend/examples/documented-component/docs/inner-component-docs/index.md @@ -0,0 +1,5 @@ +# Inner Component Docs + +This is a basic example of documentation within a larger suite of TechDocs that can be referenced by whatever entities necessary. It is intended as a showcase of the `backstage.io/techdocs-entity-path` annotation for linking to a "subpage" in TechDocs that are declared by another entity. + +Please review the [How-To Guides - Deep Linking Into TechDocs](../../../../../../docs/features/techdocs/how-to-guides.md#deep-linking-into-techdocs) section for more information and you can view the example usage on the "Example Entity Documented By TechDocs Entity Annotation" component in this [catalog-info.yaml](../../catalog-info.yaml) file. diff --git a/plugins/techdocs-backend/examples/documented-component/mkdocs.yml b/plugins/techdocs-backend/examples/documented-component/mkdocs.yml index 1a159a4d12..ab97326913 100644 --- a/plugins/techdocs-backend/examples/documented-component/mkdocs.yml +++ b/plugins/techdocs-backend/examples/documented-component/mkdocs.yml @@ -7,6 +7,8 @@ nav: - Subpage: sub-page.md - 'Code Sample': code/code-sample.md - Extensions: extensions.md + - 'Inner Component Docs': inner-component-docs/index.md plugins: - techdocs-core + \ No newline at end of file diff --git a/plugins/techdocs-react/src/helpers.ts b/plugins/techdocs-react/src/helpers.ts index e6daf99b26..aebb0b3e52 100644 --- a/plugins/techdocs-react/src/helpers.ts +++ b/plugins/techdocs-react/src/helpers.ts @@ -64,7 +64,7 @@ export function getEntityRootTechDocsPath(entity: Entity): string { /** * Build the TechDocs URL for the given entity. This helper should be used anywhere there - * is a link to an entities TechDocs. + * is a link to an entity's TechDocs. * * @public */ @@ -104,7 +104,7 @@ export const buildTechDocsURL = ( }); // Add on the external entity path to the url if one exists. This allows deep linking into another - // entities TechDocs. + // entity's TechDocs. const path = getEntityRootTechDocsPath(entity); return `${url}${path}`; diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx index 2af4915ef5..93f24e9f97 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx @@ -18,6 +18,7 @@ import { ReactNode } from 'react'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { + catalogApiRef, entityPresentationApiRef, entityRouteRef, } from '@backstage/plugin-catalog-react'; @@ -30,9 +31,10 @@ import { import { techdocsApiRef, techdocsStorageApiRef } from '../../../api'; import { rootRouteRef, rootDocsRouteRef } from '../../../routes'; +import { TECHDOCS_EXTERNAL_ANNOTATION } from '@backstage/plugin-techdocs-common'; import { TechDocsReaderPage } from './TechDocsReaderPage'; -import { Route, useParams } from 'react-router-dom'; +import { Route, useNavigate, useParams } from 'react-router-dom'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; import { FlatRoutes } from '@backstage/core-app-api'; @@ -94,6 +96,10 @@ const entityPresentationApiMock: jest.Mocked< }), }; +const catalogApiMock = { + getEntityByRef: jest.fn().mockResolvedValue(mockEntityMetadata), +}; + const fetchApiMock = { fetch: jest.fn().mockResolvedValue({ ok: true, @@ -114,6 +120,11 @@ jest.mock('@backstage/core-components', () => ({ Page: jest.fn(), })); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: jest.fn(), +})); + const configApi = mockApis.config({ data: { app: { baseUrl: 'http://localhost:3000' } }, }); @@ -129,6 +140,7 @@ const Wrapper = ({ children }: { children: ReactNode }) => { [techdocsApiRef, techdocsApiMock], [techdocsStorageApiRef, techdocsStorageApiMock], [entityPresentationApiRef, entityPresentationApiMock], + [catalogApiRef, catalogApiMock], ]} > {children} @@ -143,6 +155,8 @@ const mountedRoutes = { }; describe('', () => { + const mockNavigate = jest.fn(); + beforeEach(() => { getEntityMetadata.mockResolvedValue(mockEntityMetadata); getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); @@ -150,6 +164,8 @@ describe('', () => { // Expires in 10 minutes expiresAt: new Date(Date.now() + 10 * 60 * 1000).toISOString(), }); + + (useNavigate as jest.Mock).mockReturnValue(mockNavigate); }); afterEach(() => { @@ -285,4 +301,117 @@ describe('', () => { expect(text).toHaveStyle('fontFamily: Comic Sans MS'); }); + + describe('external TechDocs redirect', () => { + beforeEach(() => { + mockNavigate.mockClear(); + catalogApiMock.getEntityByRef.mockReset(); + catalogApiMock.getEntityByRef.mockResolvedValue(mockEntityMetadata); + }); + + it('should navigate to external URL when entity has external techdocs annotation', async () => { + const mockEntityWithExternalAnnotation = { + ...mockEntityMetadata, + metadata: { + ...mockEntityMetadata.metadata, + annotations: { + [TECHDOCS_EXTERNAL_ANNOTATION]: + 'component:external-namespace/external-docs', + }, + }, + }; + + catalogApiMock.getEntityByRef.mockResolvedValue( + mockEntityWithExternalAnnotation, + ); + + await renderInTestApp( + + + , + { + mountedRoutes, + }, + ); + + expect(mockNavigate).toHaveBeenCalledWith( + '/docs/external-namespace/component/external-docs', + { replace: true }, + ); + }); + + it('should render normally when entity has no external techdocs annotation', async () => { + const mockEntityWithoutExternalAnnotation = { + ...mockEntityMetadata, + metadata: { + ...mockEntityMetadata.metadata, + annotations: undefined, + }, + }; + + catalogApiMock.getEntityByRef.mockResolvedValue( + mockEntityWithoutExternalAnnotation, + ); + + const rendered = await renderInTestApp( + + + , + { + mountedRoutes, + }, + ); + + expect(rendered.container.querySelector('header')).toBeInTheDocument(); + expect(rendered.container.querySelector('article')).toBeInTheDocument(); + expect(mockNavigate).not.toHaveBeenCalled(); + }); + + it('should render normally when entity has external annotation but no value', async () => { + const mockEntityWithEmptyExternalAnnotation = { + ...mockEntityMetadata, + metadata: { + ...mockEntityMetadata.metadata, + annotations: { + [TECHDOCS_EXTERNAL_ANNOTATION]: '', + }, + }, + }; + + catalogApiMock.getEntityByRef.mockResolvedValue( + mockEntityWithEmptyExternalAnnotation, + ); + + const rendered = await renderInTestApp( + + + , + { + mountedRoutes, + }, + ); + + expect(rendered.container.querySelector('header')).toBeInTheDocument(); + expect(rendered.container.querySelector('article')).toBeInTheDocument(); + expect(mockNavigate).not.toHaveBeenCalled(); + }); + }); }); diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx index 80545151d9..9b4999899d 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx @@ -14,19 +14,26 @@ * limitations under the License. */ -import { Children, ReactElement, ReactNode } from 'react'; -import { useOutlet } from 'react-router-dom'; - +import { + Children, + ReactElement, + ReactNode, + useEffect, + useMemo, + useCallback, +} from 'react'; +import { useOutlet, useNavigate } from 'react-router-dom'; import { Page } from '@backstage/core-components'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { TECHDOCS_ADDONS_KEY, TECHDOCS_ADDONS_WRAPPER_KEY, TechDocsReaderPageProvider, + buildTechDocsURL, } from '@backstage/plugin-techdocs-react'; - +import { TECHDOCS_EXTERNAL_ANNOTATION } from '@backstage/plugin-techdocs-common'; +import useAsync from 'react-use/esm/useAsync'; import { TechDocsReaderPageRenderFunction } from '../../../types'; - import { TechDocsReaderPageContent } from '../TechDocsReaderPageContent'; import { TechDocsReaderPageHeader } from '../TechDocsReaderPageHeader'; import { TechDocsReaderPageSubheader } from '../TechDocsReaderPageSubheader'; @@ -34,9 +41,11 @@ import { rootDocsRouteRef } from '../../../routes'; import { getComponentData, useRouteRefParams, + useApi, + useRouteRef, } from '@backstage/core-plugin-api'; - import { CookieAuthRefreshProvider } from '@backstage/plugin-auth-react'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { createTheme, styled, @@ -44,6 +53,7 @@ import { ThemeProvider, useTheme, } from '@material-ui/core/styles'; +import { Progress } from '@backstage/core-components'; /* An explanation for the multiple ways of customizing the TechDocs reader page @@ -177,44 +187,100 @@ const StyledPage = styled(Page)({ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { const currentTheme = useTheme(); - const readerPageTheme = createTheme({ - ...currentTheme, - ...(props.overrideThemeOptions || {}), - }); + const readerPageTheme = useMemo( + () => + createTheme({ + ...currentTheme, + ...(props.overrideThemeOptions || {}), + }), + [currentTheme, props.overrideThemeOptions], + ); + const { kind, name, namespace } = useRouteRefParams(rootDocsRouteRef); const { children, entityRef = { kind, name, namespace } } = props; const outlet = useOutlet(); - if (!children) { + const catalogApi = useApi(catalogApiRef); + const navigate = useNavigate(); + const viewTechdocLink = useRouteRef(rootDocsRouteRef); + + const memoizedEntityRef = useMemo( + () => ({ + kind: entityRef.kind, + name: entityRef.name, + namespace: entityRef.namespace, + }), + [entityRef.kind, entityRef.name, entityRef.namespace], + ); + + const externalEntityTechDocsUrl = useAsync(async () => { + const catalogEntity = await catalogApi.getEntityByRef(memoizedEntityRef); + + if (catalogEntity?.metadata?.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]) { + return buildTechDocsURL(catalogEntity, viewTechdocLink); + } + + return undefined; + }, [memoizedEntityRef, catalogApi, viewTechdocLink]); + + const handleNavigation = useCallback( + (url: string) => { + navigate(url, { replace: true }); + }, + [navigate], + ); + + useEffect(() => { + if (!externalEntityTechDocsUrl.loading && externalEntityTechDocsUrl.value) { + handleNavigation(externalEntityTechDocsUrl.value); + } + }, [ + externalEntityTechDocsUrl.loading, + externalEntityTechDocsUrl.value, + handleNavigation, + ]); + + const page: ReactNode = useMemo(() => { + if (children) { + return null; + } + const childrenList = outlet ? Children.toArray(outlet.props.children) : []; const grandChildren = childrenList.flatMap( child => (child as ReactElement)?.props?.children ?? [], ); - const page: ReactNode = grandChildren.find( + return grandChildren.find( grandChild => !getComponentData(grandChild, TECHDOCS_ADDONS_WRAPPER_KEY) && !getComponentData(grandChild, TECHDOCS_ADDONS_KEY), ); + }, [children, outlet]); - // As explained above, "page" is configuration 4 and is 1 + if (externalEntityTechDocsUrl.loading || externalEntityTechDocsUrl.value) { + return ; + } + + // As explained above, "page" is configuration 4 and is 1 + if (!children) { return ( - + {(page as JSX.Element) || } ); } + // As explained above, a render function is configuration 3 and React element is 2 return ( - + {({ metadata, entityMetadata, onReady }) => ( { > {children instanceof Function ? children({ - entityRef, + entityRef: memoizedEntityRef, techdocsMetadataValue: metadata.value, entityMetadataValue: entityMetadata.value, onReady, From dcdb03c7f491179188ab53c8f26958051dba92ca Mon Sep 17 00:00:00 2001 From: Owen Shartle Date: Mon, 25 Aug 2025 15:49:41 -0400 Subject: [PATCH 12/71] Correcting chagesets -- had duplicate changeset. Signed-off-by: Owen Shartle --- .changeset/eighty-numbers-act.md | 2 +- .changeset/lovely-actors-love.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.changeset/eighty-numbers-act.md b/.changeset/eighty-numbers-act.md index 183d857d92..dc7d504adc 100644 --- a/.changeset/eighty-numbers-act.md +++ b/.changeset/eighty-numbers-act.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-techdocs-backend': patch +'@backstage/plugin-techdocs-react': patch --- Update to documentation regarding TechDocs redirects. diff --git a/.changeset/lovely-actors-love.md b/.changeset/lovely-actors-love.md index f645cc5b3f..b428a11619 100644 --- a/.changeset/lovely-actors-love.md +++ b/.changeset/lovely-actors-love.md @@ -2,4 +2,4 @@ '@backstage/plugin-techdocs-backend': minor --- -Adding new entity that specifies an external entity in the techdocs-entity annotation. +Adding new entity that specifies an external entity in the techdocs-entity annotation and updates to documentation regarding TechDocs redirects. From 40fe543447f190cdaa6465abd4916cefed3117ff Mon Sep 17 00:00:00 2001 From: Owen Shartle Date: Mon, 25 Aug 2025 16:43:58 -0400 Subject: [PATCH 13/71] Running prettier on files. Signed-off-by: Owen Shartle --- .../examples/documented-component/catalog-info.yaml | 2 +- .../techdocs-backend/examples/documented-component/mkdocs.yml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/techdocs-backend/examples/documented-component/catalog-info.yaml b/plugins/techdocs-backend/examples/documented-component/catalog-info.yaml index 4d3b71eb2a..5b90693e23 100644 --- a/plugins/techdocs-backend/examples/documented-component/catalog-info.yaml +++ b/plugins/techdocs-backend/examples/documented-component/catalog-info.yaml @@ -23,4 +23,4 @@ metadata: spec: type: service lifecycle: experimental - owner: user:guest \ No newline at end of file + owner: user:guest diff --git a/plugins/techdocs-backend/examples/documented-component/mkdocs.yml b/plugins/techdocs-backend/examples/documented-component/mkdocs.yml index ab97326913..fd954225a3 100644 --- a/plugins/techdocs-backend/examples/documented-component/mkdocs.yml +++ b/plugins/techdocs-backend/examples/documented-component/mkdocs.yml @@ -11,4 +11,3 @@ nav: plugins: - techdocs-core - \ No newline at end of file From 8eb950ff649b18430d29364ff0a11a5593e1515d Mon Sep 17 00:00:00 2001 From: Owen Shartle Date: Mon, 25 Aug 2025 21:36:57 -0400 Subject: [PATCH 14/71] Adding a try-catch around the usage of the catalog API in the TechDocsReaderPage as it could still attempt to load a standard TechDocs page. Signed-off-by: Owen Shartle --- .../TechDocsReaderPage.test.tsx | 25 +++++++++++++++++++ .../TechDocsReaderPage/TechDocsReaderPage.tsx | 12 ++++++--- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx index 93f24e9f97..c645d1db83 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx @@ -413,5 +413,30 @@ describe('', () => { expect(rendered.container.querySelector('article')).toBeInTheDocument(); expect(mockNavigate).not.toHaveBeenCalled(); }); + + it('should render normally when catalog API throws an error', async () => { + catalogApiMock.getEntityByRef.mockRejectedValue( + new Error('Catalog API error'), + ); + + const rendered = await renderInTestApp( + + + , + { + mountedRoutes, + }, + ); + + expect(rendered.container.querySelector('header')).toBeInTheDocument(); + expect(rendered.container.querySelector('article')).toBeInTheDocument(); + expect(mockNavigate).not.toHaveBeenCalled(); + }); }); }); diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx index 9b4999899d..c67660fd79 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx @@ -215,10 +215,16 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { ); const externalEntityTechDocsUrl = useAsync(async () => { - const catalogEntity = await catalogApi.getEntityByRef(memoizedEntityRef); + try { + const catalogEntity = await catalogApi.getEntityByRef(memoizedEntityRef); - if (catalogEntity?.metadata?.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION]) { - return buildTechDocsURL(catalogEntity, viewTechdocLink); + if ( + catalogEntity?.metadata?.annotations?.[TECHDOCS_EXTERNAL_ANNOTATION] + ) { + return buildTechDocsURL(catalogEntity, viewTechdocLink); + } + } catch (error) { + // Ignore error and allow an attempt at loading the current entity's TechDocs when unable to fetch an external entity from the catalog. } return undefined; From def6247bb6cc42437a77c5c9bbb3b303fa195d02 Mon Sep 17 00:00:00 2001 From: Owen Shartle Date: Mon, 25 Aug 2025 22:00:22 -0400 Subject: [PATCH 15/71] Adding catalogApiRef to test-utils.tsx. Signed-off-by: Owen Shartle --- plugins/techdocs-addons-test-utils/src/test-utils.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugins/techdocs-addons-test-utils/src/test-utils.tsx b/plugins/techdocs-addons-test-utils/src/test-utils.tsx index 91b85e7885..2d7420ec62 100644 --- a/plugins/techdocs-addons-test-utils/src/test-utils.tsx +++ b/plugins/techdocs-addons-test-utils/src/test-utils.tsx @@ -39,6 +39,7 @@ import { } from '@backstage/plugin-techdocs-react'; import { TechDocsReaderPage, techdocsPlugin } from '@backstage/plugin-techdocs'; import { + catalogApiRef, EntityPresentationApi, entityPresentationApiRef, entityRouteRef, @@ -235,8 +236,16 @@ export class TechDocsAddonTester { }), }; + const catalogApi = { + getEntityByRef: jest.fn().mockResolvedValue({ + kind: 'Component', + metadata: { namespace: 'default', name: 'docs' }, + }), + }; + const apis: TechdocsAddonTesterApis = [ [fetchApiRef, fetchApi], + [catalogApiRef, catalogApi], [entityPresentationApiRef, entityPresentationApi], [discoveryApiRef, discoveryApi], [techdocsApiRef, techdocsApi], From 72543e92b8bae988f7ebfcf6fa08930ca5e27c8e Mon Sep 17 00:00:00 2001 From: Owen Shartle Date: Mon, 25 Aug 2025 22:05:59 -0400 Subject: [PATCH 16/71] Adding catalogApiRef to test-utils to support catalog API usage by TechDocs reader page. Signed-off-by: Owen Shartle --- .changeset/tiny-spoons-mix.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tiny-spoons-mix.md diff --git a/.changeset/tiny-spoons-mix.md b/.changeset/tiny-spoons-mix.md new file mode 100644 index 0000000000..bb607aca3f --- /dev/null +++ b/.changeset/tiny-spoons-mix.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-addons-test-utils': minor +--- + +Adding catalogApiRef to test-utils to support catalog API usage by TechDocs reader page. From 6dbf53531f3dd3ad39d6f443b57c1c1f2fac5925 Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Wed, 27 Aug 2025 13:17:25 -0400 Subject: [PATCH 17/71] docs: Update docker.md - node:20 --> node:22 for base images Signed-off-by: Mathieu Benoit --- docs/deployment/docker.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 83f8cff6ea..04b72da445 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -53,7 +53,7 @@ Once the host build is complete, we are ready to build our image. The following `Dockerfile` is included when creating a new app with `@backstage/create-app`: ```dockerfile -FROM node:20-bookworm-slim +FROM node:22-bookworm-slim # Set Python interpreter for `node-gyp` to use ENV PYTHON=/usr/bin/python3 @@ -178,7 +178,7 @@ the repo root: ```dockerfile # Stage 1 - Create yarn install skeleton layer -FROM node:20-bookworm-slim AS packages +FROM node:22-bookworm-slim AS packages WORKDIR /app COPY backstage.json package.json yarn.lock ./ @@ -193,7 +193,7 @@ COPY plugins plugins RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf {} \+ # Stage 2 - Install dependencies and build packages -FROM node:20-bookworm-slim AS build +FROM node:22-bookworm-slim AS build # Set Python interpreter for `node-gyp` to use ENV PYTHON=/usr/bin/python3 @@ -231,7 +231,7 @@ RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \ && tar xzf packages/backend/dist/bundle.tar.gz -C packages/backend/dist/bundle # Stage 3 - Build the actual backend image and install production dependencies -FROM node:20-bookworm-slim +FROM node:22-bookworm-slim # Set Python interpreter for `node-gyp` to use ENV PYTHON=/usr/bin/python3 From bf02e9649e19674ed8d66abc381ca73b65a4c8ce Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Wed, 27 Aug 2025 16:14:49 -0400 Subject: [PATCH 18/71] feat: Update backend/Dockerfile - node:20 --> node:22 Signed-off-by: Mathieu Benoit --- .../create-app/templates/next-app/packages/backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-app/templates/next-app/packages/backend/Dockerfile b/packages/create-app/templates/next-app/packages/backend/Dockerfile index 68d6c2fd58..ee80245d8b 100644 --- a/packages/create-app/templates/next-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/next-app/packages/backend/Dockerfile @@ -12,7 +12,7 @@ # Alternatively, there is also a multi-stage Dockerfile documented here: # https://backstage.io/docs/deployment/docker#multi-stage-build -FROM node:20-bookworm-slim +FROM node:22-bookworm-slim # Set Python interpreter for `node-gyp` to use ENV PYTHON=/usr/bin/python3 From 7cfea1aac81c96e4ee21b0f3257308e1e1ec904a Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Wed, 27 Aug 2025 16:19:32 -0400 Subject: [PATCH 19/71] feat: Update backend/Dockerfile - node:20 --> node:22 Signed-off-by: Mathieu Benoit --- .../templates/default-app/packages/backend/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index 68d6c2fd58..ee80245d8b 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -12,7 +12,7 @@ # Alternatively, there is also a multi-stage Dockerfile documented here: # https://backstage.io/docs/deployment/docker#multi-stage-build -FROM node:20-bookworm-slim +FROM node:22-bookworm-slim # Set Python interpreter for `node-gyp` to use ENV PYTHON=/usr/bin/python3 From 75977819f642e46df29f57ac8be7830b9218eb73 Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Thu, 28 Aug 2025 09:11:43 +0300 Subject: [PATCH 20/71] fix(catalog): msgraph to use only first parent for groups the order of the groups from msgraph is random, thus the parent group for groups having multiple parents can be random. this leads to situation where the grpup parent is changing over the msgraph provider runs leading to unnecessary stitching. other possibility for the fix would be to change the model to allow multiple parents for groups (like mentioned in the TODO) but that could have some weird consequences in apps. Signed-off-by: Hellgren Heikki --- .changeset/loose-memes-bathe.md | 5 +++++ .../src/microsoftGraph/read.test.ts | 18 ++++++++++++++++-- .../src/microsoftGraph/read.ts | 9 ++++++++- 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 .changeset/loose-memes-bathe.md diff --git a/.changeset/loose-memes-bathe.md b/.changeset/loose-memes-bathe.md new file mode 100644 index 0000000000..85eff94399 --- /dev/null +++ b/.changeset/loose-memes-bathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Ensure that msgraph parent group stays same in case the group has multiple parents diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts index 33ae04bd76..bd58881728 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.test.ts @@ -1102,6 +1102,14 @@ describe('read microsoft graph', () => { name: 'c', }, }); + const groupD = group({ + metadata: { + annotations: { + 'graph.microsoft.com/group-id': 'group-id-d', + }, + name: 'd', + }, + }); const user1 = user({ metadata: { annotations: { @@ -1118,16 +1126,17 @@ describe('read microsoft graph', () => { name: 'user2', }, }); - const groups = [rootGroup, groupA, groupB, groupC]; + const groups = [rootGroup, groupA, groupB, groupC, groupD]; const users = [user1, user2]; const groupMember = new Map>(); groupMember.set('group-id-b', new Set(['group-id-c'])); + groupMember.set('group-id-d', new Set(['group-id-c'])); const groupMemberOf = new Map>(); groupMemberOf.set('user-id-1', new Set(['group-id-a'])); groupMemberOf.set('user-id-2', new Set(['group-id-c'])); // We have a root groups - // We have three groups: a, b, c. c is child of b + // We have three groups: a, b, c. c is child of b and d, b should be picked as it's first // we have two users: u1, u2. u1 is member of a, u2 is member of c resolveRelations(rootGroup, groups, users, groupMember, groupMemberOf); @@ -1147,6 +1156,11 @@ describe('read microsoft graph', () => { expect(groupC.spec.parent).toEqual('group:default/b'); expect(groupC.spec.children).toEqual(expect.arrayContaining([])); + expect(groupD.spec.parent).toEqual('group:default/root'); + expect(groupD.spec.children).toEqual( + expect.arrayContaining(['group:default/c']), + ); + expect(user1.spec.memberOf).toEqual( expect.arrayContaining(['group:default/a']), ); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 4761c45ac7..8bc7e6899b 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -359,7 +359,14 @@ export function resolveRelations( } }); + // TODO: Until we have better support for multiple parents in the model, + // the order of the parents is important as changing it causes + // unnecessary entity stitching randomly. retrieveItems(parentGroups, id).forEach(p => { + // Only set the parent if it doesn't exist yet + if (group.spec.parent) { + return; + } const parentGroup = groupMap.get(p); if (parentGroup) { // TODO: Only having a single parent group might not match every companies model, but fine for now. @@ -546,5 +553,5 @@ function retrieveItems( target: Map>, key: string, ): Set { - return target.get(key) ?? new Set(); + return new Set([...(target.get(key) ?? [])].sort()); } From 28ea47efe43f5b9176921334aedf25ca836c975b Mon Sep 17 00:00:00 2001 From: Owen Shartle Date: Mon, 1 Sep 2025 20:42:44 -0400 Subject: [PATCH 21/71] Adding 'subpage' as an accepted word for the spell checker. Signed-off-by: Owen Shartle --- .github/vale/config/vocabularies/Backstage/accept.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index ff6ea3aacb..495325a90e 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -459,6 +459,7 @@ subfolders subheader subheaders subkey +subpage subpath subroutes substring From ff40e2297ca4dde38b1a82a96428a1308fb62531 Mon Sep 17 00:00:00 2001 From: Owen Shartle Date: Mon, 1 Sep 2025 21:09:50 -0400 Subject: [PATCH 22/71] Using catalogApiMock from @backstage/plugin-catalog-react/testUtils. Signed-off-by: Owen Shartle --- .../src/test-utils.tsx | 16 ++++++++++------ .../TechDocsReaderPage.test.tsx | 5 ++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/plugins/techdocs-addons-test-utils/src/test-utils.tsx b/plugins/techdocs-addons-test-utils/src/test-utils.tsx index 2d7420ec62..a7cb74cb93 100644 --- a/plugins/techdocs-addons-test-utils/src/test-utils.tsx +++ b/plugins/techdocs-addons-test-utils/src/test-utils.tsx @@ -44,6 +44,7 @@ import { entityPresentationApiRef, entityRouteRef, } from '@backstage/plugin-catalog-react'; +import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; import { searchApiRef } from '@backstage/plugin-search-react'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; @@ -236,12 +237,15 @@ export class TechDocsAddonTester { }), }; - const catalogApi = { - getEntityByRef: jest.fn().mockResolvedValue({ - kind: 'Component', - metadata: { namespace: 'default', name: 'docs' }, - }), - }; + const catalogApi = catalogApiMock({ + entities: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { namespace: 'default', name: 'docs' }, + }, + ], + }); const apis: TechdocsAddonTesterApis = [ [fetchApiRef, fetchApi], diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx index c645d1db83..aea45a60fa 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.test.tsx @@ -27,6 +27,7 @@ import { renderInTestApp, TestApiProvider, } from '@backstage/test-utils'; +import { catalogApiMock as catalogApiMockFactory } from '@backstage/plugin-catalog-react/testUtils'; import { techdocsApiRef, techdocsStorageApiRef } from '../../../api'; @@ -96,9 +97,7 @@ const entityPresentationApiMock: jest.Mocked< }), }; -const catalogApiMock = { - getEntityByRef: jest.fn().mockResolvedValue(mockEntityMetadata), -}; +const catalogApiMock = catalogApiMockFactory.mock(); const fetchApiMock = { fetch: jest.fn().mockResolvedValue({ From 02f3d811604877ab6c5deaf704e9054746020d8e Mon Sep 17 00:00:00 2001 From: Praphull <1288395+praphull-purohit@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:28:41 +0200 Subject: [PATCH 23/71] Add block creations field to github branch protection scaffolder action Signed-off-by: Praphull <1288395+praphull-purohit@users.noreply.github.com> --- .../src/actions/gitHelpers.ts | 3 +++ .../src/actions/githubBranchProtection.ts | 3 +++ .../src/actions/inputProperties.ts | 9 +++++++++ 3 files changed, 15 insertions(+) diff --git a/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts b/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts index c720690ee6..d7ef3496dd 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/gitHelpers.ts @@ -44,6 +44,7 @@ type BranchProtectionOptions = { dismissStaleReviews?: boolean; requiredCommitSigning?: boolean; requiredLinearHistory?: boolean; + blockCreations?: boolean; }; export const enableBranchProtectionOnDefaultRepoBranch = async ({ @@ -64,6 +65,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ dismissStaleReviews = false, requiredCommitSigning = false, requiredLinearHistory = false, + blockCreations = false, }: BranchProtectionOptions): Promise => { const tryOnce = async () => { try { @@ -96,6 +98,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ }, required_conversation_resolution: requiredConversationResolution, required_linear_history: requiredLinearHistory, + block_creations: blockCreations, }); if (requiredCommitSigning) { diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts index f82bfc6950..6ae0f67135 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts @@ -57,6 +57,7 @@ export function createGithubBranchProtectionAction(options: { requireLastPushApproval: inputProps.requireLastPushApproval, requiredCommitSigning: inputProps.requiredCommitSigning, requiredLinearHistory: inputProps.requiredLinearHistory, + blockCreations: inputProps.blockCreations, token: inputProps.token, }, }, @@ -76,6 +77,7 @@ export function createGithubBranchProtectionAction(options: { requireLastPushApproval = false, requiredCommitSigning = false, requiredLinearHistory = false, + blockCreations = false, token: providedToken, } = ctx.input; @@ -129,6 +131,7 @@ export function createGithubBranchProtectionAction(options: { dismissStaleReviews, requiredCommitSigning, requiredLinearHistory, + blockCreations, }); }, }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts index e1df40f85d..bfa232c45d 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/inputProperties.ts @@ -348,6 +348,14 @@ const requiredLinearHistory = (z: typeof zod) => }) .optional(); +const blockCreations = (z: typeof zod) => + z + .boolean({ + description: `Prevents creation of new branches during push, unless the push is initiated by a user, team, or app (defined in restrictions) which has the ability to push.`, + }) + .default(false) + .optional(); + const repoVariables = (z: typeof zod) => z .record(z.string(), { @@ -449,4 +457,5 @@ export { protectEnforceAdmins, bypassPullRequestAllowances, branch, + blockCreations, }; From 6393b78fe4115cda68b1f1ff5a6805acc4f26413 Mon Sep 17 00:00:00 2001 From: Praphull <1288395+praphull-purohit@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:38:25 +0200 Subject: [PATCH 24/71] Add changeset Signed-off-by: Praphull <1288395+praphull-purohit@users.noreply.github.com> --- .changeset/ripe-boxes-stand.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/ripe-boxes-stand.md diff --git a/.changeset/ripe-boxes-stand.md b/.changeset/ripe-boxes-stand.md new file mode 100644 index 0000000000..e40dedbe90 --- /dev/null +++ b/.changeset/ripe-boxes-stand.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': minor +--- + +Add field block_creations in github branch protection scaffolder actions From f2be6858aef081fc37bc11dec20d110da6d97af3 Mon Sep 17 00:00:00 2001 From: Praphull <1288395+praphull-purohit@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:40:51 +0200 Subject: [PATCH 25/71] Update tests Signed-off-by: Praphull <1288395+praphull-purohit@users.noreply.github.com> --- .../src/actions/githubBranchProtection.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts index d20a02d460..98d296e176 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.test.ts @@ -105,6 +105,7 @@ describe('github:branch-protection:create', () => { }, required_conversation_resolution: false, required_linear_history: false, + block_creations: false, }); expect( mockOctokit.rest.repos.createCommitSignatureProtection, @@ -142,6 +143,7 @@ describe('github:branch-protection:create', () => { }, required_conversation_resolution: false, required_linear_history: false, + block_creations: false, }); expect( mockOctokit.rest.repos.createCommitSignatureProtection, @@ -178,6 +180,7 @@ describe('github:branch-protection:create', () => { requireLastPushApproval: true, requiredCommitSigning: true, requiredLinearHistory: true, + blockCreations: true, }, }); @@ -211,6 +214,7 @@ describe('github:branch-protection:create', () => { }, required_conversation_resolution: true, required_linear_history: true, + block_creations: true, }); expect( mockOctokit.rest.repos.createCommitSignatureProtection, From 172d57c6b02afa8fec1c5c70c7fca0c734ba99bd Mon Sep 17 00:00:00 2001 From: Praphull <1288395+praphull-purohit@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:51:33 +0200 Subject: [PATCH 26/71] Add API Report Signed-off-by: Praphull <1288395+praphull-purohit@users.noreply.github.com> --- plugins/scaffolder-backend-module-github/report.api.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder-backend-module-github/report.api.md b/plugins/scaffolder-backend-module-github/report.api.md index 85ca128c34..35e4e71c3c 100644 --- a/plugins/scaffolder-backend-module-github/report.api.md +++ b/plugins/scaffolder-backend-module-github/report.api.md @@ -81,6 +81,7 @@ export function createGithubBranchProtectionAction(options: { requireLastPushApproval?: boolean | undefined; requiredCommitSigning?: boolean | undefined; requiredLinearHistory?: boolean | undefined; + blockCreations?: boolean | undefined; token?: string | undefined; }, { From 2f79f0bac84b762651a4aa2d410d564e79efa1ad Mon Sep 17 00:00:00 2001 From: Praphull <1288395+praphull-purohit@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:54:59 +0200 Subject: [PATCH 27/71] Fix tests in examples Signed-off-by: Praphull <1288395+praphull-purohit@users.noreply.github.com> --- .../src/actions/githubBranchProtection.examples.test.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts index 31a15f6019..b02d451992 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.examples.test.ts @@ -99,6 +99,7 @@ describe('github:branch-protection:create', () => { }, required_conversation_resolution: false, required_linear_history: false, + block_creations: false, }); expect( mockOctokit.rest.repos.createCommitSignatureProtection, @@ -132,6 +133,7 @@ describe('github:branch-protection:create', () => { }, required_conversation_resolution: false, required_linear_history: false, + block_creations: false, }); expect( mockOctokit.rest.repos.createCommitSignatureProtection, @@ -165,6 +167,7 @@ describe('github:branch-protection:create', () => { }, required_conversation_resolution: true, required_linear_history: false, + block_creations: false, }); expect( mockOctokit.rest.repos.createCommitSignatureProtection, @@ -202,6 +205,7 @@ describe('github:branch-protection:create', () => { }, required_conversation_resolution: true, required_linear_history: true, + block_creations: false, }); expect( mockOctokit.rest.repos.createCommitSignatureProtection, From a599afa296384a1e39cd7db866593265128a27ef Mon Sep 17 00:00:00 2001 From: Praphull <1288395+praphull-purohit@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:57:14 +0200 Subject: [PATCH 28/71] Minor correction in changeset Signed-off-by: Praphull <1288395+praphull-purohit@users.noreply.github.com> --- .changeset/ripe-boxes-stand.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/ripe-boxes-stand.md b/.changeset/ripe-boxes-stand.md index e40dedbe90..163f5ae545 100644 --- a/.changeset/ripe-boxes-stand.md +++ b/.changeset/ripe-boxes-stand.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend-module-github': minor --- -Add field block_creations in github branch protection scaffolder actions +Add field blockCreations in github branch protection scaffolder actions From 5a767f5c8abf8dbc5677e590d2385037ff6e72b4 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Thu, 4 Sep 2025 13:22:34 +0200 Subject: [PATCH 29/71] update docs for table component Signed-off-by: Emma Indal --- docs-ui/src/content/components/table.props.ts | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/docs-ui/src/content/components/table.props.ts b/docs-ui/src/content/components/table.props.ts index 87be472609..1b40055849 100644 --- a/docs-ui/src/content/components/table.props.ts +++ b/docs-ui/src/content/components/table.props.ts @@ -217,18 +217,18 @@ export const cellPropDefs: Record = { }; export const tablePaginationPropDefs: Record = { - pageIndex: { + offset: { type: 'number', - description: 'The current page index.', + description: 'The current offset (starting index) for pagination.', }, pageSize: { type: 'number', description: 'The number of items per page.', }, - setPageIndex: { + setOffset: { type: 'enum', - values: ['(pageIndex: number) => void'], - description: 'Handler that is called when the page index changes.', + values: ['(offset: number) => void'], + description: 'Handler that is called when the offset changes.', }, setPageSize: { type: 'enum', @@ -277,10 +277,7 @@ export const tableUsageSnippet = `import { Cell, ..., TableHeader, TablePaginati `; -export const tableBasicSnippet = `import { Table, TablePagination } from '@backstage/ui'; - -const [pageIndex, setPageIndex] = useState(0); -const [pageSize, setPageSize] = useState(5); +export const tableBasicSnippet = `import { Table, TableHeader, Column, TableBody, Row, Cell, CellProfile, TablePagination, useTable } from '@backstage/ui'; const data = [ { @@ -293,10 +290,13 @@ const data = [ // ... more data ]; -const newData = data4.slice( - pageIndex * pageSize, - (pageIndex + 1) * pageSize, -); +// Uncontrolled pagination (easiest) +const { data: paginatedData, paginationProps } = useTable({ + data, + pagination: { + defaultPageSize: 5, + }, +}); @@ -306,9 +306,9 @@ const newData = data4.slice( Albums - {newData.map(item => ( + {paginatedData?.map(item => ( -
-`; +`; From da404afd70598dbe54dbc261ef14b3a6b2ad1425 Mon Sep 17 00:00:00 2001 From: Praphull <1288395+praphull-purohit@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:20:53 +0200 Subject: [PATCH 30/71] Incorporate code review suggestion Signed-off-by: Praphull <1288395+praphull-purohit@users.noreply.github.com> --- .changeset/ripe-boxes-stand.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/ripe-boxes-stand.md b/.changeset/ripe-boxes-stand.md index 163f5ae545..f76f7b2516 100644 --- a/.changeset/ripe-boxes-stand.md +++ b/.changeset/ripe-boxes-stand.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend-module-github': minor --- -Add field blockCreations in github branch protection scaffolder actions +Add block creations field in github branch protection scaffolder actions From 169ae728b8d86364b5deb880300550a36a653b01 Mon Sep 17 00:00:00 2001 From: Owen Shartle Date: Sat, 6 Sep 2025 21:23:00 -0400 Subject: [PATCH 31/71] Moving the TechDocs entity annotation FAQ entry. Signed-off-by: Owen Shartle --- docs/features/techdocs/FAQ.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/features/techdocs/FAQ.md b/docs/features/techdocs/FAQ.md index 323489ddab..24777e5763 100644 --- a/docs/features/techdocs/FAQ.md +++ b/docs/features/techdocs/FAQ.md @@ -46,6 +46,10 @@ annotation should still be present in entity descriptor file (e.g. `catalog-info.yaml`) for Backstage to know that TechDocs is enabled for the entity. +#### What happens when you navigate to a TechDocs URL for an entity uses the `backstage.io/techdocs-entity` annotation? + +If you navigate to a TechDocs URL in the format `docs/{namespace}/{kind}/{name}` for an entity that has the `backstage.io/techdocs-entity` annotation (instead of the `backstage.io/techdocs-ref` annotation), then Backstage will redirect to the TechDocs page of the entity referenced in the value of that annotation. + #### Is it possible for users to suggest changes or provide feedback on a TechDocs page? This is supported for TechDocs sites whose source code is hosted in either @@ -57,7 +61,3 @@ your `mkdocs.yml` files per If the host name of your source code hosting URL does not include `github` or `gitlab`, an `integrations` entry in your `app-config.yaml` pointed at your source code provider is also needed (only the `host` key is necessary). - -#### What happens when you navigate to a TechDocs URL for an entity uses the `backstage.io/techdocs-entity` annotation? - -If you navigate to a TechDocs URL in the format `docs/{namespace}/{kind}/{name}` for an entity that has the `backstage.io/techdocs-entity` annotation (instead of the `backstage.io/techdocs-ref` annotation), then Backstage will redirect to the TechDocs page of the entity referenced in the value of that annotation. From 0ab3ae36541e8beee0a9f52ac8e323268833e14d Mon Sep 17 00:00:00 2001 From: Praphull Purohit <1288395+praphull-purohit@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:10:01 +0200 Subject: [PATCH 32/71] Change from minor to patch Co-authored-by: Paul Schultz Signed-off-by: Praphull Purohit <1288395+praphull-purohit@users.noreply.github.com> --- .changeset/ripe-boxes-stand.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/ripe-boxes-stand.md b/.changeset/ripe-boxes-stand.md index f76f7b2516..9a1e72f052 100644 --- a/.changeset/ripe-boxes-stand.md +++ b/.changeset/ripe-boxes-stand.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-scaffolder-backend-module-github': minor +'@backstage/plugin-scaffolder-backend-module-github': patch --- Add block creations field in github branch protection scaffolder actions From 02110671648a8cd8de00c57c5fd622c5e445a856 Mon Sep 17 00:00:00 2001 From: Praphull Purohit <1288395+praphull-purohit@users.noreply.github.com> Date: Tue, 9 Sep 2025 16:10:28 +0200 Subject: [PATCH 33/71] Remove blockCreations defaulting Apply suggestion from @schultzp2020 Co-authored-by: Paul Schultz Signed-off-by: Praphull Purohit <1288395+praphull-purohit@users.noreply.github.com> --- .../src/actions/githubBranchProtection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts index 6ae0f67135..ee8555661b 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubBranchProtection.ts @@ -77,7 +77,7 @@ export function createGithubBranchProtectionAction(options: { requireLastPushApproval = false, requiredCommitSigning = false, requiredLinearHistory = false, - blockCreations = false, + blockCreations, token: providedToken, } = ctx.input; From 33bb3c2305f1d72a45c13ef3dab3008a2cecc8e3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 2 Jul 2025 19:22:45 +0000 Subject: [PATCH 34/71] fix(deps): update dependency dompurify to v3.2.4 [security] Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index 50a73bb657..42cf3d4a3d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21537,10 +21537,10 @@ __metadata: languageName: node linkType: hard -"@types/trusted-types@npm:*": - version: 2.0.3 - resolution: "@types/trusted-types@npm:2.0.3" - checksum: 10/4794804bc4a4a173d589841b6d26cf455ff5dc4f3e704e847de7d65d215f2e7043d8757e4741ce3a823af3f08260a8d04a1a6e9c5ec9b20b7b04586956a6b005 +"@types/trusted-types@npm:*, @types/trusted-types@npm:^2.0.7": + version: 2.0.7 + resolution: "@types/trusted-types@npm:2.0.7" + checksum: 10/8e4202766a65877efcf5d5a41b7dd458480b36195e580a3b1085ad21e948bc417d55d6f8af1fd2a7ad008015d4117d5fdfe432731157da3c68678487174e4ba3 languageName: node linkType: hard @@ -28454,9 +28454,14 @@ __metadata: linkType: hard "dompurify@npm:^3.0.0, dompurify@npm:^3.1.7": - version: 3.1.7 - resolution: "dompurify@npm:3.1.7" - checksum: 10/dc637a064306f83cf911caa267ffe1f973552047602020e3b6723c90f67962813edf8a65a0b62e8c9bc13fcd173a2691212a3719bc116226967f46bcd6181277 + version: 3.2.6 + resolution: "dompurify@npm:3.2.6" + dependencies: + "@types/trusted-types": "npm:^2.0.7" + dependenciesMeta: + "@types/trusted-types": + optional: true + checksum: 10/b91631ed0e4d17fae950ef53613cc009ed7e73adc43ac94a41dd52f35483f7538d13caebdafa7626e0da145fc8184e7ac7935f14f25b7e841b32fda777e40447 languageName: node linkType: hard From d821c01c5ecca6873c6c2a33e3e205dd9c27c4c0 Mon Sep 17 00:00:00 2001 From: Jackson Chen Date: Tue, 9 Sep 2025 17:31:19 -0400 Subject: [PATCH 35/71] refactor and fix dompurify tsc errors Signed-off-by: Jackson Chen --- .../transformers/html/hooks/attributes.ts | 32 +++++++++++++++ .../reader/transformers/html/hooks/iframes.ts | 7 +++- .../reader/transformers/html/hooks/index.ts | 2 + .../reader/transformers/html/hooks/links.ts | 8 ++-- .../transformers/html/hooks/metatags.ts | 41 +++++++++++++++++++ .../reader/transformers/html/transformer.ts | 30 ++++---------- .../src/reader/transformers/html/utils.ts | 19 +++++++++ 7 files changed, 113 insertions(+), 26 deletions(-) create mode 100644 plugins/techdocs/src/reader/transformers/html/hooks/attributes.ts create mode 100644 plugins/techdocs/src/reader/transformers/html/hooks/metatags.ts create mode 100644 plugins/techdocs/src/reader/transformers/html/utils.ts diff --git a/plugins/techdocs/src/reader/transformers/html/hooks/attributes.ts b/plugins/techdocs/src/reader/transformers/html/hooks/attributes.ts new file mode 100644 index 0000000000..ae0b4e2ce9 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/html/hooks/attributes.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. + */ +import { UponSanitizeAttributeHook } from 'dompurify'; + +/** + * Removes attributes that should only be present on meta tags from other elements. + * This ensures that http-equiv and content attributes are only allowed on meta tags + * where they are required for the redirect feature. + */ +export const removeRestrictedAttributes: UponSanitizeAttributeHook = ( + node, + data, +) => { + if (node.tagName !== 'META') { + if (data.attrName === 'http-equiv' || data.attrName === 'content') { + node.removeAttribute(data.attrName); + } + } +}; diff --git a/plugins/techdocs/src/reader/transformers/html/hooks/iframes.ts b/plugins/techdocs/src/reader/transformers/html/hooks/iframes.ts index 25259dbc43..23711b52b7 100644 --- a/plugins/techdocs/src/reader/transformers/html/hooks/iframes.ts +++ b/plugins/techdocs/src/reader/transformers/html/hooks/iframes.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { isElement } from '../utils'; + /** * Checks whether a node is iframe or not. * @param node - can be any element. @@ -42,9 +44,10 @@ const isSafe = (node: Element, hosts: string[]) => { * @param node - can be any element. * @param hosts - list of allowed hosts. */ -export const removeUnsafeIframes = (hosts: string[]) => (node: Element) => { +export const removeUnsafeIframes = (hosts: string[]) => (node: Node) => { + if (!isElement(node)) return; + if (isIframe(node) && !isSafe(node, hosts)) { node.remove(); } - return node; }; diff --git a/plugins/techdocs/src/reader/transformers/html/hooks/index.ts b/plugins/techdocs/src/reader/transformers/html/hooks/index.ts index a4356db2e9..ce237ec6d2 100644 --- a/plugins/techdocs/src/reader/transformers/html/hooks/index.ts +++ b/plugins/techdocs/src/reader/transformers/html/hooks/index.ts @@ -16,3 +16,5 @@ export { removeUnsafeLinks } from './links'; export { removeUnsafeIframes } from './iframes'; +export { removeUnsafeMetaTags } from './metatags'; +export { removeRestrictedAttributes } from './attributes'; diff --git a/plugins/techdocs/src/reader/transformers/html/hooks/links.ts b/plugins/techdocs/src/reader/transformers/html/hooks/links.ts index 38f775cfbe..eb2cbfc6de 100644 --- a/plugins/techdocs/src/reader/transformers/html/hooks/links.ts +++ b/plugins/techdocs/src/reader/transformers/html/hooks/links.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { isElement } from '../utils'; + const MKDOCS_CSS = /main\.[A-Fa-f0-9]{8}\.min\.css$/; const GOOGLE_FONTS = /^https:\/\/fonts\.googleapis\.com/; const GSTATIC_FONTS = /^https:\/\/fonts\.gstatic\.com/; @@ -41,11 +43,11 @@ const isSafe = (node: Element) => { /** * Function that removes unsafe link nodes. * @param node - can be any element. - * @param hosts - list of allowed hosts. */ -export const removeUnsafeLinks = (node: Element) => { +export const removeUnsafeLinks = (node: Node) => { + if (!isElement(node)) return; + if (isLink(node) && !isSafe(node)) { node.remove(); } - return node; }; diff --git a/plugins/techdocs/src/reader/transformers/html/hooks/metatags.ts b/plugins/techdocs/src/reader/transformers/html/hooks/metatags.ts new file mode 100644 index 0000000000..a207a5bca2 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/html/hooks/metatags.ts @@ -0,0 +1,41 @@ +/* + * 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 { UponSanitizeElementHook } from 'dompurify'; +import { isElement } from '../utils'; + +/** + * Checks if a meta tag is a refresh redirect tag that should be allowed. + * These tags are required for the TechDocs redirect feature. + */ +const isAllowedMetaRefreshTag = (element: Element): boolean => { + const httpEquiv = element.getAttribute('http-equiv'); + const content = element.getAttribute('content'); + + return httpEquiv === 'refresh' && content?.includes('url=') === true; +}; + +/** + * Removes unsafe meta tags from the DOM while preserving allowed refresh redirect tags. + * Only meta tags used for page refreshing/redirects are allowed as they are required + * for the TechDocs redirect feature. + */ +export const removeUnsafeMetaTags: UponSanitizeElementHook = (node, data) => { + if (!isElement(node)) return; + + if (data.tagName === 'meta' && !isAllowedMetaRefreshTag(node)) { + node.parentNode?.removeChild(node); + } +}; diff --git a/plugins/techdocs/src/reader/transformers/html/transformer.ts b/plugins/techdocs/src/reader/transformers/html/transformer.ts index d4b31a34e5..6ebf53f26e 100644 --- a/plugins/techdocs/src/reader/transformers/html/transformer.ts +++ b/plugins/techdocs/src/reader/transformers/html/transformer.ts @@ -20,7 +20,12 @@ import { useCallback, useMemo } from 'react'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; import { Transformer } from '../transformer'; -import { removeUnsafeIframes, removeUnsafeLinks } from './hooks'; +import { + removeRestrictedAttributes, + removeUnsafeIframes, + removeUnsafeLinks, + removeUnsafeMetaTags, +} from './hooks'; /** * Returns html sanitizer configuration @@ -51,26 +56,9 @@ export const useSanitizerTransformer = (): Transformer => { DOMPurify.addHook('beforeSanitizeElements', removeUnsafeIframes(hosts)); } - // Only allow meta tags if they are used for refreshing the page. They are required for the redirect feature. - DOMPurify.addHook('uponSanitizeElement', (currNode, data) => { - if (data.tagName === 'meta') { - const isMetaRefreshTag = - currNode.getAttribute('http-equiv') === 'refresh' && - currNode.getAttribute('content')?.includes('url='); - if (!isMetaRefreshTag) { - currNode.parentNode?.removeChild(currNode); - } - } - }); + DOMPurify.addHook('uponSanitizeElement', removeUnsafeMetaTags); - // Only allow http-equiv and content attributes on meta tags. They are required for the redirect feature. - DOMPurify.addHook('uponSanitizeAttribute', (currNode, data) => { - if (currNode.tagName !== 'META') { - if (data.attrName === 'http-equiv' || data.attrName === 'content') { - currNode.removeAttribute(data.attrName); - } - } - }); + DOMPurify.addHook('uponSanitizeAttribute', removeRestrictedAttributes); const tagNameCheck = config?.getOptionalString( 'allowedCustomElementTagNameRegExp', @@ -122,7 +110,7 @@ export const useSanitizerTransformer = (): Transformer => { ? new RegExp(attributeNameCheck) : undefined, }, - }); + }) as Element; }, [config], ); diff --git a/plugins/techdocs/src/reader/transformers/html/utils.ts b/plugins/techdocs/src/reader/transformers/html/utils.ts new file mode 100644 index 0000000000..b97b63743c --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/html/utils.ts @@ -0,0 +1,19 @@ +/* + * 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 const isElement = (node: Node): node is Element => { + return node.nodeType === Node.ELEMENT_NODE; +}; From 313cec7bed81039ea9473227c035f57396fa7a97 Mon Sep 17 00:00:00 2001 From: Jackson Chen Date: Tue, 9 Sep 2025 17:47:35 -0400 Subject: [PATCH 36/71] add changeset Signed-off-by: Jackson Chen --- .changeset/chilly-llamas-attend.md | 5 +++++ plugins/techdocs/package.json | 2 +- yarn.lock | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/chilly-llamas-attend.md diff --git a/.changeset/chilly-llamas-attend.md b/.changeset/chilly-llamas-attend.md new file mode 100644 index 0000000000..0a0876ccac --- /dev/null +++ b/.changeset/chilly-llamas-attend.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Updated dependency `dompurify` to `^3.2.4`. diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index ca867db802..7777133862 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -81,7 +81,7 @@ "@material-ui/lab": "4.0.0-alpha.61", "@material-ui/styles": "^4.10.0", "@microsoft/fetch-event-source": "^2.0.1", - "dompurify": "^3.0.0", + "dompurify": "^3.2.4", "git-url-parse": "^15.0.0", "jss": "~10.10.0", "lodash": "^4.17.21", diff --git a/yarn.lock b/yarn.lock index 42cf3d4a3d..0e5078d824 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7289,7 +7289,7 @@ __metadata: "@testing-library/user-event": "npm:^14.0.0" "@types/dompurify": "npm:^3.0.0" "@types/react": "npm:^18.0.0" - dompurify: "npm:^3.0.0" + dompurify: "npm:^3.2.4" git-url-parse: "npm:^15.0.0" jss: "npm:~10.10.0" lodash: "npm:^4.17.21" @@ -28453,7 +28453,7 @@ __metadata: languageName: node linkType: hard -"dompurify@npm:^3.0.0, dompurify@npm:^3.1.7": +"dompurify@npm:^3.1.7, dompurify@npm:^3.2.4": version: 3.2.6 resolution: "dompurify@npm:3.2.6" dependencies: From 7bd14534b86fab32d5dfbf0f4d5fcc2b6e8c9557 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Wed, 10 Sep 2025 11:10:39 +0530 Subject: [PATCH 37/71] Made the chages to the software catalog Link Signed-off-by: Aditya Kumar --- docs/frontend-system/building-plugins/01-index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/building-plugins/01-index.md b/docs/frontend-system/building-plugins/01-index.md index 03da20ea73..283c162fa7 100644 --- a/docs/frontend-system/building-plugins/01-index.md +++ b/docs/frontend-system/building-plugins/01-index.md @@ -185,7 +185,7 @@ export const examplePlugin = createFrontendPlugin({ ## Plugin specific extensions -There are many different plugins that you can extend with additional functionality through extensions. One such plugin is [the catalog plugin](https://backstage.io/docs/features/software-catalog/), one of the core features of Backstage. It lets you catalog the software in your organization, where each item in the catalog has its own page that can be populated with tools and information relating to that catalog entity. In this example we will explore how our plugin can provide such a tool to display on an entity page. +There are many different plugins that you can extend with additional functionality through extensions. One such plugin is [the catalog plugin](../../features/software-catalog/), one of the core features of Backstage. It lets you catalog the software in your organization, where each item in the catalog has its own page that can be populated with tools and information relating to that catalog entity. In this example we will explore how our plugin can provide such a tool to display on an entity page. ```tsx title="in src/plugin.ts - An example entity content extension" import { EntityContentBlueprint } from '@backstage/plugin-catalog-react/alpha'; From e72c879b09c344602f330545f8700e84a2c8c896 Mon Sep 17 00:00:00 2001 From: Aditya Kumar Date: Wed, 10 Sep 2025 12:16:48 +0530 Subject: [PATCH 38/71] Updated Note format Signed-off-by: Aditya Kumar --- plugins/catalog-backend/src/schema/openapi.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index baaba44d48..e4282f1700 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -6,8 +6,9 @@ info: The API surface consists of a few distinct groups of functionality. Each has a dedicated section below. - > **Note:** This page only describes some of the most commonly used parts of the - > API, and is a work in progress. + :::note Note + This page only describes some of the most commonly used parts of the API, and is a work in progress. + ::: All of the URL paths in this article are assumed to be on top of some base URL pointing at your catalog installation. For example, if the path given in a From 184d03a4cfaab326c09ac66a6c2e1dcb03cb5cf1 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 10 Sep 2025 10:34:24 +0200 Subject: [PATCH 39/71] clean up api-docs community-plugins Signed-off-by: Vincenzo Scamporlino --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7aed18ab74..5534155276 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,9 @@ "scripts": { "build-storybook": "storybook build --output-dir dist-storybook", "build:all": "backstage-cli repo build --all", - "build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(adr|adr-backend|adr-common|airbrake|airbrake-backend|allure|analytics-module-ga|analytics-module-ga4|analytics-module-newrelic-browser|apache-airflow|api-docs|api-docs-module-protoc-gen-doc|apollo-explorer|app-visualizer|azure-devops|azure-devops-backend|azure-devops-common|azure-sites|azure-sites-backend|azure-sites-common|badges|badges-backend|bazaar|bazaar-backend|bitbucket-cloud-common|bitrise|catalog-graph|catalog-graphql|catalog-import|catalog-unprocessed-entities|cicd-statistics|cicd-statistics-module-gitlab|circleci|cloudbuild|code-climate|code-coverage|code-coverage-backend|codescene|config-schema|cost-insights|cost-insights-common|dynatrace|entity-feedback|entity-feedback-backend|entity-feedback-common|entity-validation|example-todo-list|example-todo-list-backend|example-todo-list-common|firehydrant|fossa|gcalendar|gcp-projects|git-release-manager|github-actions|github-deployments|github-issues|github-pull-requests-board|gitops-profiles|gocd|graphiql|graphql-backend|graphql-voyager|ilert|jenkins|jenkins-backend|jenkins-common|kafka|kafka-backend|lighthouse|lighthouse-backend|lighthouse-common|linguist|linguist-backend|linguist-common|microsoft-calendar|newrelic|newrelic-dashboard|nomad|nomad-backend|octopus-deploy|opencost|pagerduty|periskop|periskop-backend|playlist|playlist-backend|playlist-common|proxy-backend|puppetdb|rollbar|rollbar-backend|sentry|shortcuts|splunk-on-call|stack-overflow|stack-overflow-backend|stackstorm|tech-radar|tech-radar-2|todo|todo-backend|xcmetrics)'", + "build:api-docs": "LANG=en_EN yarn build:api-reports --docs --exclude 'plugins/@(api-docs|api-docs-module-protoc-gen-doc|app-visualizer|catalog-graph|catalog-import|catalog-unprocessed-entities|config-schema|example-todo-list|example-todo-list-backend)'", "build:api-reports": "yarn build:api-reports:only --tsc", - "build:api-reports:only": "LANG=en_US.UTF-8 NODE_OPTIONS=--max-old-space-size=8192 backstage-repo-tools api-reports --sql-reports --allow-warnings 'packages/backend-app-api,packages/core-components,plugins/+(catalog|catalog-import|git-release-manager|jenkins|kubernetes)' -o ae-undocumented,ae-wrong-input-file-type --validate-release-tags", + "build:api-reports:only": "LANG=en_US.UTF-8 NODE_OPTIONS=--max-old-space-size=8192 backstage-repo-tools api-reports --sql-reports --allow-warnings 'packages/backend-app-api,packages/core-components,plugins/+(catalog|catalog-import|kubernetes)' -o ae-undocumented,ae-wrong-input-file-type --validate-release-tags", "build:backend": "yarn workspace example-backend build", "build:knip-reports": "backstage-repo-tools knip-reports", "build:plugins-report": "node ./scripts/build-plugins-report", From d03010ec9be71f0f756cfbc860de3120ff87f0e7 Mon Sep 17 00:00:00 2001 From: Hayato Kihara <14058454+gumimin@users.noreply.github.com> Date: Thu, 11 Sep 2025 03:23:23 +0900 Subject: [PATCH 40/71] docs: Add missing TOC entries to TechDocs FAQ (#30900) Signed-off-by: Hayato Kihara --- .github/vale/config/vocabularies/Backstage/accept.txt | 1 + docs/features/techdocs/FAQ.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index ec43e1fbbc..516eec516f 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -146,6 +146,7 @@ Expedia facto failover Fargate +faqs featureful Figma firehydrant diff --git a/docs/features/techdocs/FAQ.md b/docs/features/techdocs/FAQ.md index 24777e5763..9c78e3e123 100644 --- a/docs/features/techdocs/FAQ.md +++ b/docs/features/techdocs/FAQ.md @@ -12,6 +12,8 @@ This page answers frequently asked questions about [TechDocs](README.md). - [What static site generator is TechDocs using?](#what-static-site-generator-is-techdocs-using) - [What is the mkdocs-techdocs-core plugin?](#what-is-the-mkdocs-techdocs-core-plugin) - [Does TechDocs support file formats other than Markdown (e.g. RST, AsciiDoc)?](#does-techdocs-support-file-formats-other-than-markdown-eg-rst-asciidoc-) +- [What should be the value of `backstage.io/techdocs-ref` when using external build and storage?](#what-should-be-the-value-of-backstageiotechdocs-ref-when-using-external-build-and-storage) +- [Is it possible for users to suggest changes or provide feedback on a TechDocs page?](#is-it-possible-for-users-to-suggest-changes-or-provide-feedback-on-a-techdocs-page) #### What static site generator is TechDocs using? From 8d18d23e347ef24ca30c26fe4fb4efe3e870227f Mon Sep 17 00:00:00 2001 From: Luna Stadler Date: Wed, 10 Sep 2025 20:34:58 +0200 Subject: [PATCH 41/71] Improve TechDocs page titles (especially for nested pages) (#31054) * Replace underscores in techdocs titles Signed-off-by: Luna Stadler * Make techdocs titles similar to component titles The pattern for components is entity name, page/tab and then app title. This ordering makes it easier to distinguish tabs at a glance. Signed-off-by: Luna Stadler * Abbreviate nested pages in techdocs A deeply nested page like `/really/very/deeply/nested/page`, will now become "Really | ... | Nested | Page". This should preserve some of the context and support docs whith deeply nested pages. Signed-off-by: Luna Stadler * Add changeset for TechDocs page title improvements Signed-off-by: Luna Stadler * Display the full title based on all parts of the path Signed-off-by: Luna Stadler --------- Signed-off-by: Luna Stadler --- .changeset/clear-houses-wonder.md | 5 +++ .../TechDocsReaderPageHeader.test.tsx | 33 ++++++++++++++++--- .../TechDocsReaderPageHeader.tsx | 5 ++- 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 .changeset/clear-houses-wonder.md diff --git a/.changeset/clear-houses-wonder.md b/.changeset/clear-houses-wonder.md new file mode 100644 index 0000000000..46ada62fbe --- /dev/null +++ b/.changeset/clear-houses-wonder.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +TechDocs page titles have been improved, especially for deeply nested pages. diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.test.tsx index 6f6ee81c8b..3c10b26f7f 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.test.tsx @@ -52,13 +52,11 @@ const mockTechDocsMetadata = { site_description: 'test-site-desc', }; -const mockUseParams = jest.fn(); -mockUseParams.mockReturnValue({ '*': 'foo/bar/baz/' }); - +let useParamsPath = '/'; jest.mock('react-router-dom', () => { return { ...(jest.requireActual('react-router-dom') as any), - useParams: () => mockUseParams(), + useParams: () => ({ '*': useParamsPath }), }; }); @@ -189,6 +187,7 @@ describe('', () => { getEntityMetadata.mockResolvedValue(mockEntityMetadata); getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + useParamsPath = 'foo/bar/baz/'; await renderInTestApp( @@ -203,7 +202,31 @@ describe('', () => { await waitFor(() => { expect(document.title).toEqual( - 'Backstage | Test Entity | Foo | Bar | Baz', + 'Test Entity | Foo | Bar | Baz | Backstage', + ); + }); + }); + + it('The header title is abbreviated if path is too long', async () => { + getEntityMetadata.mockResolvedValue(mockEntityMetadata); + getTechDocsMetadata.mockResolvedValue(mockTechDocsMetadata); + + useParamsPath = 'foo/bar/baz/qux/quux/'; + await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + '/docs': rootRouteRef, + }, + }, + ); + + await waitFor(() => { + expect(document.title).toEqual( + 'Test Entity | Foo | Bar | Baz | Qux | Quux | Backstage', ); }); }); diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx index bafcdd270c..6328becc73 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader/TechDocsReaderPageHeader.tsx @@ -172,17 +172,16 @@ export const TechDocsReaderPageHeader = ( const removeTrailingSlash = (str: string) => str.replace(/\/$/, ''); const normalizeAndSpace = (str: string) => - str.replace(/-/g, ' ').split(' ').map(capitalize).join(' '); + str.replace(/[-_]/g, ' ').split(' ').map(capitalize).join(' '); let techdocsTabTitleItems: string[] = []; if (path !== '') techdocsTabTitleItems = removeTrailingSlash(path) .split('/') - .slice(0, 3) .map(normalizeAndSpace); - const tabTitleItems = [appTitle, entityDisplayName, ...techdocsTabTitleItems]; + const tabTitleItems = [entityDisplayName, ...techdocsTabTitleItems, appTitle]; const tabTitle = tabTitleItems.join(' | '); return ( From 79ff318518479ed193d50f37fddfbfe65070ac4a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 11 Sep 2025 00:53:22 +0200 Subject: [PATCH 42/71] catalog-react: removed entity card type deprecation warning Signed-off-by: Patrik Oldsberg --- .changeset/tough-cars-cry.md | 5 +++++ .../src/alpha/blueprints/EntityCardBlueprint.ts | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 .changeset/tough-cars-cry.md diff --git a/.changeset/tough-cars-cry.md b/.changeset/tough-cars-cry.md new file mode 100644 index 0000000000..9235553da7 --- /dev/null +++ b/.changeset/tough-cars-cry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Removed the deprecation warning when not passing an explicit type to `EntityCardBlueprint`. Omitting the type is now intended, allowing the layout to pick the default type instead, typically `content`. diff --git a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts index b2f64c19e6..fed6ced70f 100644 --- a/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts +++ b/plugins/catalog-react/src/alpha/blueprints/EntityCardBlueprint.ts @@ -75,11 +75,6 @@ export const EntityCardBlueprint = createExtensionBlueprint({ const finalType = config.type ?? type; if (finalType) { yield entityCardTypeDataRef(finalType); - } else { - // eslint-disable-next-line no-console - console.warn( - `DEPRECATION WARNING: Not providing type for entity cards is deprecated. Missing from '${node.spec.id}'`, - ); } }, }); From 56897d717e9445eb7aacfceba3f17bfe782f4809 Mon Sep 17 00:00:00 2001 From: Lee Standen Date: Wed, 10 Sep 2025 15:57:16 -0700 Subject: [PATCH 43/71] Fixes issue with organization name case sensitivity when using allowedInstallationOwners Signed-off-by: Lee Standen --- .changeset/thin-phones-press.md | 5 +++ ...eInstanceGithubCredentialsProvider.test.ts | 36 +++++++++++++++++++ ...SingleInstanceGithubCredentialsProvider.ts | 10 ++++-- 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 .changeset/thin-phones-press.md diff --git a/.changeset/thin-phones-press.md b/.changeset/thin-phones-press.md new file mode 100644 index 0000000000..8b3f985772 --- /dev/null +++ b/.changeset/thin-phones-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': patch +--- + +Fixes issue with Github credentials provider which fails to match organization name if using allowedInstallationOwners diff --git a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts index ec07d1b465..0468e96e14 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.test.ts @@ -231,6 +231,42 @@ describe('SingleInstanceGithubCredentialsProvider tests', () => { expect(token).toEqual(undefined); }); + it('should not fail to issue tokens for an organization when there is a case mismatch in the organization name', async () => { + octokit.apps.listInstallations.mockResolvedValue({ + headers: { + etag: '123', + }, + data: [ + { + id: 1, + repository_selection: 'selected', + account: { + login: 'backstage', + }, + }, + ], + } as RestEndpointMethodTypes['apps']['listInstallations']['response']); + + octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ + data: { + expires_at: DateTime.local().plus({ hours: 1 }).toString(), + token: 'secret_token', + repository_selection: 'selected', + }, + } as RestEndpointMethodTypes['apps']['createInstallationAccessToken']['response']); + + octokit.apps.listReposAccessibleToInstallation.mockReturnValue({ + data: [{ name: 'some-repo' }], + } as unknown as RestEndpointMethodTypes['apps']['listReposAccessibleToInstallation']['response']); + + const { token, headers } = await github.getCredentials({ + url: 'https://github.com/Backstage', + }); + const expectedToken = 'secret_token'; + expect(headers).toEqual({ Authorization: `Bearer ${expectedToken}` }); + expect(token).toEqual('secret_token'); + }); + it('should not fail to issue tokens for an organization when the app is installed for a single repo', async () => { octokit.apps.listInstallations.mockResolvedValue({ headers: { diff --git a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts index fd3c02486f..eaaabe8b0f 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts @@ -102,7 +102,9 @@ class GithubAppManager { private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations constructor(config: GithubAppConfig, baseUrl?: string) { - this.allowedInstallationOwners = config.allowedInstallationOwners; + this.allowedInstallationOwners = config.allowedInstallationOwners?.map( + owner => owner.toLocaleLowerCase('en-US'), + ); this.baseUrl = baseUrl; this.baseAuthConfig = { appId: config.appId, @@ -121,7 +123,11 @@ class GithubAppManager { repo?: string, ): Promise<{ accessToken: string | undefined }> { if (this.allowedInstallationOwners) { - if (!this.allowedInstallationOwners?.includes(owner)) { + if ( + !this.allowedInstallationOwners?.includes( + owner.toLocaleLowerCase('en-US'), + ) + ) { return { accessToken: undefined }; // An empty token allows anonymous access to public repos } } From 8f9d0f947cdf710c629ae647c46c2ce63ece8ebd Mon Sep 17 00:00:00 2001 From: Hellgren Heikki Date: Thu, 11 Sep 2025 08:54:18 +0300 Subject: [PATCH 44/71] feat(microsite): add search for plugins Signed-off-by: Hellgren Heikki --- .../pluginsSearch/pluginsSearch.tsx | 19 +++++ microsite/src/pages/plugins/index.tsx | 84 ++++++++++++------- .../src/pages/plugins/plugins.module.scss | 14 ++++ 3 files changed, 88 insertions(+), 29 deletions(-) create mode 100644 microsite/src/components/pluginsSearch/pluginsSearch.tsx diff --git a/microsite/src/components/pluginsSearch/pluginsSearch.tsx b/microsite/src/components/pluginsSearch/pluginsSearch.tsx new file mode 100644 index 0000000000..68f9a2831a --- /dev/null +++ b/microsite/src/components/pluginsSearch/pluginsSearch.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +type Props = { + searchTerm: string; + onSearchTermChange: (newTerm: string) => void; +}; + +export const PluginsSearch = (props: Props) => { + const { searchTerm, onSearchTermChange } = props; + return ( + onSearchTermChange((e.target as HTMLInputElement).value)} + value={searchTerm} + placeholder="Search plugins..." + className="DocSearch-Input search" + /> + ); +}; diff --git a/microsite/src/pages/plugins/index.tsx b/microsite/src/pages/plugins/index.tsx index 2abdbabf4b..1a87f9c0af 100644 --- a/microsite/src/pages/plugins/index.tsx +++ b/microsite/src/pages/plugins/index.tsx @@ -5,10 +5,11 @@ import { truncateDescription } from '@site/src/util/truncateDescription'; import { ChipCategory } from '@site/src/util/types'; import Layout from '@theme/Layout'; import clsx from 'clsx'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { IPluginData, PluginCard } from './_pluginCard'; import pluginsStyles from './plugins.module.scss'; +import { PluginsSearch } from '@site/src/components/pluginsSearch/pluginsSearch'; interface IPluginsList { corePlugins: IPluginData[]; @@ -55,6 +56,7 @@ const Plugins = () => { const [selectedCategories, setSelectedCategories] = useState([]); const [showCoreFeatures, setShowCoreFeatures] = useState(true); const [showOtherPlugins, setShowOtherPlugins] = useState(true); + const [searchTerm, setSearchTerm] = useState(''); const handleChipClick = (categoryName: string) => { const isSelected = @@ -97,6 +99,34 @@ const Plugins = () => { } }; + const matchesSearch = (pluginData: IPluginData, term: string) => { + if (!term) return true; + const lowerTerm = term.toLowerCase(); + return ( + pluginData.title.toLowerCase().includes(lowerTerm) || + pluginData.description.toLowerCase().includes(lowerTerm) || + pluginData.category.toLowerCase().includes(lowerTerm) || + (pluginData.author && pluginData.author.toLowerCase().includes(lowerTerm)) + ); + }; + + const matchesCategory = (pluginData: IPluginData, categories: string[]) => { + if (categories.length === 0) return true; + return categories.includes(pluginData.category); + }; + + const corePlugins = useMemo(() => { + return plugins.corePlugins + .filter(pluginData => matchesCategory(pluginData, selectedCategories)) + .filter(pluginData => matchesSearch(pluginData, searchTerm)); + }, [selectedCategories, searchTerm]); + + const otherPlugins = useMemo(() => { + return plugins.otherPlugins + .filter(pluginData => matchesCategory(pluginData, selectedCategories)) + .filter(pluginData => matchesSearch(pluginData, searchTerm)); + }, [selectedCategories, searchTerm]); + return (
{ categories={categories} handleChipClick={handleChipClick} /> +
- {showCoreFeatures && ( + {corePlugins.length === 0 && otherPlugins.length === 0 && ( +
+

No plugins found

+

+ We couldn't find any plugins matching your criteria. Please try + adjusting your search or filter settings. +

+
+ )} + + {showCoreFeatures && corePlugins.length > 0 && (
-

Core Features

+

Core Features ({corePlugins.length})

- {plugins.corePlugins - .filter( - pluginData => - !selectedCategories.length || - selectedCategories.includes(pluginData.category), - ) - .map(pluginData => ( - - ))} + {corePlugins.map(pluginData => ( + + ))}
)} - {showOtherPlugins && ( + {showOtherPlugins && otherPlugins.length > 0 && (
-

All Plugins

+

All Plugins ({otherPlugins.length})

Friendly reminder: While we love the variety and contributions of our open source plugins, they haven't been fully vetted by the @@ -158,18 +193,9 @@ const Plugins = () => { your due diligence before installing. Happy exploring!

- {plugins.otherPlugins - .filter( - pluginData => - !selectedCategories.length || - selectedCategories.includes(pluginData.category), - ) - .map(pluginData => ( - - ))} + {otherPlugins.map(pluginData => ( + + ))}
)} diff --git a/microsite/src/pages/plugins/plugins.module.scss b/microsite/src/pages/plugins/plugins.module.scss index fb17e90625..a17e4bc5c0 100644 --- a/microsite/src/pages/plugins/plugins.module.scss +++ b/microsite/src/pages/plugins/plugins.module.scss @@ -76,6 +76,20 @@ height: 1rem; } + :global(.search) { + float: right; + margin-bottom: 1rem; + margin-right: 1rem; + font-size: calc(0.875rem * var(--ifm-button-size-multiplier)); + font-weight: var(--ifm-button-font-weight); + background-color: transparent; + border: var(--ifm-button-border-width) solid var(--ifm-color-primary); + border-radius: var(--ifm-button-border-radius); + color: var(--ifm-color-primary); + width: 220px; + height: 2.2rem; + } + :global(.dropdown) { float: right; margin-bottom: 1rem; From 8c51c700202c573e6ce50b2b0e26c0295babb22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 11 Sep 2025 13:16:16 +0200 Subject: [PATCH 45/71] fix a non-working example template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../sample-templates/notifications-demo/template.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml b/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml index abd18b1d42..56a7e7f5c6 100644 --- a/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml +++ b/plugins/scaffolder-backend/sample-templates/notifications-demo/template.yaml @@ -32,7 +32,7 @@ spec: title: Title type: string description: Notification title - description: + info: title: Description type: string description: Notification longer description @@ -67,7 +67,7 @@ spec: recipients: ${{ parameters.recipients }} entityRefs: ${{ parameters.entityRefs }} title: ${{ parameters.title }} - description: ${{ parameters.description }} + info: ${{ parameters.info }} link: ${{ parameters.link }} severity: ${{ parameters.severity }} topic: ${{ parameters.topic }} From 4815b120f8d45ae0741419ef227d79e6c8893046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 11 Sep 2025 13:49:15 +0200 Subject: [PATCH 46/71] fix notifications rendering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/cold-garlics-care.md | 5 +++++ plugins/notifications/src/alpha.tsx | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 .changeset/cold-garlics-care.md diff --git a/.changeset/cold-garlics-care.md b/.changeset/cold-garlics-care.md new file mode 100644 index 0000000000..960ebe6bd4 --- /dev/null +++ b/.changeset/cold-garlics-care.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications': patch +--- + +Fixed missing app context when rendering the notifications view diff --git a/plugins/notifications/src/alpha.tsx b/plugins/notifications/src/alpha.tsx index 6d32150c35..ca4be58209 100644 --- a/plugins/notifications/src/alpha.tsx +++ b/plugins/notifications/src/alpha.tsx @@ -23,6 +23,7 @@ import { } from '@backstage/frontend-plugin-api'; import { rootRouteRef } from './routes'; import { + compatWrapper, convertLegacyRouteRef, convertLegacyRouteRefs, } from '@backstage/core-compat-api'; @@ -33,9 +34,9 @@ const page = PageBlueprint.make({ path: '/notifications', routeRef: convertLegacyRouteRef(rootRouteRef), loader: () => - import('./components/NotificationsPage').then(m => ( - - )), + import('./components/NotificationsPage').then(m => + compatWrapper(), + ), }, }); From 987cd8a6f564e68ff748ca3d49cc530a44db438f Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Thu, 11 Sep 2025 13:31:41 +0200 Subject: [PATCH 47/71] chore: make the catalog:get-catalog-entity action readOnly Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- .../src/actions/createGetCatalogEntityAction.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/catalog-backend/src/actions/createGetCatalogEntityAction.ts b/plugins/catalog-backend/src/actions/createGetCatalogEntityAction.ts index a1c7cefe78..b9f98291fd 100644 --- a/plugins/catalog-backend/src/actions/createGetCatalogEntityAction.ts +++ b/plugins/catalog-backend/src/actions/createGetCatalogEntityAction.ts @@ -28,6 +28,11 @@ export const createGetCatalogEntityAction = ({ actionsRegistry.register({ name: 'get-catalog-entity', title: 'Get Catalog Entity', + attributes: { + destructive: false, + readOnly: true, + idempotent: true, + }, description: ` This allows you to get a single entity from the software catalog. Each entity in the software catalog has a unique name, kind, and namespace. The default namespace is "default". From 2c0894a64b0d73a4f973d92cf482bca05c8f9d30 Mon Sep 17 00:00:00 2001 From: MT Lewis Date: Thu, 11 Sep 2025 17:10:30 +0100 Subject: [PATCH 48/71] bui: fix TextField story Signed-off-by: MT Lewis --- packages/ui/src/components/TextField/TextField.stories.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/ui/src/components/TextField/TextField.stories.tsx b/packages/ui/src/components/TextField/TextField.stories.tsx index 57cc2b2909..eb64e95514 100644 --- a/packages/ui/src/components/TextField/TextField.stories.tsx +++ b/packages/ui/src/components/TextField/TextField.stories.tsx @@ -113,6 +113,7 @@ export const DisabledWithIcon: Story = { ...WithIcon.args, isDisabled: true, }, + render: WithIcon.render, }; export const ShowError: Story = { From e934a2777571f7c04f01f1c78ca3372971f8a031 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 12 Sep 2025 08:58:17 +0200 Subject: [PATCH 49/71] chore: added the ability to pull actions from actions registry Signed-off-by: benjdlambert --- .changeset/dark-teams-give.md | 5 ++ .../src/ScaffolderPlugin.ts | 4 ++ .../scaffolder-backend/src/service/router.ts | 46 ++++++++++++++++++- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 .changeset/dark-teams-give.md diff --git a/.changeset/dark-teams-give.md b/.changeset/dark-teams-give.md new file mode 100644 index 0000000000..692f015618 --- /dev/null +++ b/.changeset/dark-teams-give.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Updating `catalog:get-catalog-entity` action to be `readOnly` and non destructive diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index 7ef3abcbe3..a00da9e524 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -56,6 +56,7 @@ import { convertFiltersToRecord, convertGlobalsToRecord, } from './util/templating'; +import { actionsServiceRef } from '@backstage/backend-plugin-api/alpha'; /** * Scaffolder plugin @@ -139,6 +140,7 @@ export const scaffolderPlugin = createBackendPlugin({ auditor: coreServices.auditor, catalog: catalogServiceRef, events: eventsServiceRef, + actionsRegistry: actionsServiceRef, }, async init({ logger, @@ -153,6 +155,7 @@ export const scaffolderPlugin = createBackendPlugin({ permissions, events, auditor, + actionsRegistry, }) { const log = loggerToWinstonLogger(logger); const integrations = ScmIntegrations.fromConfig(config); @@ -222,6 +225,7 @@ export const scaffolderPlugin = createBackendPlugin({ additionalWorkspaceProviders, events, auditor, + actionsRegistry, }); httpRouter.use(router); }, diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 8296a6003f..2c9df0c8c4 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -26,6 +26,7 @@ import { resolveSafeChildPath, SchedulerService, } from '@backstage/backend-plugin-api'; +import { Schema } from 'jsonschema'; import { CompoundEntityRef, Entity, @@ -131,6 +132,8 @@ import { } from './rules'; import { TaskFilters } from '@backstage/plugin-scaffolder-node'; +import { ActionsService } from '@backstage/backend-plugin-api/alpha'; +import { isObject } from 'lodash'; /** * RouterOptions @@ -163,6 +166,7 @@ export interface RouterOptions { events?: EventsService; auditor?: AuditorService; autocompleteHandlers?: Record; + actionsRegistry: ActionsService; } function isSupportedTemplate(entity: TemplateEntityV1beta3) { @@ -198,7 +202,7 @@ export async function createRouter( config, database, catalog, - actions, + actions = [], scheduler, additionalTemplateFilters, additionalTemplateGlobals, @@ -210,6 +214,7 @@ export async function createRouter( auth, httpAuth, auditor, + actionsRegistry, } = options; const concurrentTasksLimit = @@ -301,7 +306,44 @@ export async function createRouter( workers.push(worker); } - actions?.forEach(action => actionRegistry.register(action)); + // TODO(blam): it's a little unfortunate that you have to restart the scaffolder + // backend in order to pick these up. We should really just make `ActionsRegistry.get()` async + // and then we can move this logic into the there instead. + const { actions: distributedActions } = await actionsRegistry.list({ + credentials: await auth.getOwnServiceCredentials(), + }); + + for (const action of actions) { + actionRegistry.register(action); + } + + for (const action of distributedActions) { + actionRegistry.register({ + id: action.id, + description: action.description, + examples: [], + supportsDryRun: + action.attributes?.readOnly === true && + action.attributes?.destructive === false, + handler: async ctx => { + const { output } = await actionsRegistry.invoke({ + id: action.id, + input: ctx.input, + credentials: await ctx.getInitiatorCredentials(), + }); + + if (isObject(output)) { + for (const [key, value] of Object.entries(output)) { + ctx.output(key as keyof typeof output, value); + } + } + }, + schema: { + input: action.schema.input as Schema, + output: action.schema.output as Schema, + }, + }); + } const launchWorkers = () => workers.forEach(worker => worker.start()); From e0db9b8534c968daa98d2c8fbb9bfa028545e263 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Thu, 11 Sep 2025 19:15:19 +0200 Subject: [PATCH 50/71] fix(cli): identify custom patterns that extend the default pattern If a custom pattern is used, `backstage.json` will not be updated. However, if the custom pattern extends the default pattern, `backstage.json` will be updated, too. Example: - `@backstage/*` (default) - `@{backstage,backstage-community}/*` - `@{extra1,backstage,extra2}/*` Fixes: #28839 Signed-off-by: Patrick Jungermann --- .changeset/fast-corners-roll.md | 11 +++++++++++ .../modules/migrate/commands/versions/bump.test.ts | 3 ++- .../src/modules/migrate/commands/versions/bump.ts | 13 +++++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 .changeset/fast-corners-roll.md diff --git a/.changeset/fast-corners-roll.md b/.changeset/fast-corners-roll.md new file mode 100644 index 0000000000..f883774aef --- /dev/null +++ b/.changeset/fast-corners-roll.md @@ -0,0 +1,11 @@ +--- +'@backstage/cli': patch +--- + +Modify the `backstage.json` also for custom patterns if it extends the default pattern. + +Examples: + +- `@backstage/*` (default pattern) +- `@{backstage,backstage-community}/*` +- `@{extra1,backstage,extra2}/*` diff --git a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts index a309ddb192..1fb28d0289 100644 --- a/packages/cli/src/modules/migrate/commands/versions/bump.test.ts +++ b/packages/cli/src/modules/migrate/commands/versions/bump.test.ts @@ -772,6 +772,7 @@ describe('bump', () => { res( ctx.status(200), ctx.json({ + releaseVersion: '1.0.0', packages: [], }), ), @@ -797,7 +798,7 @@ describe('bump', () => { 'bumping @backstage-extra/custom in b to ^1.1.0', 'bumping @backstage-extra/custom-two in b to ^2.0.0', 'bumping @backstage/theme in b to ^2.0.0', - 'Skipping backstage.json update as custom pattern is used', + 'Your project is now at version 1.0.0, which has been written to backstage.json', 'Running yarn install to install new versions', 'Checking for moved packages to the @backstage-community namespace...', '⚠️ The following packages may have breaking changes:', diff --git a/packages/cli/src/modules/migrate/commands/versions/bump.ts b/packages/cli/src/modules/migrate/commands/versions/bump.ts index 23202ee1b5..391ccf8f22 100644 --- a/packages/cli/src/modules/migrate/commands/versions/bump.ts +++ b/packages/cli/src/modules/migrate/commands/versions/bump.ts @@ -18,6 +18,7 @@ maybeBootstrapProxy(); import fs from 'fs-extra'; import chalk from 'chalk'; +import { minimatch } from 'minimatch'; import semver from 'semver'; import { OptionValues } from 'commander'; import yaml from 'yaml'; @@ -78,6 +79,14 @@ type PkgVersionInfo = { location: string; }; +function extendsDefaultPattern(pattern: string): boolean { + if (!pattern.endsWith('/*')) { + return false; + } + + return minimatch('@backstage/', pattern.slice(0, -1)); +} + export default async (opts: OptionValues) => { const lockfilePath = paths.resolveTargetRoot('yarn.lock'); const lockfile = await Lockfile.load(lockfilePath); @@ -245,8 +254,8 @@ export default async (opts: OptionValues) => { console.log(); - // Do not update backstage.json when upgrade patterns are used. - if (pattern === DEFAULT_PATTERN_GLOB) { + // Do not update backstage.json when default pattern is not covered + if (extendsDefaultPattern(pattern)) { await bumpBackstageJsonVersion( releaseManifest.releaseVersion, hasYarnPlugin, From a3edb2eb408793779e71e105d9d7439a0df6364f Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 12 Sep 2025 08:59:59 +0200 Subject: [PATCH 51/71] chore: added comment] Signed-off-by: benjdlambert --- plugins/scaffolder-backend/src/service/router.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 2c9df0c8c4..a48bcb46b0 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -309,6 +309,9 @@ export async function createRouter( // TODO(blam): it's a little unfortunate that you have to restart the scaffolder // backend in order to pick these up. We should really just make `ActionsRegistry.get()` async // and then we can move this logic into the there instead. + // But we can't make those changes until next major. + // Alternatively, we could look at setting up a periodic task that refreshes the actions registry, but + // not feeling that it's worth the complexity. const { actions: distributedActions } = await actionsRegistry.list({ credentials: await auth.getOwnServiceCredentials(), }); From 53e19ab6d84851f954e9fd4092c82900986d68c1 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 12 Sep 2025 09:10:58 +0200 Subject: [PATCH 52/71] chore: added tests Signed-off-by: benjdlambert --- .../src/service/router.test.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 4144c552fb..60fcce0739 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -68,6 +68,8 @@ import { import { createDefaultFilters } from '../lib/templating/filters/createDefaultFilters'; import { createRouter } from './router'; import { DatabaseTaskStore } from '../scaffolder/tasks/DatabaseTaskStore'; +import { actionsRegistryServiceMock } from '@backstage/backend-test-utils/alpha'; +import { ActionsService } from '@backstage/backend-plugin-api/alpha'; function createDatabase(): DatabaseService { return DatabaseManager.fromConfig( @@ -178,6 +180,7 @@ const createTestRouter = async ( | Record | CreatedTemplateGlobal[]; autocompleteHandlers?: Record; + actionsRegistry?: ActionsService; } = {}, ) => { const logger = mockServices.logger.mock({ @@ -246,6 +249,7 @@ const createTestRouter = async ( }), createDebugLogAction(), ], + actionsRegistry: overrides.actionsRegistry ?? actionsRegistryServiceMock(), }); router.use(mockErrorHandler()); @@ -275,6 +279,49 @@ describe('scaffolder router', () => { expect(response.body[0].id).toBeDefined(); expect(response.body.length).toBe(2); }); + + it('should include actiosn from the remote actions registry', async () => { + const mockActionsRegistry = actionsRegistryServiceMock(); + mockActionsRegistry.register({ + name: 'my-demo-action', + title: 'Test', + description: 'Test', + schema: { + input: z => z.object({ name: z.string() }), + output: z => z.object({ name: z.string() }), + }, + action: async () => ({ output: { name: 'test' } }), + }); + const { router } = await createTestRouter({ + actionsRegistry: mockActionsRegistry, + }); + const response = await request(router).get('/v2/actions').send(); + + expect(response.status).toEqual(200); + expect(response.body.length).toBe(3); + + expect(response.body).toContainEqual({ + description: 'Test', + examples: [], + id: 'test:my-demo-action', + schema: { + input: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { name: { type: 'string' } }, + required: ['name'], + type: 'object', + }, + output: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { name: { type: 'string' } }, + required: ['name'], + type: 'object', + }, + }, + }); + }); }); describe('GET /v2/templating-extensions', () => { From a57185fd484bd563f1db6887d9807222231dfa64 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 12 Sep 2025 09:11:40 +0200 Subject: [PATCH 53/71] chore: changeset Signed-off-by: benjdlambert --- .changeset/bumpy-eyes-divide.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/bumpy-eyes-divide.md diff --git a/.changeset/bumpy-eyes-divide.md b/.changeset/bumpy-eyes-divide.md new file mode 100644 index 0000000000..6300f7aaf6 --- /dev/null +++ b/.changeset/bumpy-eyes-divide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Added support for executing actions from the `ActionsRegistry` in the `scaffolder-backend` From ca0aeea3427591f3f76161b5b2c8d52b791a5501 Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 12 Sep 2025 10:23:20 +0200 Subject: [PATCH 54/71] chore: code-review comments Signed-off-by: benjdlambert --- plugins/scaffolder-backend/src/service/router.test.ts | 2 +- plugins/scaffolder-backend/src/service/router.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 60fcce0739..16a6000224 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -280,7 +280,7 @@ describe('scaffolder router', () => { expect(response.body.length).toBe(2); }); - it('should include actiosn from the remote actions registry', async () => { + it('should include actions from the remote actions registry', async () => { const mockActionsRegistry = actionsRegistryServiceMock(); mockActionsRegistry.register({ name: 'my-demo-action', diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index a48bcb46b0..2a0792346d 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -133,7 +133,7 @@ import { import { TaskFilters } from '@backstage/plugin-scaffolder-node'; import { ActionsService } from '@backstage/backend-plugin-api/alpha'; -import { isObject } from 'lodash'; +import { isPlainObject } from 'lodash'; /** * RouterOptions @@ -335,8 +335,8 @@ export async function createRouter( credentials: await ctx.getInitiatorCredentials(), }); - if (isObject(output)) { - for (const [key, value] of Object.entries(output)) { + if (isPlainObject(output)) { + for (const [key, value] of Object.entries(output as JsonObject)) { ctx.output(key as keyof typeof output, value); } } From 31ef3f023cf2ab90d7ba5e02ec0987cb864604bb Mon Sep 17 00:00:00 2001 From: chonpisitkong01 <112919164+chonpisitkong01@users.noreply.github.com> Date: Fri, 12 Sep 2025 17:30:14 +0700 Subject: [PATCH 55/71] fix: correct import path for catalogPermissionRules.ts Update incorrect file path reference in catalogPermissionRules.ts import statement. Signed-off-by: chonpisitkong01 <112919164+chonpisitkong01@users.noreply.github.com> --- docs/permissions/custom-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index 61b9a5326a..dd11f4d773 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -184,7 +184,7 @@ To install custom rules in a plugin, we need to use the [`PermissionsRegistrySer import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ); /* highlight-add-next-line */ - backend.add(import('./extensions/catalogPermissionRules')); + backend.add(import('./modules/catalogPermissionRules.ts')); ``` 5. Now when you run you Backstage instance - `yarn start` - the rule will be added to the catalog plugin. From 99bd91dcf72a2ea2a42bcee6e070915a9b2fcbf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 12 Sep 2025 14:10:19 +0200 Subject: [PATCH 56/71] Update docs/permissions/custom-rules.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/permissions/custom-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index dd11f4d773..7bfcd1220a 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -184,7 +184,7 @@ To install custom rules in a plugin, we need to use the [`PermissionsRegistrySer import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), ); /* highlight-add-next-line */ - backend.add(import('./modules/catalogPermissionRules.ts')); + backend.add(import('./modules/catalogPermissionRules')); ``` 5. Now when you run you Backstage instance - `yarn start` - the rule will be added to the catalog plugin. From 0fc9b1c71f4ebd234431a31cf9fec06000114965 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 12 Sep 2025 14:15:45 +0200 Subject: [PATCH 57/71] create-app: remove redundant lock seed entries Signed-off-by: Patrik Oldsberg --- packages/create-app/seed-yarn.lock | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/packages/create-app/seed-yarn.lock b/packages/create-app/seed-yarn.lock index 21b2aa543d..00a86105cc 100644 --- a/packages/create-app/seed-yarn.lock +++ b/packages/create-app/seed-yarn.lock @@ -16,28 +16,3 @@ // package: the name of the package, e.g. @testing-library/react // query: the version query to pin the version for, e.g. ^14.0.0 // version: the version to pin to, must be in range of the query, e.g. 14.11.0 - -"@google-cloud/storage@^7.0.0": - version "7.14.0" - resolved "https://registry.yarnpkg.com/@google-cloud/storage/-/storage-7.14.0.tgz#eda9715f68507949214af804c906eba6d168a214" - integrity sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ== - -"@octokit/types@npm:^13.0.0": - version "13.6.2" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.2.tgz#e10fc4d2bdd65d836d1ced223b03ad4cfdb525bd" - integrity sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA== - -"@octokit/types@npm:^13.1.0": - version "13.6.2" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.2.tgz#e10fc4d2bdd65d836d1ced223b03ad4cfdb525bd" - integrity sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA== - -"@octokit/types@npm:^13.5.0": - version "13.6.2" - resolved "https://registry.yarnpkg.com/@octokit/types/-/types-13.6.2.tgz#e10fc4d2bdd65d836d1ced223b03ad4cfdb525bd" - integrity sha512-WpbZfZUcZU77DrSW4wbsSgTPfKcp286q3ItaIgvSbBpZJlu6mnYXAkjZz6LVZPXkEvLIM8McanyZejKTYUHipA== - -"@octokit/openapi-types@^22.2.0": - version "22.2.0" - resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-22.2.0.tgz#75aa7dcd440821d99def6a60b5f014207ae4968e" - integrity sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg== From c68cb321aeaab40d83e0a0672c5d6f7b548b13ce Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Fri, 12 Sep 2025 15:06:25 +0200 Subject: [PATCH 58/71] chore: exit pre-release Signed-off-by: benjdlambert --- .changeset/pre.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index b1a840849d..174818edf4 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -1,5 +1,5 @@ { - "mode": "pre", + "mode": "exit", "tag": "next", "initialVersions": { "example-app": "0.2.112", From 7151260d275694c08e689c81366ef76457314a05 Mon Sep 17 00:00:00 2001 From: Alec Jacobs Date: Fri, 12 Sep 2025 14:20:12 -0700 Subject: [PATCH 59/71] fix(scaffolder): prevent MultiEntityPicker from removing existing options from form data Signed-off-by: Alec Jacobs --- .changeset/two-coins-stop.md | 5 ++ .../MultiEntityPicker.test.tsx | 65 +++++++++++++++++++ .../MultiEntityPicker/MultiEntityPicker.tsx | 47 +++++++++----- 3 files changed, 99 insertions(+), 18 deletions(-) create mode 100644 .changeset/two-coins-stop.md diff --git a/.changeset/two-coins-stop.md b/.changeset/two-coins-stop.md new file mode 100644 index 0000000000..005ea81121 --- /dev/null +++ b/.changeset/two-coins-stop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Prevent the MultiEntityPicker from removing options present in form state when new options are selected diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx index a02d793a34..fc824f20f9 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.test.tsx @@ -262,6 +262,71 @@ describe('', () => { }); }); + describe('with existing form data', () => { + beforeEach(() => { + uiSchema = { 'ui:options': {} }; + props = { + onChange, + schema, + required, + uiSchema, + rawErrors, + formData: ['group:default/team-a'], + } as unknown as FieldProps; + }); + + it('preserves existing data on blur', async () => { + const { getByRole } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'squ' } }); + fireEvent.blur(input); + + expect(onChange).toHaveBeenCalledWith(['group:default/team-a', 'squ']); + }); + + it('preserves existing data on value create', async () => { + const { getByRole } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'squ' } }); + fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' }); + + expect(onChange).toHaveBeenCalledWith(['group:default/team-a', 'squ']); + }); + + it('preserves existing data on selecting an existing option', async () => { + catalogApi.getEntities.mockResolvedValue({ items: entities }); + + const { getByRole } = await renderInTestApp( + + + , + ); + + const input = getByRole('textbox'); + + fireEvent.mouseDown(input); + const optionA = screen.getByText('squad-b'); + await userEvent.click(optionA as HTMLElement); + + expect(onChange).toHaveBeenCalledWith([ + 'group:default/team-a', + 'group:default/squad-b', + ]); + }); + }); + describe('uses full entity ref', () => { beforeEach(() => { uiSchema = { diff --git a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx index 57c4b87d6d..39130c4b78 100644 --- a/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/MultiEntityPicker/MultiEntityPicker.tsx @@ -50,6 +50,12 @@ import { scaffolderTranslationRef } from '../../../translation'; export { MultiEntityPickerSchema } from './schema'; +// AutocompleteChangeReason events that can be triggered when a user inputs a freeSolo option +const FREE_SOLO_EVENTS: readonly AutocompleteChangeReason[] = [ + 'blur', + 'create-option', +]; + /** * The underlying component that is rendered in the form for the `MultiEntityPicker` * field extension. @@ -110,29 +116,34 @@ export const MultiEntityPicker = (props: MultiEntityPickerProps) => { (_: any, refs: (string | Entity)[], reason: AutocompleteChangeReason) => { const values = refs .map(ref => { + // If the ref is not a string, then it was a selected option in the picker if (typeof ref !== 'string') { // if ref does not exist: pass 'undefined' to trigger validation for required value return ref ? stringifyEntityRef(ref as Entity) : undefined; } - if (reason === 'blur' || reason === 'create-option') { - // Add in default namespace, etc. - let entityRef = ref; - try { - // Attempt to parse the entity ref into it's full form. - entityRef = stringifyEntityRef( - parseEntityRef(ref as string, { - defaultKind, - defaultNamespace, - }), - ); - } catch (err) { - // If the passed in value isn't an entity ref, do nothing. - } - // We need to check against formData here as that's the previous value for this field. - if (formData?.includes(ref) || allowArbitraryValues) { - return entityRef; - } + // Add in default namespace, etc. + let entityRef = ref; + try { + // Attempt to parse the entity ref into it's full form. + entityRef = stringifyEntityRef( + parseEntityRef(ref as string, { + defaultKind, + defaultNamespace, + }), + ); + } catch (err) { + // If the passed in value isn't an entity ref, do nothing. + } + + // We need to check against formData here as that's the previous value for this field. + if ( + // If value already matches what exists in form data, allow it + formData?.includes(ref) || + // If arbitrary values are allowed and the reason is a free solo event, allow it + (allowArbitraryValues && FREE_SOLO_EVENTS.includes(reason)) + ) { + return entityRef; } return undefined; From bdcc589842ecdaa29bfe35c0160f10f56c9fe1dc Mon Sep 17 00:00:00 2001 From: Bohdan Liashenko Date: Mon, 15 Sep 2025 11:43:17 +0200 Subject: [PATCH 60/71] Use contenthash name param for webpack caching Signed-off-by: Bohdan Liashenko --- packages/cli/src/modules/build/lib/bundler/config.ts | 4 ++-- packages/cli/src/modules/build/lib/bundler/optimization.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cli/src/modules/build/lib/bundler/config.ts b/packages/cli/src/modules/build/lib/bundler/config.ts index bf78d04b4a..9031ce09d2 100644 --- a/packages/cli/src/modules/build/lib/bundler/config.ts +++ b/packages/cli/src/modules/build/lib/bundler/config.ts @@ -430,10 +430,10 @@ export async function createConfig( path: paths.targetDist, publicPath: options.moduleFederation?.mode === 'remote' ? 'auto' : `${publicPath}/`, - filename: isDev ? '[name].js' : 'static/[name].[fullhash:8].js', + filename: isDev ? '[name].js' : 'static/[name].[contenthash:8].js', chunkFilename: isDev ? '[name].chunk.js' - : 'static/[name].[chunkhash:8].chunk.js', + : 'static/[name].[contenthash:8].chunk.js', ...(isDev ? { devtoolModuleFilenameTemplate: (info: any) => diff --git a/packages/cli/src/modules/build/lib/bundler/optimization.ts b/packages/cli/src/modules/build/lib/bundler/optimization.ts index 5d763ea4ac..63319ebec6 100644 --- a/packages/cli/src/modules/build/lib/bundler/optimization.ts +++ b/packages/cli/src/modules/build/lib/bundler/optimization.ts @@ -72,7 +72,7 @@ export const optimization = ( }, filename: isDev ? 'module-[name].js' - : 'static/module-[name].[chunkhash:8].js', + : 'static/module-[name].[contenthash:8].js', priority: 10, minSize: 100000, minChunks: 1, From e6f45dc34ca844547dd8f5b617ed70c4ca12719d Mon Sep 17 00:00:00 2001 From: Bohdan Liashenko Date: Mon, 15 Sep 2025 11:51:20 +0200 Subject: [PATCH 61/71] Add changeset log Signed-off-by: Bohdan Liashenko --- .changeset/all-bats-shine.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/all-bats-shine.md diff --git a/.changeset/all-bats-shine.md b/.changeset/all-bats-shine.md new file mode 100644 index 0000000000..7d9af288c3 --- /dev/null +++ b/.changeset/all-bats-shine.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Change webpack caching config param to `contenthash` for cli bundler. This fixes an issue with webpack sometimes generating same hash name output files even though the content of a file was different. See more at [https://webpack.js.org/guides/caching/](https://webpack.js.org/guides/caching/). From 0efcc977872dae7e2b4242af3d00792ea0e3bc5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 15 Sep 2025 12:07:50 +0200 Subject: [PATCH 62/71] run yarn generate on all packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/large-otters-invent.md | 9 + .../openapi/generated/apis/Api.client.ts | 451 +----------------- .../schema/openapi/generated/apis/index.ts | 2 +- .../src/schema/openapi/generated/index.ts | 2 +- .../AnalyzeLocationEntityField.model.ts | 2 +- .../AnalyzeLocationExistingEntity.model.ts | 2 +- .../AnalyzeLocationGenerateEntity.model.ts | 2 +- .../models/AnalyzeLocationRequest.model.ts | 2 +- .../models/AnalyzeLocationResponse.model.ts | 2 +- .../models/CreateLocation201Response.model.ts | 2 +- .../models/CreateLocationRequest.model.ts | 2 +- .../models/EntitiesBatchResponse.model.ts | 2 +- .../models/EntitiesQueryResponse.model.ts | 2 +- .../EntitiesQueryResponsePageInfo.model.ts | 2 +- .../openapi/generated/models/Entity.model.ts | 2 +- .../models/EntityAncestryResponse.model.ts | 2 +- .../EntityAncestryResponseItemsInner.model.ts | 2 +- .../generated/models/EntityFacet.model.ts | 2 +- .../models/EntityFacetsResponse.model.ts | 2 +- .../generated/models/EntityLink.model.ts | 2 +- .../generated/models/EntityMeta.model.ts | 2 +- .../generated/models/EntityRelation.model.ts | 2 +- .../generated/models/ErrorError.model.ts | 2 +- .../generated/models/ErrorRequest.model.ts | 2 +- .../generated/models/ErrorResponse.model.ts | 2 +- .../models/GetEntitiesByRefsRequest.model.ts | 2 +- .../GetLocations200ResponseInner.model.ts | 2 +- .../generated/models/Location.model.ts | 2 +- .../generated/models/LocationInput.model.ts | 2 +- .../generated/models/LocationSpec.model.ts | 2 +- .../generated/models/ModelError.model.ts | 2 +- .../generated/models/NullableEntity.model.ts | 2 +- .../models/RecursivePartialEntity.model.ts | 2 +- .../RecursivePartialEntityMeta.model.ts | 2 +- .../RecursivePartialEntityMetaAllOf.model.ts | 2 +- .../RecursivePartialEntityRelation.model.ts | 2 +- .../models/RefreshEntityRequest.model.ts | 2 +- .../models/ValidateEntity400Response.model.ts | 2 +- ...idateEntity400ResponseErrorsInner.model.ts | 2 +- .../models/ValidateEntityRequest.model.ts | 2 +- .../schema/openapi/generated/models/index.ts | 2 +- .../src/schema/openapi/generated/pluginId.ts | 2 +- .../src/schema/openapi/index.ts | 2 +- .../openapi/generated/apis/Api.server.ts | 2 +- .../schema/openapi/generated/apis/index.ts | 2 +- .../src/schema/openapi/generated/index.ts | 2 +- .../AnalyzeLocationEntityField.model.ts | 2 +- .../AnalyzeLocationExistingEntity.model.ts | 2 +- .../AnalyzeLocationGenerateEntity.model.ts | 2 +- .../models/AnalyzeLocationRequest.model.ts | 2 +- .../models/AnalyzeLocationResponse.model.ts | 2 +- .../models/CreateLocation201Response.model.ts | 2 +- .../models/CreateLocationRequest.model.ts | 2 +- .../models/EntitiesBatchResponse.model.ts | 2 +- .../models/EntitiesQueryResponse.model.ts | 2 +- .../EntitiesQueryResponsePageInfo.model.ts | 2 +- .../openapi/generated/models/Entity.model.ts | 2 +- .../models/EntityAncestryResponse.model.ts | 2 +- .../EntityAncestryResponseItemsInner.model.ts | 2 +- .../generated/models/EntityFacet.model.ts | 2 +- .../models/EntityFacetsResponse.model.ts | 2 +- .../generated/models/EntityLink.model.ts | 2 +- .../generated/models/EntityMeta.model.ts | 2 +- .../generated/models/EntityRelation.model.ts | 2 +- .../generated/models/ErrorError.model.ts | 2 +- .../generated/models/ErrorRequest.model.ts | 2 +- .../generated/models/ErrorResponse.model.ts | 2 +- .../models/GetEntitiesByRefsRequest.model.ts | 2 +- .../GetLocations200ResponseInner.model.ts | 2 +- .../generated/models/Location.model.ts | 2 +- .../generated/models/LocationInput.model.ts | 2 +- .../generated/models/LocationSpec.model.ts | 2 +- .../generated/models/ModelError.model.ts | 2 +- .../generated/models/NullableEntity.model.ts | 2 +- .../models/RecursivePartialEntity.model.ts | 2 +- .../RecursivePartialEntityMeta.model.ts | 2 +- .../RecursivePartialEntityMetaAllOf.model.ts | 2 +- .../RecursivePartialEntityRelation.model.ts | 2 +- .../models/RefreshEntityRequest.model.ts | 2 +- .../models/ValidateEntity400Response.model.ts | 2 +- ...idateEntity400ResponseErrorsInner.model.ts | 2 +- .../models/ValidateEntityRequest.model.ts | 2 +- .../schema/openapi/generated/models/index.ts | 2 +- .../src/schema/openapi/generated/router.ts | 4 +- .../src/schema/openapi/index.ts | 2 +- .../openapi/generated/apis/Api.server.ts | 2 +- .../schema/openapi/generated/apis/index.ts | 2 +- .../src/schema/openapi/generated/index.ts | 2 +- .../generated/models/ErrorError.model.ts | 2 +- .../generated/models/ErrorRequest.model.ts | 2 +- .../generated/models/ErrorResponse.model.ts | 2 +- .../openapi/generated/models/Event.model.ts | 2 +- .../GetSubscriptionEvents200Response.model.ts | 2 +- .../generated/models/ModelError.model.ts | 2 +- .../models/PostEventRequest.model.ts | 2 +- .../models/PutSubscriptionRequest.model.ts | 2 +- .../schema/openapi/generated/models/index.ts | 2 +- .../src/schema/openapi/generated/router.ts | 2 +- .../src/schema/openapi/index.ts | 2 +- .../openapi/generated/apis/Api.client.ts | 2 +- .../schema/openapi/generated/apis/index.ts | 2 +- .../src/schema/openapi/generated/index.ts | 2 +- .../generated/models/ErrorError.model.ts | 2 +- .../generated/models/ErrorRequest.model.ts | 2 +- .../generated/models/ErrorResponse.model.ts | 2 +- .../openapi/generated/models/Event.model.ts | 2 +- .../GetSubscriptionEvents200Response.model.ts | 2 +- .../generated/models/ModelError.model.ts | 2 +- .../models/PostEventRequest.model.ts | 2 +- .../models/PutSubscriptionRequest.model.ts | 2 +- .../schema/openapi/generated/models/index.ts | 2 +- .../src/schema/openapi/generated/pluginId.ts | 2 +- .../events-node/src/schema/openapi/index.ts | 2 +- .../openapi/generated/apis/Api.server.ts | 2 +- .../schema/openapi/generated/apis/index.ts | 2 +- .../src/schema/openapi/generated/index.ts | 2 +- .../generated/models/ErrorError.model.ts | 2 +- .../generated/models/ErrorRequest.model.ts | 2 +- .../generated/models/ErrorResponse.model.ts | 2 +- .../generated/models/ModelError.model.ts | 2 +- .../models/Query200Response.model.ts | 2 +- .../Query200ResponseResultsInner.model.ts | 2 +- ...ry200ResponseResultsInnerDocument.model.ts | 2 +- .../schema/openapi/generated/models/index.ts | 2 +- .../src/schema/openapi/index.ts | 2 +- 125 files changed, 159 insertions(+), 549 deletions(-) create mode 100644 .changeset/large-otters-invent.md diff --git a/.changeset/large-otters-invent.md b/.changeset/large-otters-invent.md new file mode 100644 index 0000000000..2efaf853a3 --- /dev/null +++ b/.changeset/large-otters-invent.md @@ -0,0 +1,9 @@ +--- +'@backstage/catalog-client': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-events-backend': patch +'@backstage/plugin-search-backend': patch +'@backstage/plugin-events-node': patch +--- + +Updated generated schemas diff --git a/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts b/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts index be1fa78117..a2a751ae2f 100644 --- a/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts +++ b/packages/catalog-client/src/schema/openapi/generated/apis/Api.client.ts @@ -236,115 +236,14 @@ export class DefaultApiClient { } /** - * Get all entities matching a given filter. - * @param fields - By default the full entities are returned, but you can pass in a `fields` query -parameter which selects what parts of the entity data to retain. This makes the -response smaller and faster to transfer, and may allow the catalog to perform -more efficient queries. - -The query parameter value is a comma separated list of simplified JSON paths -like above. Each path corresponds to the key of either a value, or of a subtree -root that you want to keep in the output. The rest is pruned away. For example, -specifying `?fields=metadata.name,metadata.annotations,spec` retains only the -`name` and `annotations` fields of the `metadata` of each entity (it'll be an -object with at most two keys), keeps the entire `spec` unchanged, and cuts out -all other roots such as `relations`. - -Some more real world usable examples: - -- Return only enough data to form the full ref of each entity: - - `/entities/by-query?fields=kind,metadata.namespace,metadata.name` - - * @param limit - Number of records to return in the response. - * @param filter - You can pass in one or more filter sets that get matched against each entity. -Each filter set is a number of conditions that all have to match for the -condition to be true (conditions effectively have an AND between them). At least -one filter set has to be true for the entity to be part of the result set -(filter sets effectively have an OR between them). - -Example: - -```text -/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type - - Return entities that match - - Filter set 1: - Condition 1: kind = user - AND - Condition 2: metadata.namespace = default - - OR - - Filter set 2: - Condition 1: kind = group - AND - Condition 2: spec.type exists -``` - -Each condition is either on the form `<key>`, or on the form `<key>=<value>`. -The first form asserts on the existence of a certain key (with any value), and -the second asserts that the key exists and has a certain value. All checks are -always case _insensitive_. - -In all cases, the key is a simplified JSON path in a given piece of entity data. -Each part of the path is a key of an object, and the traversal also descends -through arrays. There are two special forms: - -- Array items that are simple value types (such as strings) match on a key-value - pair where the key is the item as a string, and the value is the string `true` -- Relations can be matched on a `relations.<type>=<targetRef>` form - -Let's look at a simplified example to illustrate the concept: - -```json -{ - "a": { - "b": ["c", { "d": 1 }], - "e": 7 - } -} -``` - -This would match any one of the following conditions: - -- `a` -- `a.b` -- `a.b.c` -- `a.b.c=true` -- `a.b.d` -- `a.b.d=1` -- `a.e` -- `a.e=7` - -Some more real world usable examples: - -- Return all orphaned entities: - - `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - -- Return all users and groups: - - `/entities/by-query?filter=kind=user&filter=kind=group` - -- Return all service components: - - `/entities/by-query?filter=kind=component,spec.type=service` - -- Return all entities with the `java` tag: - - `/entities/by-query?filter=metadata.tags.java` - -- Return all users who are members of the `ops` group (note that the full - [reference](references.md) of the group is used): - - `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops` - - * @param offset - Number of records to skip in the query page. - * @param after - Pointer to the previous page of results. - * @param order - - */ + * Get all entities matching a given filter. + * @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it'll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name` + * @param limit - Number of records to return in the response. + * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops` + * @param offset - Number of records to skip in the query page. + * @param after - Pointer to the previous page of results. + * @param order - + */ public async getEntities( // @ts-ignore request: GetEntities, @@ -368,148 +267,16 @@ Some more real world usable examples: } /** - * Search for entities by a given query. - * @param fields - By default the full entities are returned, but you can pass in a `fields` query -parameter which selects what parts of the entity data to retain. This makes the -response smaller and faster to transfer, and may allow the catalog to perform -more efficient queries. - -The query parameter value is a comma separated list of simplified JSON paths -like above. Each path corresponds to the key of either a value, or of a subtree -root that you want to keep in the output. The rest is pruned away. For example, -specifying `?fields=metadata.name,metadata.annotations,spec` retains only the -`name` and `annotations` fields of the `metadata` of each entity (it'll be an -object with at most two keys), keeps the entire `spec` unchanged, and cuts out -all other roots such as `relations`. - -Some more real world usable examples: - -- Return only enough data to form the full ref of each entity: - - `/entities/by-query?fields=kind,metadata.namespace,metadata.name` - - * @param limit - Number of records to return in the response. - * @param offset - Number of records to skip in the query page. - * @param orderField - By default the entities are returned ordered by their internal uid. You can -customize the `orderField` query parameters to affect that ordering. - -For example, to return entities by their name: - -`/entities/by-query?orderField=metadata.name,asc` - -Each parameter can be followed by `asc` for ascending lexicographical order or -`desc` for descending (reverse) lexicographical order. - - * @param cursor - You may pass the `cursor` query parameters to perform cursor based pagination -through the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property: - -```json - "pageInfo": { - "nextCursor": "a-cursor", - "prevCursor": "another-cursor" - } -``` - -If `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach, -if `prevCursor` exists, it can be used to retrieve the previous batch of entities. - -- [`filter`](#filtering), for selecting only a subset of all entities -- [`fields`](#field-selection), for selecting only parts of the full data - structure of each entity -- `limit` for limiting the number of entities returned (20 is the default) -- [`orderField`](#ordering), for deciding the order of the entities -- `fullTextFilter` - **NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that, - it isn't possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters, - as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration. - - * @param filter - You can pass in one or more filter sets that get matched against each entity. -Each filter set is a number of conditions that all have to match for the -condition to be true (conditions effectively have an AND between them). At least -one filter set has to be true for the entity to be part of the result set -(filter sets effectively have an OR between them). - -Example: - -```text -/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type - - Return entities that match - - Filter set 1: - Condition 1: kind = user - AND - Condition 2: metadata.namespace = default - - OR - - Filter set 2: - Condition 1: kind = group - AND - Condition 2: spec.type exists -``` - -Each condition is either on the form `<key>`, or on the form `<key>=<value>`. -The first form asserts on the existence of a certain key (with any value), and -the second asserts that the key exists and has a certain value. All checks are -always case _insensitive_. - -In all cases, the key is a simplified JSON path in a given piece of entity data. -Each part of the path is a key of an object, and the traversal also descends -through arrays. There are two special forms: - -- Array items that are simple value types (such as strings) match on a key-value - pair where the key is the item as a string, and the value is the string `true` -- Relations can be matched on a `relations.<type>=<targetRef>` form - -Let's look at a simplified example to illustrate the concept: - -```json -{ - "a": { - "b": ["c", { "d": 1 }], - "e": 7 - } -} -``` - -This would match any one of the following conditions: - -- `a` -- `a.b` -- `a.b.c` -- `a.b.c=true` -- `a.b.d` -- `a.b.d=1` -- `a.e` -- `a.e=7` - -Some more real world usable examples: - -- Return all orphaned entities: - - `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - -- Return all users and groups: - - `/entities/by-query?filter=kind=user&filter=kind=group` - -- Return all service components: - - `/entities/by-query?filter=kind=component,spec.type=service` - -- Return all entities with the `java` tag: - - `/entities/by-query?filter=metadata.tags.java` - -- Return all users who are members of the `ops` group (note that the full - [reference](references.md) of the group is used): - - `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops` - - * @param fullTextFilterTerm - Text search term. - * @param fullTextFilterFields - A comma separated list of fields to sort returned results by. - */ + * Search for entities by a given query. + * @param fields - By default the full entities are returned, but you can pass in a `fields` query parameter which selects what parts of the entity data to retain. This makes the response smaller and faster to transfer, and may allow the catalog to perform more efficient queries. The query parameter value is a comma separated list of simplified JSON paths like above. Each path corresponds to the key of either a value, or of a subtree root that you want to keep in the output. The rest is pruned away. For example, specifying `?fields=metadata.name,metadata.annotations,spec` retains only the `name` and `annotations` fields of the `metadata` of each entity (it'll be an object with at most two keys), keeps the entire `spec` unchanged, and cuts out all other roots such as `relations`. Some more real world usable examples: - Return only enough data to form the full ref of each entity: `/entities/by-query?fields=kind,metadata.namespace,metadata.name` + * @param limit - Number of records to return in the response. + * @param offset - Number of records to skip in the query page. + * @param orderField - By default the entities are returned ordered by their internal uid. You can customize the `orderField` query parameters to affect that ordering. For example, to return entities by their name: `/entities/by-query?orderField=metadata.name,asc` Each parameter can be followed by `asc` for ascending lexicographical order or `desc` for descending (reverse) lexicographical order. + * @param cursor - You may pass the `cursor` query parameters to perform cursor based pagination through the set of entities. The value of `cursor` will be returned in the response, under the `pageInfo` property: ```json \"pageInfo\": { \"nextCursor\": \"a-cursor\", \"prevCursor\": \"another-cursor\" } ``` If `nextCursor` exists, it can be used to retrieve the next batch of entities. Following the same approach, if `prevCursor` exists, it can be used to retrieve the previous batch of entities. - [`filter`](#filtering), for selecting only a subset of all entities - [`fields`](#field-selection), for selecting only parts of the full data structure of each entity - `limit` for limiting the number of entities returned (20 is the default) - [`orderField`](#ordering), for deciding the order of the entities - `fullTextFilter` **NOTE**: [`filter`, `orderField`, `fullTextFilter`] and `cursor` are mutually exclusive. This means that, it isn't possible to change any of [`filter`, `orderField`, `fullTextFilter`] when passing `cursor` as query parameters, as changing any of these properties will affect pagination. If any of `filter`, `orderField`, `fullTextFilter` is specified together with `cursor`, only the latter is taken into consideration. + * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops` + * @param fullTextFilterTerm - Text search term. + * @param fullTextFilterFields - A comma separated list of fields to sort returned results by. + */ public async getEntitiesByQuery( // @ts-ignore request: GetEntitiesByQuery, @@ -533,93 +300,10 @@ Some more real world usable examples: } /** - * Get a batch set of entities given an array of entityRefs. - * @param filter - You can pass in one or more filter sets that get matched against each entity. -Each filter set is a number of conditions that all have to match for the -condition to be true (conditions effectively have an AND between them). At least -one filter set has to be true for the entity to be part of the result set -(filter sets effectively have an OR between them). - -Example: - -```text -/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type - - Return entities that match - - Filter set 1: - Condition 1: kind = user - AND - Condition 2: metadata.namespace = default - - OR - - Filter set 2: - Condition 1: kind = group - AND - Condition 2: spec.type exists -``` - -Each condition is either on the form `<key>`, or on the form `<key>=<value>`. -The first form asserts on the existence of a certain key (with any value), and -the second asserts that the key exists and has a certain value. All checks are -always case _insensitive_. - -In all cases, the key is a simplified JSON path in a given piece of entity data. -Each part of the path is a key of an object, and the traversal also descends -through arrays. There are two special forms: - -- Array items that are simple value types (such as strings) match on a key-value - pair where the key is the item as a string, and the value is the string `true` -- Relations can be matched on a `relations.<type>=<targetRef>` form - -Let's look at a simplified example to illustrate the concept: - -```json -{ - "a": { - "b": ["c", { "d": 1 }], - "e": 7 - } -} -``` - -This would match any one of the following conditions: - -- `a` -- `a.b` -- `a.b.c` -- `a.b.c=true` -- `a.b.d` -- `a.b.d=1` -- `a.e` -- `a.e=7` - -Some more real world usable examples: - -- Return all orphaned entities: - - `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - -- Return all users and groups: - - `/entities/by-query?filter=kind=user&filter=kind=group` - -- Return all service components: - - `/entities/by-query?filter=kind=component,spec.type=service` - -- Return all entities with the `java` tag: - - `/entities/by-query?filter=metadata.tags.java` - -- Return all users who are members of the `ops` group (note that the full - [reference](references.md) of the group is used): - - `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops` - - * @param getEntitiesByRefsRequest - - */ + * Get a batch set of entities given an array of entityRefs. + * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops` + * @param getEntitiesByRefsRequest - + */ public async getEntitiesByRefs( // @ts-ignore request: GetEntitiesByRefs, @@ -730,93 +414,10 @@ Some more real world usable examples: } /** - * Get all entity facets that match the given filters. - * @param facet - - * @param filter - You can pass in one or more filter sets that get matched against each entity. -Each filter set is a number of conditions that all have to match for the -condition to be true (conditions effectively have an AND between them). At least -one filter set has to be true for the entity to be part of the result set -(filter sets effectively have an OR between them). - -Example: - -```text -/entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type - - Return entities that match - - Filter set 1: - Condition 1: kind = user - AND - Condition 2: metadata.namespace = default - - OR - - Filter set 2: - Condition 1: kind = group - AND - Condition 2: spec.type exists -``` - -Each condition is either on the form `<key>`, or on the form `<key>=<value>`. -The first form asserts on the existence of a certain key (with any value), and -the second asserts that the key exists and has a certain value. All checks are -always case _insensitive_. - -In all cases, the key is a simplified JSON path in a given piece of entity data. -Each part of the path is a key of an object, and the traversal also descends -through arrays. There are two special forms: - -- Array items that are simple value types (such as strings) match on a key-value - pair where the key is the item as a string, and the value is the string `true` -- Relations can be matched on a `relations.<type>=<targetRef>` form - -Let's look at a simplified example to illustrate the concept: - -```json -{ - "a": { - "b": ["c", { "d": 1 }], - "e": 7 - } -} -``` - -This would match any one of the following conditions: - -- `a` -- `a.b` -- `a.b.c` -- `a.b.c=true` -- `a.b.d` -- `a.b.d=1` -- `a.e` -- `a.e=7` - -Some more real world usable examples: - -- Return all orphaned entities: - - `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - -- Return all users and groups: - - `/entities/by-query?filter=kind=user&filter=kind=group` - -- Return all service components: - - `/entities/by-query?filter=kind=component,spec.type=service` - -- Return all entities with the `java` tag: - - `/entities/by-query?filter=metadata.tags.java` - -- Return all users who are members of the `ops` group (note that the full - [reference](references.md) of the group is used): - - `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops` - - */ + * Get all entity facets that match the given filters. + * @param facet - + * @param filter - You can pass in one or more filter sets that get matched against each entity. Each filter set is a number of conditions that all have to match for the condition to be true (conditions effectively have an AND between them). At least one filter set has to be true for the entity to be part of the result set (filter sets effectively have an OR between them). Example: ```text /entities/by-query?filter=kind=user,metadata.namespace=default&filter=kind=group,spec.type Return entities that match Filter set 1: Condition 1: kind = user AND Condition 2: metadata.namespace = default OR Filter set 2: Condition 1: kind = group AND Condition 2: spec.type exists ``` Each condition is either on the form `<key>`, or on the form `<key>=<value>`. The first form asserts on the existence of a certain key (with any value), and the second asserts that the key exists and has a certain value. All checks are always case _insensitive_. In all cases, the key is a simplified JSON path in a given piece of entity data. Each part of the path is a key of an object, and the traversal also descends through arrays. There are two special forms: - Array items that are simple value types (such as strings) match on a key-value pair where the key is the item as a string, and the value is the string `true` - Relations can be matched on a `relations.<type>=<targetRef>` form Let's look at a simplified example to illustrate the concept: ```json { \"a\": { \"b\": [\"c\", { \"d\": 1 }], \"e\": 7 } } ``` This would match any one of the following conditions: - `a` - `a.b` - `a.b.c` - `a.b.c=true` - `a.b.d` - `a.b.d=1` - `a.e` - `a.e=7` Some more real world usable examples: - Return all orphaned entities: `/entities/by-query?filter=metadata.annotations.backstage.io/orphan=true` - Return all users and groups: `/entities/by-query?filter=kind=user&filter=kind=group` - Return all service components: `/entities/by-query?filter=kind=component,spec.type=service` - Return all entities with the `java` tag: `/entities/by-query?filter=metadata.tags.java` - Return all users who are members of the `ops` group (note that the full [reference](references.md) of the group is used): `/entities/by-query?filter=kind=user,relations.memberof=group:default/ops` + */ public async getEntityFacets( // @ts-ignore request: GetEntityFacets, diff --git a/packages/catalog-client/src/schema/openapi/generated/apis/index.ts b/packages/catalog-client/src/schema/openapi/generated/apis/index.ts index af52f9db46..fc7c83b736 100644 --- a/packages/catalog-client/src/schema/openapi/generated/apis/index.ts +++ b/packages/catalog-client/src/schema/openapi/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/index.ts b/packages/catalog-client/src/schema/openapi/generated/index.ts index bb399e97a0..dc3055033d 100644 --- a/packages/catalog-client/src/schema/openapi/generated/index.ts +++ b/packages/catalog-client/src/schema/openapi/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationEntityField.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationEntityField.model.ts index ad2b5aecd1..65d2a2db65 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationEntityField.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationEntityField.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts index fb01ae8e8e..e7563a5c41 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts index e760d9cf94..dca7b943cc 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts index c1085a2a7b..b91f979678 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts index 66fc46bc0f..40d67804b7 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/CreateLocation201Response.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/CreateLocation201Response.model.ts index 27e557639a..75575079c4 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/CreateLocation201Response.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/CreateLocation201Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/CreateLocationRequest.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/CreateLocationRequest.model.ts index 5351ac289b..183aedc1f8 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/CreateLocationRequest.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/CreateLocationRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts index 1a962367d1..7b02ad53d9 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts index 11c42a6aee..ea67ecb2e5 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponsePageInfo.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponsePageInfo.model.ts index 07b4137c77..8e0fab6f68 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponsePageInfo.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntitiesQueryResponsePageInfo.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/Entity.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/Entity.model.ts index f386f3dd17..fed0b96399 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/Entity.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/Entity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts index a94f784f05..e48164ea7c 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts index 40594a1515..b82248b5e2 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityFacet.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityFacet.model.ts index f0f89f6eae..36551c0b66 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/EntityFacet.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityFacet.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts index e013f0b5a8..43b10e0965 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityLink.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityLink.model.ts index 53d7d1f9ff..3c406bb785 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/EntityLink.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityLink.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityMeta.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityMeta.model.ts index cc8dd555a6..ea2dedac5c 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/EntityMeta.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityMeta.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/EntityRelation.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/EntityRelation.model.ts index fe98a84481..79313ca658 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/EntityRelation.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/EntityRelation.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/ErrorError.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/ErrorError.model.ts index fe5811628d..e0265e95d7 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/ErrorError.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/ErrorRequest.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/ErrorRequest.model.ts index d44dcb66d9..3eb5e15740 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/ErrorRequest.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/ErrorResponse.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/ErrorResponse.model.ts index 91c120483d..edbcc32df7 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/ErrorResponse.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts index 9160f0823b..2308831357 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts index a9f1af1c53..d5f1f1260d 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/Location.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/Location.model.ts index d7e4a22de4..9c6e8fbc96 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/Location.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/Location.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/LocationInput.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/LocationInput.model.ts index 9cdc4c2c13..f86296a14b 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/LocationInput.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/LocationInput.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/LocationSpec.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/LocationSpec.model.ts index 1d56d8f3b0..9286b27d01 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/LocationSpec.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/LocationSpec.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/ModelError.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/ModelError.model.ts index 5526d703e6..958fde7d0b 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/ModelError.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/NullableEntity.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/NullableEntity.model.ts index aa6135ded4..fccc37650e 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/NullableEntity.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/NullableEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts index b3913ccd85..345adaedea 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts index df5ce3811b..da5d7dce1c 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMetaAllOf.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMetaAllOf.model.ts index 6598f8df96..fc2437efc5 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMetaAllOf.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityMetaAllOf.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityRelation.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityRelation.model.ts index 9c7dafb534..6c70398610 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityRelation.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/RecursivePartialEntityRelation.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/RefreshEntityRequest.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/RefreshEntityRequest.model.ts index 617f35288b..b26b7f1c45 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/RefreshEntityRequest.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/RefreshEntityRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts index fac9525afa..532b6fa9e3 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts index 8718d7938d..81cf98faac 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntityRequest.model.ts b/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntityRequest.model.ts index c3be04818d..c411697d94 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntityRequest.model.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/ValidateEntityRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/models/index.ts b/packages/catalog-client/src/schema/openapi/generated/models/index.ts index d6c81becb5..0e0d46df01 100644 --- a/packages/catalog-client/src/schema/openapi/generated/models/index.ts +++ b/packages/catalog-client/src/schema/openapi/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/generated/pluginId.ts b/packages/catalog-client/src/schema/openapi/generated/pluginId.ts index b45379f0b0..62afe18e6e 100644 --- a/packages/catalog-client/src/schema/openapi/generated/pluginId.ts +++ b/packages/catalog-client/src/schema/openapi/generated/pluginId.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/packages/catalog-client/src/schema/openapi/index.ts b/packages/catalog-client/src/schema/openapi/index.ts index db98243cbf..196aad553a 100644 --- a/packages/catalog-client/src/schema/openapi/index.ts +++ b/packages/catalog-client/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts b/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts index b758377e5f..c10518a08e 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/apis/Api.server.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/apis/index.ts b/plugins/catalog-backend/src/schema/openapi/generated/apis/index.ts index a3cdbbebd2..8d81cbaf39 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/apis/index.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/index.ts b/plugins/catalog-backend/src/schema/openapi/generated/index.ts index 69c39313c6..dec4b8804e 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/index.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationEntityField.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationEntityField.model.ts index ad2b5aecd1..65d2a2db65 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationEntityField.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationEntityField.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts index fb01ae8e8e..e7563a5c41 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationExistingEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts index e760d9cf94..dca7b943cc 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationGenerateEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts index c1085a2a7b..b91f979678 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts index 66fc46bc0f..40d67804b7 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/AnalyzeLocationResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/CreateLocation201Response.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/CreateLocation201Response.model.ts index 27e557639a..75575079c4 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/CreateLocation201Response.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/CreateLocation201Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/CreateLocationRequest.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/CreateLocationRequest.model.ts index 5351ac289b..183aedc1f8 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/CreateLocationRequest.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/CreateLocationRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts index 1a962367d1..7b02ad53d9 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesBatchResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts index 11c42a6aee..ea67ecb2e5 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesQueryResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesQueryResponsePageInfo.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesQueryResponsePageInfo.model.ts index 07b4137c77..8e0fab6f68 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesQueryResponsePageInfo.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntitiesQueryResponsePageInfo.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/Entity.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/Entity.model.ts index f386f3dd17..fed0b96399 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/Entity.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/Entity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts index a94f784f05..e48164ea7c 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityAncestryResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts index 40594a1515..b82248b5e2 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityAncestryResponseItemsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityFacet.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityFacet.model.ts index f0f89f6eae..36551c0b66 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityFacet.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityFacet.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts index e013f0b5a8..43b10e0965 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityFacetsResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityLink.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityLink.model.ts index 53d7d1f9ff..3c406bb785 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityLink.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityLink.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityMeta.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityMeta.model.ts index cc8dd555a6..ea2dedac5c 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityMeta.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityMeta.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityRelation.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityRelation.model.ts index fe98a84481..79313ca658 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/EntityRelation.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/EntityRelation.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorError.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorError.model.ts index fe5811628d..e0265e95d7 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorError.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts index d44dcb66d9..3eb5e15740 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts index 91c120483d..edbcc32df7 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts index 9160f0823b..2308831357 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/GetEntitiesByRefsRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts index a9f1af1c53..d5f1f1260d 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/GetLocations200ResponseInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/Location.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/Location.model.ts index d7e4a22de4..9c6e8fbc96 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/Location.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/Location.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/LocationInput.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/LocationInput.model.ts index 9cdc4c2c13..f86296a14b 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/LocationInput.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/LocationInput.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/LocationSpec.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/LocationSpec.model.ts index 1d56d8f3b0..9286b27d01 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/LocationSpec.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/LocationSpec.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/ModelError.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/ModelError.model.ts index 5526d703e6..958fde7d0b 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/ModelError.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/NullableEntity.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/NullableEntity.model.ts index aa6135ded4..fccc37650e 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/NullableEntity.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/NullableEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts index b3913ccd85..345adaedea 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntity.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts index df5ce3811b..da5d7dce1c 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityMeta.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityMetaAllOf.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityMetaAllOf.model.ts index 6598f8df96..fc2437efc5 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityMetaAllOf.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityMetaAllOf.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityRelation.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityRelation.model.ts index 9c7dafb534..6c70398610 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityRelation.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/RecursivePartialEntityRelation.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/RefreshEntityRequest.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/RefreshEntityRequest.model.ts index 617f35288b..b26b7f1c45 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/RefreshEntityRequest.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/RefreshEntityRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts index fac9525afa..532b6fa9e3 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntity400Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts index 8718d7938d..81cf98faac 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntity400ResponseErrorsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntityRequest.model.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntityRequest.model.ts index c3be04818d..c411697d94 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntityRequest.model.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/ValidateEntityRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/models/index.ts b/plugins/catalog-backend/src/schema/openapi/generated/models/index.ts index d6c81becb5..0e0d46df01 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/models/index.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/catalog-backend/src/schema/openapi/generated/router.ts b/plugins/catalog-backend/src/schema/openapi/generated/router.ts index d32ef0a4de..fddec71822 100644 --- a/plugins/catalog-backend/src/schema/openapi/generated/router.ts +++ b/plugins/catalog-backend/src/schema/openapi/generated/router.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. @@ -26,7 +26,7 @@ export const spec = { title: 'catalog', version: '1', description: - 'The API surface consists of a few distinct groups of functionality. Each has a\ndedicated section below.\n\n> **Note:** This page only describes some of the most commonly used parts of the\n> API, and is a work in progress.\n\nAll of the URL paths in this article are assumed to be on top of some base URL\npointing at your catalog installation. For example, if the path given in a\nsection below is `/entities`, and the catalog is located at\n`http://localhost:7007/api/catalog` during local development, the full URL would\nbe `http://localhost:7007/api/catalog/entities`. The actual URL may vary from\none organization to the other, especially in production, but is commonly your\n`backend.baseUrl` in your app config, plus `/api/catalog` at the end.\n\nSome or all of the endpoints may accept or require an `Authorization` header\nwith a `Bearer` token, which should then be the Backstage token returned by the\n[`identity API`](https://backstage.io/docs/reference/core-plugin-api.identityapiref).\n', + 'The API surface consists of a few distinct groups of functionality. Each has a\ndedicated section below.\n\n:::note Note \n This page only describes some of the most commonly used parts of the API, and is a work in progress.\n:::\n\nAll of the URL paths in this article are assumed to be on top of some base URL\npointing at your catalog installation. For example, if the path given in a\nsection below is `/entities`, and the catalog is located at\n`http://localhost:7007/api/catalog` during local development, the full URL would\nbe `http://localhost:7007/api/catalog/entities`. The actual URL may vary from\none organization to the other, especially in production, but is commonly your\n`backend.baseUrl` in your app config, plus `/api/catalog` at the end.\n\nSome or all of the endpoints may accept or require an `Authorization` header\nwith a `Bearer` token, which should then be the Backstage token returned by the\n[`identity API`](https://backstage.io/docs/reference/core-plugin-api.identityapiref).\n', license: { name: 'Apache-2.0', url: 'http://www.apache.org/licenses/LICENSE-2.0.html', diff --git a/plugins/catalog-backend/src/schema/openapi/index.ts b/plugins/catalog-backend/src/schema/openapi/index.ts index db98243cbf..196aad553a 100644 --- a/plugins/catalog-backend/src/schema/openapi/index.ts +++ b/plugins/catalog-backend/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/apis/Api.server.ts b/plugins/events-backend/src/schema/openapi/generated/apis/Api.server.ts index 4a1efdfe67..0b116fe012 100644 --- a/plugins/events-backend/src/schema/openapi/generated/apis/Api.server.ts +++ b/plugins/events-backend/src/schema/openapi/generated/apis/Api.server.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/apis/index.ts b/plugins/events-backend/src/schema/openapi/generated/apis/index.ts index a3cdbbebd2..8d81cbaf39 100644 --- a/plugins/events-backend/src/schema/openapi/generated/apis/index.ts +++ b/plugins/events-backend/src/schema/openapi/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/index.ts b/plugins/events-backend/src/schema/openapi/generated/index.ts index 69c39313c6..dec4b8804e 100644 --- a/plugins/events-backend/src/schema/openapi/generated/index.ts +++ b/plugins/events-backend/src/schema/openapi/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/ErrorError.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/ErrorError.model.ts index 0a51c67fef..809cd3fa80 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/ErrorError.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts index d44dcb66d9..3eb5e15740 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts index 91c120483d..edbcc32df7 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/Event.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/Event.model.ts index edd88b0738..02e52a81d9 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/Event.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/Event.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts index 3ca3282f47..ff15eef3ce 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/ModelError.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/ModelError.model.ts index 3e6af947ec..3914d73531 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/ModelError.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/PostEventRequest.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/PostEventRequest.model.ts index cfaf66ce4f..6bbb2974b5 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/PostEventRequest.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/PostEventRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts b/plugins/events-backend/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts index a1dc298789..786f0a0644 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/models/index.ts b/plugins/events-backend/src/schema/openapi/generated/models/index.ts index 5044f65fff..6baa8ef449 100644 --- a/plugins/events-backend/src/schema/openapi/generated/models/index.ts +++ b/plugins/events-backend/src/schema/openapi/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/generated/router.ts b/plugins/events-backend/src/schema/openapi/generated/router.ts index d2cedd2166..f1b9de2099 100644 --- a/plugins/events-backend/src/schema/openapi/generated/router.ts +++ b/plugins/events-backend/src/schema/openapi/generated/router.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-backend/src/schema/openapi/index.ts b/plugins/events-backend/src/schema/openapi/index.ts index db98243cbf..196aad553a 100644 --- a/plugins/events-backend/src/schema/openapi/index.ts +++ b/plugins/events-backend/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/apis/Api.client.ts b/plugins/events-node/src/schema/openapi/generated/apis/Api.client.ts index c77ddc1d67..71e3767af2 100644 --- a/plugins/events-node/src/schema/openapi/generated/apis/Api.client.ts +++ b/plugins/events-node/src/schema/openapi/generated/apis/Api.client.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/apis/index.ts b/plugins/events-node/src/schema/openapi/generated/apis/index.ts index af52f9db46..fc7c83b736 100644 --- a/plugins/events-node/src/schema/openapi/generated/apis/index.ts +++ b/plugins/events-node/src/schema/openapi/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/index.ts b/plugins/events-node/src/schema/openapi/generated/index.ts index bb399e97a0..dc3055033d 100644 --- a/plugins/events-node/src/schema/openapi/generated/index.ts +++ b/plugins/events-node/src/schema/openapi/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/models/ErrorError.model.ts b/plugins/events-node/src/schema/openapi/generated/models/ErrorError.model.ts index 0a51c67fef..809cd3fa80 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/ErrorError.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/models/ErrorRequest.model.ts b/plugins/events-node/src/schema/openapi/generated/models/ErrorRequest.model.ts index d44dcb66d9..3eb5e15740 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/ErrorRequest.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/models/ErrorResponse.model.ts b/plugins/events-node/src/schema/openapi/generated/models/ErrorResponse.model.ts index 91c120483d..edbcc32df7 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/ErrorResponse.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/models/Event.model.ts b/plugins/events-node/src/schema/openapi/generated/models/Event.model.ts index edd88b0738..02e52a81d9 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/Event.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/Event.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts b/plugins/events-node/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts index 3ca3282f47..ff15eef3ce 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/GetSubscriptionEvents200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/models/ModelError.model.ts b/plugins/events-node/src/schema/openapi/generated/models/ModelError.model.ts index 3e6af947ec..3914d73531 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/ModelError.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/models/PostEventRequest.model.ts b/plugins/events-node/src/schema/openapi/generated/models/PostEventRequest.model.ts index cfaf66ce4f..6bbb2974b5 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/PostEventRequest.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/PostEventRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts b/plugins/events-node/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts index a1dc298789..786f0a0644 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/PutSubscriptionRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/models/index.ts b/plugins/events-node/src/schema/openapi/generated/models/index.ts index 5044f65fff..6baa8ef449 100644 --- a/plugins/events-node/src/schema/openapi/generated/models/index.ts +++ b/plugins/events-node/src/schema/openapi/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/generated/pluginId.ts b/plugins/events-node/src/schema/openapi/generated/pluginId.ts index 36b5433cc3..b5fc50cfc0 100644 --- a/plugins/events-node/src/schema/openapi/generated/pluginId.ts +++ b/plugins/events-node/src/schema/openapi/generated/pluginId.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/events-node/src/schema/openapi/index.ts b/plugins/events-node/src/schema/openapi/index.ts index db98243cbf..196aad553a 100644 --- a/plugins/events-node/src/schema/openapi/index.ts +++ b/plugins/events-node/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/generated/apis/Api.server.ts b/plugins/search-backend/src/schema/openapi/generated/apis/Api.server.ts index d23648d8b5..26652de2cd 100644 --- a/plugins/search-backend/src/schema/openapi/generated/apis/Api.server.ts +++ b/plugins/search-backend/src/schema/openapi/generated/apis/Api.server.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/generated/apis/index.ts b/plugins/search-backend/src/schema/openapi/generated/apis/index.ts index a3cdbbebd2..8d81cbaf39 100644 --- a/plugins/search-backend/src/schema/openapi/generated/apis/index.ts +++ b/plugins/search-backend/src/schema/openapi/generated/apis/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/generated/index.ts b/plugins/search-backend/src/schema/openapi/generated/index.ts index 69c39313c6..dec4b8804e 100644 --- a/plugins/search-backend/src/schema/openapi/generated/index.ts +++ b/plugins/search-backend/src/schema/openapi/generated/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/ErrorError.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/ErrorError.model.ts index 0a51c67fef..809cd3fa80 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/ErrorError.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/ErrorError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts index d44dcb66d9..3eb5e15740 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/ErrorRequest.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts index 91c120483d..edbcc32df7 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/ErrorResponse.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/ModelError.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/ModelError.model.ts index 3e6af947ec..3914d73531 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/ModelError.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/ModelError.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/Query200Response.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/Query200Response.model.ts index 8ddb2a0b63..190ab8dc59 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/Query200Response.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/Query200Response.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInner.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInner.model.ts index ec635c8e5b..7a1c9c155a 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInner.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInner.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInnerDocument.model.ts b/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInnerDocument.model.ts index 9e82274aa7..004ce76587 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInnerDocument.model.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/Query200ResponseResultsInnerDocument.model.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/generated/models/index.ts b/plugins/search-backend/src/schema/openapi/generated/models/index.ts index d38087bf8c..fc9f1408f7 100644 --- a/plugins/search-backend/src/schema/openapi/generated/models/index.ts +++ b/plugins/search-backend/src/schema/openapi/generated/models/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. diff --git a/plugins/search-backend/src/schema/openapi/index.ts b/plugins/search-backend/src/schema/openapi/index.ts index db98243cbf..196aad553a 100644 --- a/plugins/search-backend/src/schema/openapi/index.ts +++ b/plugins/search-backend/src/schema/openapi/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2024 The Backstage Authors + * 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. From 4a77f7bd01167faeecfcc1000adcefa5e6d18449 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 15 Sep 2025 12:16:19 +0200 Subject: [PATCH 63/71] Update .changeset/all-bats-shine.md Signed-off-by: Patrik Oldsberg --- .changeset/all-bats-shine.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/all-bats-shine.md b/.changeset/all-bats-shine.md index 7d9af288c3..68d86c8d15 100644 --- a/.changeset/all-bats-shine.md +++ b/.changeset/all-bats-shine.md @@ -2,4 +2,4 @@ '@backstage/cli': patch --- -Change webpack caching config param to `contenthash` for cli bundler. This fixes an issue with webpack sometimes generating same hash name output files even though the content of a file was different. See more at [https://webpack.js.org/guides/caching/](https://webpack.js.org/guides/caching/). +Updated the WebPack configuration to use `contenthash`. This fixes an issue were builds would sometimes generate output files with the same name but different content across builds, leading to breakages when loading the frontend app. From 0ffa4c7955aaf8d96bf869ab5564220ce976a883 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 15 Sep 2025 12:09:46 +0200 Subject: [PATCH 64/71] ui: fix matchMedia fallback Signed-off-by: Patrik Oldsberg --- .changeset/shy-chicken-smash.md | 5 +++++ .github/vale/config/vocabularies/Backstage/accept.txt | 1 + packages/ui/src/hooks/useMediaQuery.ts | 6 +++++- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/shy-chicken-smash.md diff --git a/.changeset/shy-chicken-smash.md b/.changeset/shy-chicken-smash.md new file mode 100644 index 0000000000..7524a50580 --- /dev/null +++ b/.changeset/shy-chicken-smash.md @@ -0,0 +1,5 @@ +--- +'@backstage/ui': patch +--- + +Removed the need to mock `window.matchMedia` in tests, falling back to default breakpoint values instead. diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index 516eec516f..b0225be547 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -38,6 +38,7 @@ Bitrise Blackbox bool boolean +breakpoint Brex broadcasted bugfixes diff --git a/packages/ui/src/hooks/useMediaQuery.ts b/packages/ui/src/hooks/useMediaQuery.ts index 9e2cc9ef0b..659ea4a418 100644 --- a/packages/ui/src/hooks/useMediaQuery.ts +++ b/packages/ui/src/hooks/useMediaQuery.ts @@ -22,7 +22,8 @@ type UseMediaQueryOptions = { initializeWithValue?: boolean; }; -const IS_SERVER = typeof window === 'undefined'; +const IS_SERVER = + typeof window === 'undefined' || typeof window.matchMedia === 'undefined'; export function useMediaQuery( query: string, @@ -51,6 +52,9 @@ export function useMediaQuery( } useIsomorphicLayoutEffect(() => { + if (IS_SERVER) { + return; + } const matchMedia = window.matchMedia(query); // Triggered at the first client-side load and if query changes From 15575563f7a38907d6c07a36e05ed97150a77431 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 5 Sep 2025 19:10:55 +0200 Subject: [PATCH 65/71] catalog-backend: add support for type in catalog.rules.allow Signed-off-by: Vincenzo Scamporlino --- .../src/ingestion/CatalogRules.test.ts | 38 ++++++++++++++++++- .../src/ingestion/CatalogRules.ts | 29 +++++++++++--- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts index 19c231884c..67d1e86248 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts @@ -27,8 +27,14 @@ const entity = { kind: 'Group', } as Entity, component: { - kind: 'component', + kind: 'Component', } as Entity, + componentWithType: { + kind: 'Component', + spec: { + type: 'service', + }, + } as unknown as Entity, location: { kind: 'Location', } as Entity, @@ -164,6 +170,9 @@ describe('DefaultCatalogRulesEnforcer', () => { expect(enforcer.isAllowed(entity.user, location.x)).toBe(false); expect(enforcer.isAllowed(entity.group, location.y)).toBe(false); expect(enforcer.isAllowed(entity.component, location.z)).toBe(true); + expect(enforcer.isAllowed(entity.componentWithType, location.z)).toBe( + true, + ); expect(enforcer.isAllowed(entity.location, location.z)).toBe(true); }); @@ -268,6 +277,33 @@ describe('DefaultCatalogRulesEnforcer', () => { }); }); + it('should only allow components with a specific type', () => { + const enforcer = DefaultCatalogRulesEnforcer.fromConfig( + new ConfigReader({ + catalog: { + rules: [ + { + allow: [{ kind: 'Component', type: 'service' }], + locations: [{ type: 'url', pattern: 'https://github.com/b/**' }], + }, + ], + }, + }), + ); + + expect(enforcer.isAllowed(entity.component, location.w)).toBe(false); + expect(enforcer.isAllowed(entity.component, location.y)).toBe(false); + expect(enforcer.isAllowed(entity.component, location.z)).toBe(false); + + expect(enforcer.isAllowed(entity.componentWithType, location.w)).toBe(true); + expect(enforcer.isAllowed(entity.componentWithType, location.y)).toBe( + false, + ); + expect(enforcer.isAllowed(entity.componentWithType, location.z)).toBe( + false, + ); + }); + it('should allow locations with a hidden folder', () => { const enforcer = DefaultCatalogRulesEnforcer.fromConfig( new ConfigReader({ diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.ts index 8fb237df30..ddb6398bf6 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.ts @@ -26,9 +26,7 @@ import { minimatch } from 'minimatch'; * An undefined list of matchers means match all, an empty list of matchers means match none. */ export type CatalogRule = { - allow: Array<{ - kind: string; - }>; + allow: CatalogRuleAllow[]; locations?: Array<{ exact?: string; type: string; @@ -36,6 +34,11 @@ export type CatalogRule = { }>; }; +type CatalogRuleAllow = { + kind: string; + type?: string; +}; + /** * Decides whether an entity from a given location is allowed to enter the * catalog, according to some rule set. @@ -78,6 +81,9 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer { * catalog: * rules: * - allow: [Component, API] + * - allow: + * - kind: Resource + * type: database * - allow: [Template] * locations: * - type: url @@ -105,7 +111,14 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer { const globalRules = config .getConfigArray('catalog.rules') .map(ruleConf => ({ - allow: ruleConf.getStringArray('allow').map(kind => ({ kind })), + allow: ruleConf + .get>('allow') + .map(kindOrObject => { + if (typeof kindOrObject === 'string') { + return { kind: kindOrObject }; + } + return kindOrObject; + }), locations: ruleConf .getOptionalConfigArray('locations') ?.map(locationConfig => { @@ -199,13 +212,17 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer { return false; } - private matchEntity(entity: Entity, matchers?: { kind: string }[]): boolean { + private matchEntity(entity: Entity, matchers?: CatalogRuleAllow[]): boolean { if (!matchers) { return true; } for (const matcher of matchers) { - if (entity?.kind?.toLowerCase() !== matcher.kind.toLowerCase()) { + if (entity.kind?.toLowerCase() !== matcher.kind.toLowerCase()) { + continue; + } + + if (matcher.type && matcher.type !== entity.spec?.type) { continue; } From 36694fb7749c2b8f2db8a918a907688f27fded66 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 5 Sep 2025 19:12:33 +0200 Subject: [PATCH 66/71] catalog-backend: allow type to catalog.rules.allow Signed-off-by: Vincenzo Scamporlino --- plugins/catalog-backend/config.d.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 6329bcb111..d346f01e37 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -38,8 +38,11 @@ export interface Config { * Allow entities of these particular kinds. * * E.g. ["Component", "API", "Template", "Location"] + * + * You can also specify the type of the entity by using an object with `kind` and optional `type` properties. + * E.g. [{ kind: "Component", type: "service" }] */ - allow: Array; + allow: Array; /** * Limit this rule to a specific location * From 9b40a55ded2b3a5f5819d57d573d9e0993c34c49 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Fri, 5 Sep 2025 19:12:59 +0200 Subject: [PATCH 67/71] catalog-backend: type changeset Signed-off-by: Vincenzo Scamporlino --- .changeset/rotten-hairs-attack.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .changeset/rotten-hairs-attack.md diff --git a/.changeset/rotten-hairs-attack.md b/.changeset/rotten-hairs-attack.md new file mode 100644 index 0000000000..1b89ad46e7 --- /dev/null +++ b/.changeset/rotten-hairs-attack.md @@ -0,0 +1,25 @@ +--- +'@backstage/plugin-catalog-backend': minor +--- + +Add support for specifying an entity `type` in `catalog.rules.allow` rules within the catalog configuration. + +For example, this enables allowing all `Template` entities with the type `website`: + +```diff + catalog: + rules: + - allow: + - Component + - API + - Resource + - System + - Domain + - Location ++ - allow: ++ - kind: Template ++ type: website + locations: + - type: url + pattern: https://github.com/org/*\/blob/master/*.yaml +``` From 77980e3f80b0e0001ddf78331ba9abe509000e81 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Wed, 10 Sep 2025 09:37:31 +0200 Subject: [PATCH 68/71] catalog-backend: rename type to spec.type Signed-off-by: Vincenzo Scamporlino --- .changeset/rotten-hairs-attack.md | 4 ++-- plugins/catalog-backend/config.d.ts | 2 +- plugins/catalog-backend/src/ingestion/CatalogRules.test.ts | 2 +- plugins/catalog-backend/src/ingestion/CatalogRules.ts | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.changeset/rotten-hairs-attack.md b/.changeset/rotten-hairs-attack.md index 1b89ad46e7..7754441f78 100644 --- a/.changeset/rotten-hairs-attack.md +++ b/.changeset/rotten-hairs-attack.md @@ -2,7 +2,7 @@ '@backstage/plugin-catalog-backend': minor --- -Add support for specifying an entity `type` in `catalog.rules.allow` rules within the catalog configuration. +Add support for specifying an entity `spec.type` in `catalog.rules.allow` rules within the catalog configuration. For example, this enables allowing all `Template` entities with the type `website`: @@ -18,7 +18,7 @@ For example, this enables allowing all `Template` entities with the type `websit - Location + - allow: + - kind: Template -+ type: website ++ spec.type: website locations: - type: url pattern: https://github.com/org/*\/blob/master/*.yaml diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index d346f01e37..4b349405cc 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -42,7 +42,7 @@ export interface Config { * You can also specify the type of the entity by using an object with `kind` and optional `type` properties. * E.g. [{ kind: "Component", type: "service" }] */ - allow: Array; + allow: Array; /** * Limit this rule to a specific location * diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts index 67d1e86248..a28ceb0d48 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts @@ -283,7 +283,7 @@ describe('DefaultCatalogRulesEnforcer', () => { catalog: { rules: [ { - allow: [{ kind: 'Component', type: 'service' }], + allow: [{ kind: 'Component', 'spec.type': 'service' }], locations: [{ type: 'url', pattern: 'https://github.com/b/**' }], }, ], diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.ts index ddb6398bf6..b348f5b752 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.ts @@ -36,7 +36,7 @@ export type CatalogRule = { type CatalogRuleAllow = { kind: string; - type?: string; + 'spec.type'?: string; }; /** @@ -222,7 +222,7 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer { continue; } - if (matcher.type && matcher.type !== entity.spec?.type) { + if (matcher['spec.type'] && matcher['spec.type'] !== entity.spec?.type) { continue; } From 3c3466dd51cecba1b650f33f2610a9fe8fe2032f Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 11 Sep 2025 20:33:46 +0200 Subject: [PATCH 69/71] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Vincenzo Scamporlino --- .changeset/rotten-hairs-attack.md | 2 +- plugins/catalog-backend/config.d.ts | 4 ++-- plugins/catalog-backend/src/ingestion/CatalogRules.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.changeset/rotten-hairs-attack.md b/.changeset/rotten-hairs-attack.md index 7754441f78..aa6dabc78c 100644 --- a/.changeset/rotten-hairs-attack.md +++ b/.changeset/rotten-hairs-attack.md @@ -2,7 +2,7 @@ '@backstage/plugin-catalog-backend': minor --- -Add support for specifying an entity `spec.type` in `catalog.rules.allow` rules within the catalog configuration. +Add support for specifying an entity `spec.type` in `catalog.rules` and `catalog.locations.rules` within the catalog configuration. For example, this enables allowing all `Template` entities with the type `website`: diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 4b349405cc..a506476dbf 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -39,8 +39,8 @@ export interface Config { * * E.g. ["Component", "API", "Template", "Location"] * - * You can also specify the type of the entity by using an object with `kind` and optional `type` properties. - * E.g. [{ kind: "Component", type: "service" }] + * You can also specify the type of the entity by using an object with `kind` and optional `spec.type` properties. + * E.g. [{ kind: "Component", 'spec.type': "service" }] */ allow: Array; /** diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.ts index b348f5b752..ea4e520177 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.ts @@ -83,7 +83,7 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer { * - allow: [Component, API] * - allow: * - kind: Resource - * type: database + * 'spec.type': database * - allow: [Template] * locations: * - type: url From 0364b9f107cc1be37e4342d555483b35b5eddbfa Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Thu, 11 Sep 2025 20:41:09 +0200 Subject: [PATCH 70/71] catalog-backend: improve validation Signed-off-by: Vincenzo Scamporlino --- .../src/ingestion/CatalogRules.ts | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.ts index ea4e520177..f6026a84cd 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.ts @@ -19,6 +19,7 @@ import { Entity } from '@backstage/catalog-model'; import path from 'path'; import { LocationSpec } from '@backstage/plugin-catalog-common'; import { minimatch } from 'minimatch'; +import { z } from 'zod'; /** * Rules to apply to catalog entities. @@ -47,6 +48,16 @@ export type CatalogRulesEnforcer = { isAllowed(entity: Entity, location: LocationSpec): boolean; }; +const allowRuleParser = z.array( + z + .object({ + kind: z.string(), + 'spec.type': z.string().optional(), + }) + .or(z.string()) + .transform(val => (typeof val === 'string' ? { kind: val } : val)), +); + /** * Implements the default catalog rule set, consuming the config keys * `catalog.rules` and `catalog.locations.[].rules`. @@ -111,14 +122,7 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer { const globalRules = config .getConfigArray('catalog.rules') .map(ruleConf => ({ - allow: ruleConf - .get>('allow') - .map(kindOrObject => { - if (typeof kindOrObject === 'string') { - return { kind: kindOrObject }; - } - return kindOrObject; - }), + allow: allowRuleParser.parse(ruleConf.get('allow')), locations: ruleConf .getOptionalConfigArray('locations') ?.map(locationConfig => { From 62bb3b75d076389678c1538b5bf2a55fbbd7ccf2 Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Mon, 15 Sep 2025 11:50:34 +0200 Subject: [PATCH 71/71] catalog-backend: make type comparison lowercase Signed-off-by: Vincenzo Scamporlino --- .../src/ingestion/CatalogRules.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.ts index f6026a84cd..15e620a443 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.ts @@ -222,12 +222,23 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer { } for (const matcher of matchers) { - if (entity.kind?.toLowerCase() !== matcher.kind.toLowerCase()) { + if ( + entity.kind?.toLocaleLowerCase('en-US') !== + matcher.kind.toLocaleLowerCase('en-US') + ) { continue; } - if (matcher['spec.type'] && matcher['spec.type'] !== entity.spec?.type) { - continue; + if (matcher['spec.type']) { + if (typeof entity.spec?.type !== 'string') { + continue; + } + if ( + matcher['spec.type'].toLocaleLowerCase('en-US') !== + entity.spec.type.toLocaleLowerCase('en-US') + ) { + continue; + } } return true;