From 1680359cde5091f6e9d11c2db5b8727597c3a101 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Thu, 23 Apr 2026 13:18:27 -0400 Subject: [PATCH 1/9] feat(extensions): enforce collision policy for duplicate entity identities --- .../src/providers/BaseEntityProvider.test.ts | 125 ++++++++++++++++++ .../src/providers/BaseEntityProvider.ts | 73 ++++++++-- 2 files changed, 185 insertions(+), 13 deletions(-) create mode 100644 workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts new file mode 100644 index 00000000000..a728683bdc7 --- /dev/null +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts @@ -0,0 +1,125 @@ +/* + * Copyright Red Hat, Inc. + * + * 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 { Entity } from '@backstage/catalog-model'; +import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; +import { BaseEntityProvider } from './BaseEntityProvider'; +import { JsonFileData } from '../types'; + +class TestEntityProvider extends BaseEntityProvider { + getProviderName(): string { + return 'test-entity-provider'; + } + + getKind(): string { + return 'Plugin'; + } +} + +const taskRunner: SchedulerServiceTaskRunner = { + run: jest.fn(async ({ fn }) => fn()), +}; + +const createEntity = (overrides?: Partial): Entity => ({ + apiVersion: 'extensions.backstage.io/v1alpha1', + kind: 'Plugin', + metadata: { + name: 'duplicate-plugin', + ...overrides?.metadata, + }, + spec: { + owner: 'test-owner', + ...(overrides?.spec as object), + }, + ...overrides, +}); + +const createFileData = ( + filePath: string, + entity: Entity, +): JsonFileData => ({ + filePath, + content: entity, +}); + +describe('BaseEntityProvider collision policy', () => { + beforeEach(() => { + jest.spyOn(console, 'warn').mockImplementation(() => undefined); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('keeps first definition when duplicate entities are equivalent', () => { + const provider = new TestEntityProvider(taskRunner); + const duplicate = createEntity(); + + const entities = provider.getEntities([ + createFileData('/extensions/primary/plugin.yaml', duplicate), + createFileData('/extensions/extra/community/plugin.yaml', duplicate), + ]); + + expect(entities).toHaveLength(1); + expect(console.warn).toHaveBeenCalledWith( + expect.stringContaining( + "Skipping duplicate Extensions entity 'plugin/default/duplicate-plugin'", + ), + ); + }); + + it('throws when duplicate entities have conflicting definitions', () => { + const provider = new TestEntityProvider(taskRunner); + const firstEntity = createEntity({ + spec: { owner: 'owner-a' }, + }); + const secondEntity = createEntity({ + spec: { owner: 'owner-b' }, + }); + + expect(() => + provider.getEntities([ + createFileData('/extensions/primary/plugin.yaml', firstEntity), + createFileData('/extensions/extra/community/plugin.yaml', secondEntity), + ]), + ).toThrow( + "Conflicting Extensions entities detected for 'plugin/default/duplicate-plugin'", + ); + }); + + it('keeps entities with same name when namespaces differ', () => { + const provider = new TestEntityProvider(taskRunner); + const defaultNamespaceEntity = createEntity({ + metadata: { name: 'shared-name' }, + }); + const customNamespaceEntity = createEntity({ + metadata: { name: 'shared-name', namespace: 'community' }, + }); + + const entities = provider.getEntities([ + createFileData( + '/extensions/primary/plugin-default.yaml', + defaultNamespaceEntity, + ), + createFileData( + '/extensions/extra/community/plugin-custom.yaml', + customNamespaceEntity, + ), + ]); + + expect(entities).toHaveLength(2); + }); +}); diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts index bd5f7f32b3b..26436b5dd42 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts @@ -28,6 +28,7 @@ import { readYamlFiles } from '../utils/file-utils'; import { JsonFileData } from '../types'; import path from 'path'; import fs from 'fs'; +import { isDeepStrictEqual } from 'node:util'; /** * @public @@ -50,23 +51,69 @@ export abstract class BaseEntityProvider abstract getProviderName(): string; abstract getKind(): string; + private getEntityIdentity(entity: Entity): string { + const namespace = entity.metadata.namespace ?? 'default'; + return [ + entity.kind.toLocaleLowerCase('en-US'), + namespace.toLocaleLowerCase('en-US'), + entity.metadata.name.toLocaleLowerCase('en-US'), + ].join('/'); + } + + private addProviderAnnotations(entity: T): T { + return { + ...entity, + metadata: { + ...entity.metadata, + annotations: { + ...entity.metadata.annotations, + [ANNOTATION_LOCATION]: `file:${this.getProviderName()}`, + [ANNOTATION_ORIGIN_LOCATION]: `file:${this.getProviderName()}`, + }, + }, + }; + } + getEntities(allEntities: JsonFileData[]): T[] { if (allEntities.length === 0) { return []; } - return allEntities - .filter(d => d.content.kind === this.getKind()) - .map(file => ({ - ...file.content, - metadata: { - ...file.content.metadata, - annotations: { - ...file.content.metadata.annotations, - [ANNOTATION_LOCATION]: `file:${this.getProviderName()}`, - [ANNOTATION_ORIGIN_LOCATION]: `file:${this.getProviderName()}`, - }, - }, - })); + + const entitiesByIdentity = new Map< + string, + { entity: T; filePath: string } + >(); + + for (const fileData of allEntities) { + if (fileData.content.kind !== this.getKind()) { + continue; + } + + const identity = this.getEntityIdentity(fileData.content); + const existing = entitiesByIdentity.get(identity); + if (!existing) { + entitiesByIdentity.set(identity, { + entity: fileData.content, + filePath: fileData.filePath, + }); + continue; + } + + if (isDeepStrictEqual(existing.entity, fileData.content)) { + console.warn( + `Skipping duplicate Extensions entity '${identity}' from '${fileData.filePath}'. Keeping first definition from '${existing.filePath}'.`, + ); + continue; + } + + throw new Error( + `Conflicting Extensions entities detected for '${identity}' in '${existing.filePath}' and '${fileData.filePath}'.`, + ); + } + + return Array.from(entitiesByIdentity.values()).map(({ entity }) => + this.addProviderAnnotations(entity), + ); } async connect(connection: EntityProviderConnection): Promise { From abc9132a71a22c3d38cea32fe8e01ae4b8b093e5 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Thu, 23 Apr 2026 14:12:24 -0400 Subject: [PATCH 2/9] add changeset --- workspaces/extensions/.changeset/clean-news-itch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 workspaces/extensions/.changeset/clean-news-itch.md diff --git a/workspaces/extensions/.changeset/clean-news-itch.md b/workspaces/extensions/.changeset/clean-news-itch.md new file mode 100644 index 00000000000..b6fcd1644b3 --- /dev/null +++ b/workspaces/extensions/.changeset/clean-news-itch.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-catalog-backend-module-extensions': minor +--- + +Enforce collision policy for duplicate entity identities From 2bc5850eec46976df43dc79459ebb2c73116e35d Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Thu, 23 Apr 2026 16:13:30 -0400 Subject: [PATCH 3/9] fix tsc error --- .../src/providers/BaseEntityProvider.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts index a728683bdc7..ab33f4ca276 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts @@ -30,7 +30,7 @@ class TestEntityProvider extends BaseEntityProvider { } const taskRunner: SchedulerServiceTaskRunner = { - run: jest.fn(async ({ fn }) => fn()), + run: jest.fn(async ({ fn }) => fn(new AbortController().signal)), }; const createEntity = (overrides?: Partial): Entity => ({ From 2911107d60adcb0c54b6efa5d4fc5ba8638b9f0b Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Thu, 30 Apr 2026 09:45:03 -0400 Subject: [PATCH 4/9] implement review suggestions --- .../src/module.ts | 4 +- .../src/providers/BaseEntityProvider.test.ts | 43 +++++++++++------- .../src/providers/BaseEntityProvider.ts | 44 +++++++++++-------- 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/module.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/module.ts index 9b6d8b230f7..81fe5d55160 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/module.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/module.ts @@ -73,10 +73,10 @@ export const catalogModuleExtensions = createBackendModule({ const catalogApi = new CatalogClient({ discoveryApi: discovery }); catalog.addEntityProvider( - new ExtensionsPackageProvider(taskRunner, config), + new ExtensionsPackageProvider(taskRunner, config, logger), ); catalog.addEntityProvider( - new ExtensionsPluginProvider(delayedTaskRunner, config), + new ExtensionsPluginProvider(delayedTaskRunner, config, logger), ); // Disabling the collection provider as collections/all.yaml is already commented in RHDH 1.5 image. // catalog.addEntityProvider( diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts index ab33f4ca276..fd8b45a01fe 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.test.ts @@ -15,7 +15,10 @@ */ import { Entity } from '@backstage/catalog-model'; -import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { BaseEntityProvider } from './BaseEntityProvider'; import { JsonFileData } from '../types'; @@ -32,6 +35,13 @@ class TestEntityProvider extends BaseEntityProvider { const taskRunner: SchedulerServiceTaskRunner = { run: jest.fn(async ({ fn }) => fn(new AbortController().signal)), }; +const logger: LoggerService = { + warn: jest.fn(), + error: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + child: jest.fn(), +}; const createEntity = (overrides?: Partial): Entity => ({ apiVersion: 'extensions.backstage.io/v1alpha1', @@ -57,7 +67,7 @@ const createFileData = ( describe('BaseEntityProvider collision policy', () => { beforeEach(() => { - jest.spyOn(console, 'warn').mockImplementation(() => undefined); + jest.clearAllMocks(); }); afterEach(() => { @@ -65,7 +75,7 @@ describe('BaseEntityProvider collision policy', () => { }); it('keeps first definition when duplicate entities are equivalent', () => { - const provider = new TestEntityProvider(taskRunner); + const provider = new TestEntityProvider(taskRunner, undefined, logger); const duplicate = createEntity(); const entities = provider.getEntities([ @@ -74,15 +84,15 @@ describe('BaseEntityProvider collision policy', () => { ]); expect(entities).toHaveLength(1); - expect(console.warn).toHaveBeenCalledWith( + expect(logger.warn).toHaveBeenCalledWith( expect.stringContaining( - "Skipping duplicate Extensions entity 'plugin/default/duplicate-plugin'", + "Skipping duplicate Extensions entity 'plugin:default/duplicate-plugin'", ), ); }); - it('throws when duplicate entities have conflicting definitions', () => { - const provider = new TestEntityProvider(taskRunner); + it('warns and skips when duplicate entities have conflicting definitions', () => { + const provider = new TestEntityProvider(taskRunner, undefined, logger); const firstEntity = createEntity({ spec: { owner: 'owner-a' }, }); @@ -90,18 +100,21 @@ describe('BaseEntityProvider collision policy', () => { spec: { owner: 'owner-b' }, }); - expect(() => - provider.getEntities([ - createFileData('/extensions/primary/plugin.yaml', firstEntity), - createFileData('/extensions/extra/community/plugin.yaml', secondEntity), - ]), - ).toThrow( - "Conflicting Extensions entities detected for 'plugin/default/duplicate-plugin'", + const entities = provider.getEntities([ + createFileData('/extensions/primary/plugin.yaml', firstEntity), + createFileData('/extensions/extra/community/plugin.yaml', secondEntity), + ]); + + expect(entities).toHaveLength(1); + expect(logger.warn).toHaveBeenCalledWith( + expect.stringContaining( + "Conflicting Extensions entities detected for 'plugin:default/duplicate-plugin'", + ), ); }); it('keeps entities with same name when namespaces differ', () => { - const provider = new TestEntityProvider(taskRunner); + const provider = new TestEntityProvider(taskRunner, undefined, logger); const defaultNamespaceEntity = createEntity({ metadata: { name: 'shared-name' }, }); diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts index 26436b5dd42..854d64d1df2 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts @@ -13,11 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; +import { + LoggerService, + SchedulerServiceTaskRunner, +} from '@backstage/backend-plugin-api'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, Entity, + stringifyEntityRef, } from '@backstage/catalog-model'; import { EntityProvider, @@ -39,27 +43,24 @@ export abstract class BaseEntityProvider private connection?: EntityProviderConnection; private taskRunner: SchedulerServiceTaskRunner; private config?: Config; + private logger?: LoggerService; private static readonly EXTENSIONS_DIRECTORY = '/extensions'; private static readonly DEPRECATED_MARKETPLACE_DIRECTORY = '/marketplace'; - constructor(taskRunner: SchedulerServiceTaskRunner, config?: Config) { + constructor( + taskRunner: SchedulerServiceTaskRunner, + config?: Config, + logger?: LoggerService, + ) { this.taskRunner = taskRunner; this.config = config; + this.logger = logger; } abstract getProviderName(): string; abstract getKind(): string; - private getEntityIdentity(entity: Entity): string { - const namespace = entity.metadata.namespace ?? 'default'; - return [ - entity.kind.toLocaleLowerCase('en-US'), - namespace.toLocaleLowerCase('en-US'), - entity.metadata.name.toLocaleLowerCase('en-US'), - ].join('/'); - } - private addProviderAnnotations(entity: T): T { return { ...entity, @@ -79,7 +80,7 @@ export abstract class BaseEntityProvider return []; } - const entitiesByIdentity = new Map< + const entitiesByEntityRef = new Map< string, { entity: T; filePath: string } >(); @@ -89,10 +90,14 @@ export abstract class BaseEntityProvider continue; } - const identity = this.getEntityIdentity(fileData.content); - const existing = entitiesByIdentity.get(identity); + const identity = stringifyEntityRef({ + kind: fileData.content.kind, + namespace: fileData.content.metadata.namespace ?? 'default', + name: fileData.content.metadata.name, + }).toLocaleLowerCase('en-US'); + const existing = entitiesByEntityRef.get(identity); if (!existing) { - entitiesByIdentity.set(identity, { + entitiesByEntityRef.set(identity, { entity: fileData.content, filePath: fileData.filePath, }); @@ -100,18 +105,19 @@ export abstract class BaseEntityProvider } if (isDeepStrictEqual(existing.entity, fileData.content)) { - console.warn( + this.logger?.warn( `Skipping duplicate Extensions entity '${identity}' from '${fileData.filePath}'. Keeping first definition from '${existing.filePath}'.`, ); continue; } - throw new Error( - `Conflicting Extensions entities detected for '${identity}' in '${existing.filePath}' and '${fileData.filePath}'.`, + this.logger?.warn( + `Conflicting Extensions entities detected for '${identity}' in '${existing.filePath}' and '${fileData.filePath}'. Skipping conflicting definition from '${fileData.filePath}'.`, ); + continue; } - return Array.from(entitiesByIdentity.values()).map(({ entity }) => + return Array.from(entitiesByEntityRef.values()).map(({ entity }) => this.addProviderAnnotations(entity), ); } From f578f712e80dba1c4de3183fd4fd083ca7c949fd Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Thu, 30 Apr 2026 09:57:59 -0400 Subject: [PATCH 5/9] chore: update docs to reflect new collision behaviour --- .../plugins/catalog-backend-module-extensions/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/README.md b/workspaces/extensions/plugins/catalog-backend-module-extensions/README.md index fed3946ffa6..723d3225199 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/README.md +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/README.md @@ -59,6 +59,14 @@ extensions: directory: /path/to/custom/extensions ``` +### Collision behavior + +When multiple YAML sources define the same entity identity (`kind:namespace/name`), the provider handles collisions as follows: + +- If definitions are equivalent, it keeps the first definition and logs a warning. +- If definitions conflict, it logs a warning and skips the conflicting definition. +- Entities with the same `kind`/`name` but different namespaces are treated as distinct entities and are both ingested. + ## Plugin configuration YAML Guide: This YAML file is used to add extensions plugin to the Software catalog in your backstage application. From 8acb22a5b0c017292334cfb2b7f9c3c1ae3c0b08 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Thu, 30 Apr 2026 10:03:24 -0400 Subject: [PATCH 6/9] update api reports --- .../plugins/catalog-backend-module-extensions/report.api.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md b/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md index 4b177035d74..26ceca31a5e 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/report.api.md @@ -27,7 +27,11 @@ import { SchedulerServiceTaskRunner } from '@backstage/backend-plugin-api'; export abstract class BaseEntityProvider implements EntityProvider { - constructor(taskRunner: SchedulerServiceTaskRunner, config?: Config); + constructor( + taskRunner: SchedulerServiceTaskRunner, + config?: Config, + logger?: LoggerService, + ); // (undocumented) connect(connection: EntityProviderConnection): Promise; // (undocumented) From 999604687874fb8d7b8c2600a701164b7e59f3b5 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Tue, 5 May 2026 16:25:53 -0400 Subject: [PATCH 7/9] replace console with logger Signed-off-by: Hope Hadfield --- .../src/providers/BaseEntityProvider.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts index 854d64d1df2..ccce1c5b753 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts @@ -172,7 +172,7 @@ export abstract class BaseEntityProvider } } } catch (error) { - console.warn( + this.logger?.warn( 'Failed to read extensions directory from config, falling back to hardcoded fallbacks', error, ); @@ -192,7 +192,7 @@ export abstract class BaseEntityProvider } } - console.warn( + this.logger?.warn( `Extensions directory not found. Checked: configured directory "${BaseEntityProvider.EXTENSIONS_DIRECTORY}" and "${BaseEntityProvider.DEPRECATED_MARKETPLACE_DIRECTORY}"`, ); return null; @@ -210,7 +210,7 @@ export abstract class BaseEntityProvider try { yamlData = readYamlFiles(extensionsFilePath); } catch (error) { - console.error(error.message); + this.logger?.error(error.message); } } From 5707a0c8263f9ed521ca2973fa61aaf78b9132e6 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Tue, 5 May 2026 16:26:59 -0400 Subject: [PATCH 8/9] fix: update test entity invalid name Signed-off-by: Hope Hadfield --- .../examples/plugins/certified-plugin-2-by-vendor-a.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workspaces/extensions/examples/plugins/certified-plugin-2-by-vendor-a.yaml b/workspaces/extensions/examples/plugins/certified-plugin-2-by-vendor-a.yaml index 7e8881014fc..1a671c894ba 100644 --- a/workspaces/extensions/examples/plugins/certified-plugin-2-by-vendor-a.yaml +++ b/workspaces/extensions/examples/plugins/certified-plugin-2-by-vendor-a.yaml @@ -3,7 +3,7 @@ apiVersion: extensions.backstage.io/v1alpha1 kind: Plugin metadata: namespace: extensions-plugin-demo - name: certified-plugin-1-by-vendor-a + name: certified-plugin-2-by-vendor-a title: Certified Plugin 2 by Vendor A description: This is a certified plugin example annotations: From f1c4eac4637d2e0f7bc64aadf92bf87ebf34a367 Mon Sep 17 00:00:00 2001 From: Hope Hadfield Date: Tue, 5 May 2026 16:32:35 -0400 Subject: [PATCH 9/9] fix: resolve sonarqube issues Signed-off-by: Hope Hadfield --- .../src/providers/BaseEntityProvider.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts index ccce1c5b753..12f8c4910a8 100644 --- a/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts +++ b/workspaces/extensions/plugins/catalog-backend-module-extensions/src/providers/BaseEntityProvider.ts @@ -43,7 +43,7 @@ export abstract class BaseEntityProvider private connection?: EntityProviderConnection; private taskRunner: SchedulerServiceTaskRunner; private config?: Config; - private logger?: LoggerService; + private readonly logger?: LoggerService; private static readonly EXTENSIONS_DIRECTORY = '/extensions'; private static readonly DEPRECATED_MARKETPLACE_DIRECTORY = '/marketplace'; @@ -114,7 +114,6 @@ export abstract class BaseEntityProvider this.logger?.warn( `Conflicting Extensions entities detected for '${identity}' in '${existing.filePath}' and '${fileData.filePath}'. Skipping conflicting definition from '${fileData.filePath}'.`, ); - continue; } return Array.from(entitiesByEntityRef.values()).map(({ entity }) =>