From e3d514507a88d71cc09ab6d23b14ca8f87fcf23d Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Aug 2022 13:40:59 +0200 Subject: [PATCH 1/9] backend-app-api: rename BackendRegistrable to BackendFeature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Fredrik Adelöw Signed-off-by: Johan Haals --- .../src/wiring/BackendInitializer.ts | 22 +++++++++---------- .../src/wiring/BackstageBackend.ts | 6 ++--- packages/backend-app-api/src/wiring/types.ts | 4 ++-- .../src/wiring/factories.ts | 14 +++++------- .../backend-plugin-api/src/wiring/index.ts | 2 +- .../backend-plugin-api/src/wiring/types.ts | 2 +- .../src/next/wiring/TestBackend.ts | 4 ++-- 7 files changed, 24 insertions(+), 30 deletions(-) diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index ab086f4144..0c2cd3e541 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -15,7 +15,7 @@ */ import { - BackendRegistrable, + BackendFeature, ExtensionPoint, ServiceRef, } from '@backstage/backend-plugin-api'; @@ -25,7 +25,7 @@ type ServiceOrExtensionPoint = ExtensionPoint | ServiceRef; export class BackendInitializer { #started = false; - #extensions = new Map(); + #features = new Map(); #registerInits = new Array(); #extensionPoints = new Map(); #serviceHolder: ServiceHolder; @@ -51,13 +51,11 @@ export class BackendInitializer { ); } - add(extension: BackendRegistrable, options?: TOptions) { + add(feature: BackendFeature, options?: TOptions) { if (this.#started) { - throw new Error( - 'extension can not be added after the backend has started', - ); + throw new Error('feature can not be added after the backend has started'); } - this.#extensions.set(extension, options); + this.#features.set(feature, options); } async start(): Promise { @@ -67,13 +65,13 @@ export class BackendInitializer { } this.#started = true; - for (const [extension] of this.#extensions) { + for (const [feature] of this.#features) { const provides = new Set>(); let registerInit: BackendRegisterInit | undefined = undefined; - console.log('Registering', extension.id); - extension.register({ + console.log('Registering', feature.id); + feature.register({ registerExtensionPoint: (extensionPointRef, impl) => { if (registerInit) { throw new Error('registerExtensionPoint called after registerInit'); @@ -89,7 +87,7 @@ export class BackendInitializer { throw new Error('registerInit must only be called once'); } registerInit = { - id: extension.id, + id: feature.id, provides, consumes: new Set(Object.values(registerOptions.deps)), deps: registerOptions.deps, @@ -100,7 +98,7 @@ export class BackendInitializer { if (!registerInit) { throw new Error( - `registerInit was not called by register in ${extension.id}`, + `registerInit was not called by register in ${feature.id}`, ); } diff --git a/packages/backend-app-api/src/wiring/BackstageBackend.ts b/packages/backend-app-api/src/wiring/BackstageBackend.ts index 104d2b6fc8..1e09ed77ba 100644 --- a/packages/backend-app-api/src/wiring/BackstageBackend.ts +++ b/packages/backend-app-api/src/wiring/BackstageBackend.ts @@ -16,7 +16,7 @@ import { AnyServiceFactory, - BackendRegistrable, + BackendFeature, } from '@backstage/backend-plugin-api'; import { BackendInitializer } from './BackendInitializer'; import { ServiceRegistry } from './ServiceRegistry'; @@ -31,8 +31,8 @@ export class BackstageBackend implements Backend { this.#initializer = new BackendInitializer(this.#services); } - add(extension: BackendRegistrable): void { - this.#initializer.add(extension); + add(feature: BackendFeature): void { + this.#initializer.add(feature); } async start(): Promise { diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts index 2aaead805d..cdd4578789 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -16,7 +16,7 @@ import { AnyServiceFactory, - BackendRegistrable, + BackendFeature, FactoryFunc, ServiceRef, } from '@backstage/backend-plugin-api'; @@ -26,7 +26,7 @@ import { BackstageBackend } from './BackstageBackend'; * @public */ export interface Backend { - add(extension: BackendRegistrable): void; + add(feature: BackendFeature): void; start(): Promise; } diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts index 1c3a27bb1c..a057c0d6ec 100644 --- a/packages/backend-plugin-api/src/wiring/factories.ts +++ b/packages/backend-plugin-api/src/wiring/factories.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - BackendInitRegistry, - BackendRegistrable, - ExtensionPoint, -} from './types'; +import { BackendInitRegistry, BackendFeature, ExtensionPoint } from './types'; /** @public */ export function createExtensionPoint(options: { @@ -46,8 +42,8 @@ export interface BackendPluginConfig { export function createBackendPlugin( config: BackendPluginConfig, ): undefined extends TOptions - ? (options?: TOptions) => BackendRegistrable - : (options: TOptions) => BackendRegistrable { + ? (options?: TOptions) => BackendFeature + : (options: TOptions) => BackendFeature { return (options?: TOptions) => ({ id: config.id, register(register: BackendInitRegistry) { @@ -70,8 +66,8 @@ export interface BackendModuleConfig { export function createBackendModule( config: BackendModuleConfig, ): undefined extends TOptions - ? (options?: TOptions) => BackendRegistrable - : (options: TOptions) => BackendRegistrable { + ? (options?: TOptions) => BackendFeature + : (options: TOptions) => BackendFeature { return (options?: TOptions) => ({ id: `${config.pluginId}.${config.moduleId}`, register(register: BackendInitRegistry) { diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts index 342a922dbd..2ad414beca 100644 --- a/packages/backend-plugin-api/src/wiring/index.ts +++ b/packages/backend-plugin-api/src/wiring/index.ts @@ -22,6 +22,6 @@ export { } from './factories'; export type { BackendInitRegistry, - BackendRegistrable, + BackendFeature, ExtensionPoint, } from './types'; diff --git a/packages/backend-plugin-api/src/wiring/types.ts b/packages/backend-plugin-api/src/wiring/types.ts index 65525fd8aa..3f01879f80 100644 --- a/packages/backend-plugin-api/src/wiring/types.ts +++ b/packages/backend-plugin-api/src/wiring/types.ts @@ -48,7 +48,7 @@ export interface BackendInitRegistry { } /** @public */ -export interface BackendRegistrable { +export interface BackendFeature { id: string; register(reg: BackendInitRegistry): void; } diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index c7bc987cb8..30dbe7f804 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -19,7 +19,7 @@ import { AnyServiceFactory, ServiceRef, createServiceFactory, - BackendRegistrable, + BackendFeature, } from '@backstage/backend-plugin-api'; /** @alpha */ @@ -53,7 +53,7 @@ export function createTestBackend( /** @alpha */ export async function startTestBackend( options: TestBackendOptions & { - registrables?: BackendRegistrable[]; + registrables?: BackendFeature[]; }, ): Promise { const { registrables = [], ...otherOptions } = options; From 04525293745fdb736d9918042633a3cc9d077c49 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Aug 2022 14:02:15 +0200 Subject: [PATCH 2/9] BackendInitRegistry -> BackendRegistrationPoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Patrik Oldsberg Signed-off-by: Johan Haals --- .../backend-plugin-api/src/wiring/factories.ts | 14 +++++++++----- packages/backend-plugin-api/src/wiring/index.ts | 2 +- packages/backend-plugin-api/src/wiring/types.ts | 5 +++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts index a057c0d6ec..68f13f6c30 100644 --- a/packages/backend-plugin-api/src/wiring/factories.ts +++ b/packages/backend-plugin-api/src/wiring/factories.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { BackendInitRegistry, BackendFeature, ExtensionPoint } from './types'; +import { + BackendRegistrationPoints, + BackendFeature, + ExtensionPoint, +} from './types'; /** @public */ export function createExtensionPoint(options: { @@ -35,7 +39,7 @@ export function createExtensionPoint(options: { /** @public */ export interface BackendPluginConfig { id: string; - register(reg: BackendInitRegistry, options: TOptions): void; + register(reg: BackendRegistrationPoints, options: TOptions): void; } /** @public */ @@ -46,7 +50,7 @@ export function createBackendPlugin( : (options: TOptions) => BackendFeature { return (options?: TOptions) => ({ id: config.id, - register(register: BackendInitRegistry) { + register(register: BackendRegistrationPoints) { return config.register(register, options!); }, }); @@ -57,7 +61,7 @@ export interface BackendModuleConfig { pluginId: string; moduleId: string; register( - reg: Omit, + reg: Omit, options: TOptions, ): void; } @@ -70,7 +74,7 @@ export function createBackendModule( : (options: TOptions) => BackendFeature { return (options?: TOptions) => ({ id: `${config.pluginId}.${config.moduleId}`, - register(register: BackendInitRegistry) { + register(register: BackendRegistrationPoints) { // TODO: Hide registerExtensionPoint return config.register(register, options!); }, diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts index 2ad414beca..8f85edb2eb 100644 --- a/packages/backend-plugin-api/src/wiring/index.ts +++ b/packages/backend-plugin-api/src/wiring/index.ts @@ -21,7 +21,7 @@ export { createExtensionPoint, } from './factories'; export type { - BackendInitRegistry, + BackendRegistrationPoints, BackendFeature, ExtensionPoint, } from './types'; diff --git a/packages/backend-plugin-api/src/wiring/types.ts b/packages/backend-plugin-api/src/wiring/types.ts index 3f01879f80..1801127ddb 100644 --- a/packages/backend-plugin-api/src/wiring/types.ts +++ b/packages/backend-plugin-api/src/wiring/types.ts @@ -36,7 +36,7 @@ export type ExtensionPoint = { }; /** @public */ -export interface BackendInitRegistry { +export interface BackendRegistrationPoints { registerExtensionPoint( ref: ServiceRef, impl: TExtensionPoint, @@ -49,6 +49,7 @@ export interface BackendInitRegistry { /** @public */ export interface BackendFeature { + // TODO(Rugvip): Try to get rid of the ID at this level, allowing for a feature to register multiple features as a bundle id: string; - register(reg: BackendInitRegistry): void; + register(reg: BackendRegistrationPoints): void; } From 0ef2719b11ba98a167cfafa86787fd71dfa09c96 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Aug 2022 14:13:02 +0200 Subject: [PATCH 3/9] registrables -> feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: Johan Haals --- .../backend-test-utils/src/next/wiring/TestBackend.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 30dbe7f804..150d4ce2af 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -53,13 +53,13 @@ export function createTestBackend( /** @alpha */ export async function startTestBackend( options: TestBackendOptions & { - registrables?: BackendFeature[]; + features?: BackendFeature[]; }, ): Promise { - const { registrables = [], ...otherOptions } = options; + const { features = [], ...otherOptions } = options; const backend = createTestBackend(otherOptions); - for (const reg of registrables) { - backend.add(reg); + for (const feature of features) { + backend.add(feature); } await backend.start(); } From 0d2e6197d8e775fcf9b146f8b7735d2a45b2f893 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Aug 2022 15:30:24 +0200 Subject: [PATCH 4/9] =?UTF-8?q?Refactor=20backend-test-utils=20Co-authored?= =?UTF-8?q?-by:=20Fredrik=20Adel=C3=B6w=20=20Co-authored-by:=20Patrik=20Oldsberg=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Johan Haals --- .../src/wiring/BackendInitializer.ts | 21 +++--- packages/backend-app-api/src/wiring/index.ts | 6 +- packages/backend-app-api/src/wiring/types.ts | 14 +++- .../backend-plugin-api/src/wiring/types.ts | 8 ++- .../src/next/wiring/TestBackend.test.ts | 6 +- .../src/next/wiring/TestBackend.ts | 64 ++++++++++++++----- .../src/next/wiring/index.ts | 2 +- plugins/catalog-node/src/extensions.ts | 4 +- .../extension/ScaffolderCatalogModule.test.ts | 5 +- 9 files changed, 89 insertions(+), 41 deletions(-) diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index 0c2cd3e541..c1b7089827 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -19,15 +19,17 @@ import { ExtensionPoint, ServiceRef, } from '@backstage/backend-plugin-api'; -import { BackendRegisterInit, ServiceHolder } from './types'; - -type ServiceOrExtensionPoint = ExtensionPoint | ServiceRef; +import { + BackendRegisterInit, + ServiceHolder, + ServiceOrExtensionPoint, +} from './types'; export class BackendInitializer { #started = false; #features = new Map(); #registerInits = new Array(); - #extensionPoints = new Map(); + #extensionPoints = new Map, unknown>(); #serviceHolder: ServiceHolder; constructor(serviceHolder: ServiceHolder) { @@ -42,7 +44,7 @@ export class BackendInitializer { await Promise.all( Object.entries(deps).map(async ([name, ref]) => [ name, - this.#extensionPoints.get(ref) || + this.#extensionPoints.get(ref as ExtensionPoint) || (await this.#serviceHolder.get(ref as ServiceRef)!( pluginId, )), @@ -66,7 +68,7 @@ export class BackendInitializer { this.#started = true; for (const [feature] of this.#features) { - const provides = new Set>(); + const provides = new Set>(); let registerInit: BackendRegisterInit | undefined = undefined; @@ -129,13 +131,13 @@ export class BackendInitializer { for (const registerInit of registerInitsToOrder) { const unInitializedDependents = []; - for (const serviceRef of registerInit.provides) { + for (const provided of registerInit.provides) { if ( registerInitsToOrder.some( - init => init !== registerInit && init.consumes.has(serviceRef), + init => init !== registerInit && init.consumes.has(provided), ) ) { - unInitializedDependents.push(serviceRef); + unInitializedDependents.push(provided); } } @@ -148,6 +150,7 @@ export class BackendInitializer { registerInitsToOrder = registerInitsToOrder.filter(r => !toRemove.has(r)); } + return orderedRegisterInits; } } diff --git a/packages/backend-app-api/src/wiring/index.ts b/packages/backend-app-api/src/wiring/index.ts index d6e4de9145..2f55076922 100644 --- a/packages/backend-app-api/src/wiring/index.ts +++ b/packages/backend-app-api/src/wiring/index.ts @@ -14,5 +14,9 @@ * limitations under the License. */ -export type { Backend, CreateSpecializedBackendOptions } from './types'; +export type { + Backend, + CreateSpecializedBackendOptions, + ServiceOrExtensionPoint, +} from './types'; export { createSpecializedBackend } from './types'; diff --git a/packages/backend-app-api/src/wiring/types.ts b/packages/backend-app-api/src/wiring/types.ts index cdd4578789..0ca40b5660 100644 --- a/packages/backend-app-api/src/wiring/types.ts +++ b/packages/backend-app-api/src/wiring/types.ts @@ -17,6 +17,7 @@ import { AnyServiceFactory, BackendFeature, + ExtensionPoint, FactoryFunc, ServiceRef, } from '@backstage/backend-plugin-api'; @@ -32,9 +33,9 @@ export interface Backend { export interface BackendRegisterInit { id: string; - consumes: Set>; - provides: Set>; - deps: { [name: string]: ServiceRef }; + consumes: Set; + provides: Set; + deps: { [name: string]: ServiceOrExtensionPoint }; init: (deps: { [name: string]: unknown }) => Promise; } @@ -57,3 +58,10 @@ export function createSpecializedBackend( ): Backend { return new BackstageBackend(options.services); } + +/** + * @public + */ +export type ServiceOrExtensionPoint = + | ExtensionPoint + | ServiceRef; diff --git a/packages/backend-plugin-api/src/wiring/types.ts b/packages/backend-plugin-api/src/wiring/types.ts index 1801127ddb..cdb56ea2e4 100644 --- a/packages/backend-plugin-api/src/wiring/types.ts +++ b/packages/backend-plugin-api/src/wiring/types.ts @@ -38,12 +38,14 @@ export type ExtensionPoint = { /** @public */ export interface BackendRegistrationPoints { registerExtensionPoint( - ref: ServiceRef, + ref: ExtensionPoint, impl: TExtensionPoint, ): void; registerInit(options: { - deps: { [name in keyof Deps]: ServiceRef }; - init: (deps: Deps) => Promise; + deps: { + [name in keyof Deps]: ServiceRef | ExtensionPoint; + }; + init(deps: Deps): Promise; }): void; } diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts index 279667560f..6b3f8e270f 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts @@ -19,12 +19,12 @@ import { createServiceFactory, createServiceRef, } from '@backstage/backend-plugin-api'; -import { createTestBackend, startTestBackend } from './TestBackend'; +import { startTestBackend } from './TestBackend'; describe('TestBackend', () => { it('should get a type error if service implementation does not match', () => { const serviceRef = createServiceRef<{ a: string; b: string }>({ id: 'a' }); - const backend = createTestBackend({ + const backend = startTestBackend({ services: [ [serviceRef, { a: 'a' }], [serviceRef, { a: 'a', b: 'b' }], @@ -68,7 +68,7 @@ describe('TestBackend', () => { await startTestBackend({ services: [sf], - registrables: [testModule({})], + features: [testModule({})], }); expect(testFn).toBeCalledWith('winning'); diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index 150d4ce2af..b9276fa9b9 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -14,31 +14,54 @@ * limitations under the License. */ -import { Backend, createSpecializedBackend } from '@backstage/backend-app-api'; +import { createSpecializedBackend } from '@backstage/backend-app-api'; import { AnyServiceFactory, ServiceRef, createServiceFactory, BackendFeature, + ExtensionPoint, } from '@backstage/backend-plugin-api'; /** @alpha */ -export interface TestBackendOptions { - services: readonly [ +export interface TestBackendOptions< + TServices extends any[], + TExtensionPoints extends any[], +> { + services?: readonly [ ...{ [index in keyof TServices]: | AnyServiceFactory | [ServiceRef, Partial]; }, ]; + extensionPoints?: readonly [ + ...{ + [index in keyof TExtensionPoints]: [ + ExtensionPoint, + Partial, + ]; + }, + ]; + features?: BackendFeature[]; } /** @alpha */ -export function createTestBackend( - options: TestBackendOptions, -): Backend { - const factories = options.services?.map(serviceDef => { +export async function startTestBackend< + TServices extends any[], + TExtensionPoints extends any[], +>(options: TestBackendOptions): Promise { + const { + services = [], + extensionPoints = [], + features = [], + ...otherOptions + } = options; + + const factories = services.map(serviceDef => { if (Array.isArray(serviceDef)) { + // if type is ExtensionPoint? + // do something differently? return createServiceFactory({ service: serviceDef[0], deps: {}, @@ -47,19 +70,26 @@ export function createTestBackend( } return serviceDef as AnyServiceFactory; }); - return createSpecializedBackend({ services: factories ?? [] }); -} -/** @alpha */ -export async function startTestBackend( - options: TestBackendOptions & { - features?: BackendFeature[]; - }, -): Promise { - const { features = [], ...otherOptions } = options; - const backend = createTestBackend(otherOptions); + const backend = createSpecializedBackend({ + ...otherOptions, + services: factories, + }); + + backend.add({ + id: `---test-extension-point-registrar`, + register(reg) { + for (const [ref, impl] of extensionPoints) { + reg.registerExtensionPoint(ref, impl); + } + + reg.registerInit({ deps: {}, async init() {} }); + }, + }); + for (const feature of features) { backend.add(feature); } + await backend.start(); } diff --git a/packages/backend-test-utils/src/next/wiring/index.ts b/packages/backend-test-utils/src/next/wiring/index.ts index 18a35c2b66..eb7b773e33 100644 --- a/packages/backend-test-utils/src/next/wiring/index.ts +++ b/packages/backend-test-utils/src/next/wiring/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export { createTestBackend, startTestBackend } from './TestBackend'; +export { startTestBackend } from './TestBackend'; export type { TestBackendOptions } from './TestBackend'; diff --git a/plugins/catalog-node/src/extensions.ts b/plugins/catalog-node/src/extensions.ts index 9f76946d30..80fbd7630f 100644 --- a/plugins/catalog-node/src/extensions.ts +++ b/plugins/catalog-node/src/extensions.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createServiceRef } from '@backstage/backend-plugin-api'; +import { createExtensionPoint } from '@backstage/backend-plugin-api'; import { EntityProvider } from './api'; import { CatalogProcessor } from './api/processor'; @@ -29,6 +29,6 @@ export interface CatalogProcessingExtensionPoint { * @alpha */ export const catalogProcessingExtensionPoint = - createServiceRef({ + createExtensionPoint({ id: 'catalog.processing', }); diff --git a/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts index a969d80a1b..e8ed3bf505 100644 --- a/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts +++ b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts @@ -23,8 +23,9 @@ describe('ScaffolderCatalogModule', () => { it('should register the extension point', async () => { const extensionPoint = { addProcessor: jest.fn() }; await startTestBackend({ - services: [[catalogProcessingExtensionPoint, extensionPoint]], - registrables: [scaffolderCatalogModule({})], + services: [], + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + features: [scaffolderCatalogModule({})], }); expect(extensionPoint.addProcessor).toHaveBeenCalledWith( From 3f37ff4c42a90a403e05524d0027513743d75bf9 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Aug 2022 15:32:25 +0200 Subject: [PATCH 5/9] chore: remove unused parameter Signed-off-by: Johan Haals --- .../src/extension/ScaffolderCatalogModule.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts index e8ed3bf505..1ced3dd676 100644 --- a/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts +++ b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.test.ts @@ -23,7 +23,6 @@ describe('ScaffolderCatalogModule', () => { it('should register the extension point', async () => { const extensionPoint = { addProcessor: jest.fn() }; await startTestBackend({ - services: [], extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], features: [scaffolderCatalogModule({})], }); From 0599732ec0dd8b392f525ce24ababa38da541945 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Aug 2022 15:40:23 +0200 Subject: [PATCH 6/9] add changeset Signed-off-by: Johan Haals --- .changeset/cyan-pens-suffer.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changeset/cyan-pens-suffer.md diff --git a/.changeset/cyan-pens-suffer.md b/.changeset/cyan-pens-suffer.md new file mode 100644 index 0000000000..b7d3a3a096 --- /dev/null +++ b/.changeset/cyan-pens-suffer.md @@ -0,0 +1,8 @@ +--- +'@backstage/backend-app-api': patch +'@backstage/backend-plugin-api': patch +'@backstage/backend-test-utils': patch +'@backstage/plugin-catalog-node': patch +--- + +Refactored experimental backend system with new type names. From c2e7491890a6ac0deb360e90e6c075f77fdee659 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Aug 2022 15:58:36 +0200 Subject: [PATCH 7/9] backend-app-api: Improve error handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Fredrik Adelöw Signed-off-by: Johan Haals --- .../src/wiring/BackendInitializer.ts | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index c1b7089827..afb5250504 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -40,17 +40,35 @@ export class BackendInitializer { deps: { [name: string]: ServiceOrExtensionPoint }, pluginId: string, ) { - return Object.fromEntries( - await Promise.all( - Object.entries(deps).map(async ([name, ref]) => [ - name, - this.#extensionPoints.get(ref as ExtensionPoint) || - (await this.#serviceHolder.get(ref as ServiceRef)!( - pluginId, - )), - ]), - ), - ); + const result = new Map(); + const missingRefs = new Set(); + + for (const [name, ref] of Object.entries(deps)) { + const extensionPoint = this.#extensionPoints.get( + ref as ExtensionPoint, + ); + if (extensionPoint) { + result.set(name, extensionPoint); + } else { + const factory = await this.#serviceHolder.get( + ref as ServiceRef, + ); + if (factory) { + result.set(name, await factory(pluginId)); + } else { + missingRefs.add(ref); + } + } + } + + if (missingRefs.size > 0) { + const missing = Array.from(missingRefs).join(', '); + throw new Error( + `No extension point or service available for the following ref(s): ${missing}`, + ); + } + + return Object.fromEntries(result); } add(feature: BackendFeature, options?: TOptions) { @@ -61,7 +79,6 @@ export class BackendInitializer { } async start(): Promise { - console.log(`Starting backend`); if (this.#started) { throw new Error('Backend has already started'); } @@ -72,7 +89,6 @@ export class BackendInitializer { let registerInit: BackendRegisterInit | undefined = undefined; - console.log('Registering', feature.id); feature.register({ registerExtensionPoint: (extensionPointRef, impl) => { if (registerInit) { @@ -107,8 +123,6 @@ export class BackendInitializer { this.#registerInits.push(registerInit); } - this.validateSetup(); - const orderedRegisterResults = this.#resolveInitOrder(this.#registerInits); for (const registerInit of orderedRegisterResults) { @@ -117,8 +131,6 @@ export class BackendInitializer { } } - private validateSetup() {} - #resolveInitOrder(registerInits: Array) { let registerInitsToOrder = registerInits.slice(); const orderedRegisterInits = new Array(); @@ -142,7 +154,6 @@ export class BackendInitializer { } if (unInitializedDependents.length === 0) { - console.log(`DEBUG: pushed ${registerInit.id} to results`); orderedRegisterInits.push(registerInit); toRemove.add(registerInit); } From b76eeaf302b691705ba9737d59e36aa3f87efc6a Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Aug 2022 15:58:52 +0200 Subject: [PATCH 8/9] add api reports Signed-off-by: Johan Haals --- packages/backend-app-api/api-report.md | 11 +++++- packages/backend-plugin-api/api-report.md | 48 +++++++++++------------ packages/backend-test-utils/api-report.md | 36 ++++++++++------- plugins/catalog-backend/api-report.md | 4 +- plugins/catalog-node/api-report.md | 4 +- plugins/scaffolder-backend/api-report.md | 4 +- 6 files changed, 61 insertions(+), 46 deletions(-) diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index 130ad6d1f1..26c8179771 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -4,8 +4,9 @@ ```ts import { AnyServiceFactory } from '@backstage/backend-plugin-api'; -import { BackendRegistrable } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; +import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { HttpRouterService } from '@backstage/backend-plugin-api'; import { Logger } from '@backstage/backend-plugin-api'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; @@ -15,13 +16,14 @@ import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { ServiceFactory } from '@backstage/backend-plugin-api'; +import { ServiceRef } from '@backstage/backend-plugin-api'; import { TokenManager } from '@backstage/backend-common'; import { UrlReader } from '@backstage/backend-common'; // @public (undocumented) export interface Backend { // (undocumented) - add(extension: BackendRegistrable): void; + add(feature: BackendFeature): void; // (undocumented) start(): Promise; } @@ -85,6 +87,11 @@ export const schedulerFactory: ServiceFactory< {} >; +// @public (undocumented) +export type ServiceOrExtensionPoint = + | ExtensionPoint + | ServiceRef; + // @public (undocumented) export const tokenManagerFactory: ServiceFactory< TokenManager, diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 44791f5744..fa5097be64 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -26,23 +26,11 @@ export type AnyServiceFactory = ServiceFactory< >; // @public (undocumented) -export interface BackendInitRegistry { +export interface BackendFeature { // (undocumented) - registerExtensionPoint( - ref: ServiceRef, - impl: TExtensionPoint, - ): void; + id: string; // (undocumented) - registerInit< - Deps extends { - [name in string]: unknown; - }, - >(options: { - deps: { - [name in keyof Deps]: ServiceRef; - }; - init: (deps: Deps) => Promise; - }): void; + register(reg: BackendRegistrationPoints): void; } // @public (undocumented) @@ -53,7 +41,7 @@ export interface BackendModuleConfig { pluginId: string; // (undocumented) register( - reg: Omit, + reg: Omit, options: TOptions, ): void; } @@ -63,15 +51,27 @@ export interface BackendPluginConfig { // (undocumented) id: string; // (undocumented) - register(reg: BackendInitRegistry, options: TOptions): void; + register(reg: BackendRegistrationPoints, options: TOptions): void; } // @public (undocumented) -export interface BackendRegistrable { +export interface BackendRegistrationPoints { // (undocumented) - id: string; + registerExtensionPoint( + ref: ExtensionPoint, + impl: TExtensionPoint, + ): void; // (undocumented) - register(reg: BackendInitRegistry): void; + registerInit< + Deps extends { + [name in string]: unknown; + }, + >(options: { + deps: { + [name in keyof Deps]: ServiceRef | ExtensionPoint; + }; + init(deps: Deps): Promise; + }): void; } // @public (undocumented) @@ -84,15 +84,15 @@ export const configServiceRef: ServiceRef; export function createBackendModule( config: BackendModuleConfig, ): undefined extends TOptions - ? (options?: TOptions) => BackendRegistrable - : (options: TOptions) => BackendRegistrable; + ? (options?: TOptions) => BackendFeature + : (options: TOptions) => BackendFeature; // @public (undocumented) export function createBackendPlugin( config: BackendPluginConfig, ): undefined extends TOptions - ? (options?: TOptions) => BackendRegistrable - : (options: TOptions) => BackendRegistrable; + ? (options?: TOptions) => BackendFeature + : (options: TOptions) => BackendFeature; // @public (undocumented) export function createExtensionPoint(options: { diff --git a/packages/backend-test-utils/api-report.md b/packages/backend-test-utils/api-report.md index 9c75402b43..b8881e659d 100644 --- a/packages/backend-test-utils/api-report.md +++ b/packages/backend-test-utils/api-report.md @@ -4,16 +4,11 @@ ```ts import { AnyServiceFactory } from '@backstage/backend-plugin-api'; -import { Backend } from '@backstage/backend-app-api'; -import { BackendRegistrable } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; +import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { Knex } from 'knex'; import { ServiceRef } from '@backstage/backend-plugin-api'; -// @alpha (undocumented) -export function createTestBackend( - options: TestBackendOptions, -): Backend; - // @public (undocumented) export function isDockerDisabledForTests(): boolean; @@ -25,16 +20,29 @@ export function setupRequestMockHandlers(worker: { }): void; // @alpha (undocumented) -export function startTestBackend( - options: TestBackendOptions & { - registrables?: BackendRegistrable[]; - }, -): Promise; +export function startTestBackend< + TServices extends any[], + TExtensionPoints extends any[], +>(options: TestBackendOptions): Promise; // @alpha (undocumented) -export interface TestBackendOptions { +export interface TestBackendOptions< + TServices extends any[], + TExtensionPoints extends any[], +> { // (undocumented) - services: readonly [ + extensionPoints?: readonly [ + ...{ + [index in keyof TExtensionPoints]: [ + ExtensionPoint, + Partial, + ]; + }, + ]; + // (undocumented) + features?: BackendFeature[]; + // (undocumented) + services?: readonly [ ...{ [index in keyof TServices]: | AnyServiceFactory diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index fa6806933d..55f2d114db 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -5,7 +5,7 @@ ```ts /// -import { BackendRegistrable } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogEntityDocument } from '@backstage/plugin-catalog-common'; import { CatalogProcessor } from '@backstage/plugin-catalog-node'; @@ -224,7 +224,7 @@ export type CatalogPermissionRule = PermissionRule; // @alpha -export const catalogPlugin: (options?: unknown) => BackendRegistrable; +export const catalogPlugin: (options?: unknown) => BackendFeature; // @public (undocumented) export interface CatalogProcessingEngine { diff --git a/plugins/catalog-node/api-report.md b/plugins/catalog-node/api-report.md index 47a9bdf45c..46e3efef99 100644 --- a/plugins/catalog-node/api-report.md +++ b/plugins/catalog-node/api-report.md @@ -7,8 +7,8 @@ import { CompoundEntityRef } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; +import { ExtensionPoint } from '@backstage/backend-plugin-api'; import { JsonValue } from '@backstage/types'; -import { ServiceRef } from '@backstage/backend-plugin-api'; // @alpha (undocumented) export interface CatalogProcessingExtensionPoint { @@ -19,7 +19,7 @@ export interface CatalogProcessingExtensionPoint { } // @alpha (undocumented) -export const catalogProcessingExtensionPoint: ServiceRef; +export const catalogProcessingExtensionPoint: ExtensionPoint; // @public (undocumented) export type CatalogProcessor = { diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 6e7220964d..96a4d7b400 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -5,7 +5,7 @@ ```ts /// -import { BackendRegistrable } from '@backstage/backend-plugin-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; @@ -566,7 +566,7 @@ export type RunCommandOptions = { }; // @alpha -export const scaffolderCatalogModule: (options?: unknown) => BackendRegistrable; +export const scaffolderCatalogModule: (options?: unknown) => BackendFeature; // @public (undocumented) export class ScaffolderEntitiesProcessor implements CatalogProcessor { From 91dd47a6a182641d880b265244f8f2e2114a87a1 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 12 Aug 2022 16:09:17 +0200 Subject: [PATCH 9/9] chore: update backend tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Fredrik Adelöw Signed-off-by: Johan Haals --- .../src/next/wiring/TestBackend.test.ts | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts index 6b3f8e270f..488a258388 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.test.ts @@ -16,16 +16,25 @@ import { createBackendModule, + createExtensionPoint, createServiceFactory, createServiceRef, } from '@backstage/backend-plugin-api'; import { startTestBackend } from './TestBackend'; describe('TestBackend', () => { - it('should get a type error if service implementation does not match', () => { - const serviceRef = createServiceRef<{ a: string; b: string }>({ id: 'a' }); - const backend = startTestBackend({ + it('should get a type error if service implementation does not match', async () => { + type Obj = { a: string; b: string }; + const serviceRef = createServiceRef({ id: 'a' }); + const extensionPoint1 = createExtensionPoint({ id: 'b1' }); + const extensionPoint2 = createExtensionPoint({ id: 'b2' }); + const extensionPoint3 = createExtensionPoint({ id: 'b3' }); + const extensionPoint4 = createExtensionPoint({ id: 'b4' }); + const extensionPoint5 = createExtensionPoint({ id: 'b5' }); + await startTestBackend({ services: [ + // @ts-expect-error + [extensionPoint1, { a: 'a' }], [serviceRef, { a: 'a' }], [serviceRef, { a: 'a', b: 'b' }], // @ts-expect-error @@ -35,8 +44,20 @@ describe('TestBackend', () => { // @ts-expect-error [serviceRef, { a: 'a', b: 'b', c: 'c' }], ], + extensionPoints: [ + // @ts-expect-error + [serviceRef, { a: 'a' }], + [extensionPoint1, { a: 'a' }], + [extensionPoint2, { a: 'a', b: 'b' }], + // @ts-expect-error + [extensionPoint3, { c: 'c' }], + // @ts-expect-error + [extensionPoint4, { a: 'a', c: 'c' }], + // @ts-expect-error + [extensionPoint5, { a: 'a', b: 'b', c: 'c' }], + ], }); - expect(backend).toBeDefined(); + expect(1).toBe(1); }); it('should start the test backend', async () => {