From 8b45adcadd32fa70eee7c9c4a967b6764ec61a4f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 4 Aug 2024 17:37:38 +0200 Subject: [PATCH] backend-plugin-api: internal refactor to split creators Signed-off-by: Patrik Oldsberg --- ...es.test.ts => createBackendModule.test.ts} | 53 +--- .../src/wiring/createBackendModule.ts | 107 ++++++++ .../src/wiring/createBackendPlugin.test.ts | 55 +++++ .../src/wiring/createBackendPlugin.ts | 100 ++++++++ .../src/wiring/createExtensionPoint.test.ts | 27 +++ .../src/wiring/createExtensionPoint.ts | 58 +++++ .../src/wiring/factories.ts | 229 ------------------ .../backend-plugin-api/src/wiring/index.ts | 16 +- 8 files changed, 354 insertions(+), 291 deletions(-) rename packages/backend-plugin-api/src/wiring/{factories.test.ts => createBackendModule.test.ts} (53%) create mode 100644 packages/backend-plugin-api/src/wiring/createBackendModule.ts create mode 100644 packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts create mode 100644 packages/backend-plugin-api/src/wiring/createBackendPlugin.ts create mode 100644 packages/backend-plugin-api/src/wiring/createExtensionPoint.test.ts create mode 100644 packages/backend-plugin-api/src/wiring/createExtensionPoint.ts delete mode 100644 packages/backend-plugin-api/src/wiring/factories.ts diff --git a/packages/backend-plugin-api/src/wiring/factories.test.ts b/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts similarity index 53% rename from packages/backend-plugin-api/src/wiring/factories.test.ts rename to packages/backend-plugin-api/src/wiring/createBackendModule.test.ts index 6d5c1f39cd..31496020bf 100644 --- a/packages/backend-plugin-api/src/wiring/factories.test.ts +++ b/packages/backend-plugin-api/src/wiring/createBackendModule.test.ts @@ -14,60 +14,9 @@ * limitations under the License. */ -import { - createBackendModule, - createBackendPlugin, - createExtensionPoint, -} from './factories'; +import { createBackendModule } from './createBackendModule'; import { InternalBackendFeature } from './types'; -describe('createExtensionPoint', () => { - it('should create an ExtensionPoint', () => { - const extensionPoint = createExtensionPoint({ id: 'x' }); - expect(extensionPoint).toBeDefined(); - expect(extensionPoint.id).toBe('x'); - expect(() => extensionPoint.T).not.toThrow(); - expect(String(extensionPoint)).toBe('extensionPoint{x}'); - }); -}); - -describe('createBackendPlugin', () => { - it('should create a BackendPlugin', () => { - const result = createBackendPlugin({ - pluginId: 'x', - register(r) { - r.registerInit({ deps: {}, async init() {} }); - }, - }); - - // legacy form - const legacy = result() as unknown as InternalBackendFeature; - expect(legacy.$$type).toEqual('@backstage/BackendFeature'); - expect(legacy.version).toEqual('v1'); - expect(legacy.getRegistrations).toEqual(expect.any(Function)); - - // new form - const plugin = result as unknown as InternalBackendFeature; - expect(plugin.$$type).toEqual('@backstage/BackendFeature'); - expect(plugin.version).toEqual('v1'); - expect(plugin.getRegistrations).toEqual(expect.any(Function)); - expect(plugin.getRegistrations()).toEqual([ - { - type: 'plugin', - pluginId: 'x', - extensionPoints: [], - init: { - deps: expect.any(Object), - func: expect.any(Function), - }, - }, - ]); - - // @ts-expect-error - expect(plugin({ a: 'a' })).toBeDefined(); - }); -}); - describe('createBackendModule', () => { it('should create a BackendModule', () => { const result = createBackendModule({ diff --git a/packages/backend-plugin-api/src/wiring/createBackendModule.ts b/packages/backend-plugin-api/src/wiring/createBackendModule.ts new file mode 100644 index 0000000000..56f041606e --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createBackendModule.ts @@ -0,0 +1,107 @@ +/* + * Copyright 2022 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 { BackendFeatureCompat } from '../types'; +import { + BackendModuleRegistrationPoints, + InternalBackendModuleRegistration, + InternalBackendPluginRegistration, +} from './types'; + +/** + * The configuration options passed to {@link createBackendModule}. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules} + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ +export interface CreateBackendModuleOptions { + /** + * Should exactly match the `id` of the plugin that the module extends. + * + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ + pluginId: string; + + /** + * The ID of this module, used to identify the module and ensure that it is not installed twice. + */ + moduleId: string; + register(reg: BackendModuleRegistrationPoints): void; +} + +/** + * Creates a new backend module for a given plugin. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules} + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ +export function createBackendModule( + options: CreateBackendModuleOptions, +): BackendFeatureCompat { + function getRegistrations() { + const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] = + []; + let init: InternalBackendModuleRegistration['init'] | undefined = undefined; + + options.register({ + registerExtensionPoint(ext, impl) { + if (init) { + throw new Error('registerExtensionPoint called after registerInit'); + } + extensionPoints.push([ext, impl]); + }, + registerInit(regInit) { + if (init) { + throw new Error('registerInit must only be called once'); + } + init = { + deps: regInit.deps, + func: regInit.init, + }; + }, + }); + + if (!init) { + throw new Error( + `registerInit was not called by register in ${options.moduleId} module for ${options.pluginId}`, + ); + } + + return [ + { + type: 'module', + pluginId: options.pluginId, + moduleId: options.moduleId, + extensionPoints, + init, + }, + ]; + } + + function backendFeatureCompatWrapper() { + return backendFeatureCompatWrapper; + } + + Object.assign(backendFeatureCompatWrapper, { + $$type: '@backstage/BackendFeature' as const, + version: 'v1', + getRegistrations, + }); + + return backendFeatureCompatWrapper as BackendFeatureCompat; +} diff --git a/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts b/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts new file mode 100644 index 0000000000..cd1ce164bf --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createBackendPlugin.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2022 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 { createBackendPlugin } from './createBackendPlugin'; +import { InternalBackendFeature } from './types'; + +describe('createBackendPlugin', () => { + it('should create a BackendPlugin', () => { + const result = createBackendPlugin({ + pluginId: 'x', + register(r) { + r.registerInit({ deps: {}, async init() {} }); + }, + }); + + // legacy form + const legacy = result() as unknown as InternalBackendFeature; + expect(legacy.$$type).toEqual('@backstage/BackendFeature'); + expect(legacy.version).toEqual('v1'); + expect(legacy.getRegistrations).toEqual(expect.any(Function)); + + // new form + const plugin = result as unknown as InternalBackendFeature; + expect(plugin.$$type).toEqual('@backstage/BackendFeature'); + expect(plugin.version).toEqual('v1'); + expect(plugin.getRegistrations).toEqual(expect.any(Function)); + expect(plugin.getRegistrations()).toEqual([ + { + type: 'plugin', + pluginId: 'x', + extensionPoints: [], + init: { + deps: expect.any(Object), + func: expect.any(Function), + }, + }, + ]); + + // @ts-expect-error + expect(plugin({ a: 'a' })).toBeDefined(); + }); +}); diff --git a/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts b/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts new file mode 100644 index 0000000000..f5984ef24b --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createBackendPlugin.ts @@ -0,0 +1,100 @@ +/* + * Copyright 2022 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 { BackendFeatureCompat } from '../types'; +import { + BackendPluginRegistrationPoints, + InternalBackendPluginRegistration, +} from './types'; + +/** + * The configuration options passed to {@link createBackendPlugin}. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins} + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ +export interface CreateBackendPluginOptions { + /** + * The ID of this plugin. + * + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ + pluginId: string; + register(reg: BackendPluginRegistrationPoints): void; +} + +/** + * Creates a new backend plugin. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins} + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ +export function createBackendPlugin( + options: CreateBackendPluginOptions, +): BackendFeatureCompat { + function getRegistrations() { + const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] = + []; + let init: InternalBackendPluginRegistration['init'] | undefined = undefined; + + options.register({ + registerExtensionPoint(ext, impl) { + if (init) { + throw new Error('registerExtensionPoint called after registerInit'); + } + extensionPoints.push([ext, impl]); + }, + registerInit(regInit) { + if (init) { + throw new Error('registerInit must only be called once'); + } + init = { + deps: regInit.deps, + func: regInit.init, + }; + }, + }); + + if (!init) { + throw new Error( + `registerInit was not called by register in ${options.pluginId}`, + ); + } + + return [ + { + type: 'plugin', + pluginId: options.pluginId, + extensionPoints, + init, + }, + ]; + } + + function backendFeatureCompatWrapper() { + return backendFeatureCompatWrapper; + } + + Object.assign(backendFeatureCompatWrapper, { + $$type: '@backstage/BackendFeature' as const, + version: 'v1', + getRegistrations, + }); + + return backendFeatureCompatWrapper as BackendFeatureCompat; +} diff --git a/packages/backend-plugin-api/src/wiring/createExtensionPoint.test.ts b/packages/backend-plugin-api/src/wiring/createExtensionPoint.test.ts new file mode 100644 index 0000000000..825063e845 --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createExtensionPoint.test.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2022 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 { createExtensionPoint } from './createExtensionPoint'; + +describe('createExtensionPoint', () => { + it('should create an ExtensionPoint', () => { + const extensionPoint = createExtensionPoint({ id: 'x' }); + expect(extensionPoint).toBeDefined(); + expect(extensionPoint.id).toBe('x'); + expect(() => extensionPoint.T).not.toThrow(); + expect(String(extensionPoint)).toBe('extensionPoint{x}'); + }); +}); diff --git a/packages/backend-plugin-api/src/wiring/createExtensionPoint.ts b/packages/backend-plugin-api/src/wiring/createExtensionPoint.ts new file mode 100644 index 0000000000..9dbae249ea --- /dev/null +++ b/packages/backend-plugin-api/src/wiring/createExtensionPoint.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2022 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 { ExtensionPoint } from './types'; + +/** + * The configuration options passed to {@link createExtensionPoint}. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ +export interface CreateExtensionPointOptions { + /** + * The ID of this extension point. + * + * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} + */ + id: string; +} + +/** + * Creates a new backend extension point. + * + * @public + * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} + */ +export function createExtensionPoint( + options: CreateExtensionPointOptions, +): ExtensionPoint { + return { + id: options.id, + get T(): T { + if (process.env.NODE_ENV === 'test') { + // Avoid throwing errors so tests asserting extensions' properties cannot be easily broken + return null as T; + } + throw new Error(`tried to read ExtensionPoint.T of ${this}`); + }, + toString() { + return `extensionPoint{${options.id}}`; + }, + $$type: '@backstage/ExtensionPoint', + }; +} diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts deleted file mode 100644 index 81e2247152..0000000000 --- a/packages/backend-plugin-api/src/wiring/factories.ts +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright 2022 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 { BackendFeatureCompat } from '../types'; -import { - BackendModuleRegistrationPoints, - BackendPluginRegistrationPoints, - ExtensionPoint, - InternalBackendModuleRegistration, - InternalBackendPluginRegistration, -} from './types'; - -/** - * The configuration options passed to {@link createExtensionPoint}. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ -export interface CreateExtensionPointOptions { - /** - * The ID of this extension point. - * - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ - id: string; -} - -/** - * Creates a new backend extension point. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/extension-points | The architecture of extension points} - */ -export function createExtensionPoint( - options: CreateExtensionPointOptions, -): ExtensionPoint { - return { - id: options.id, - get T(): T { - if (process.env.NODE_ENV === 'test') { - // Avoid throwing errors so tests asserting extensions' properties cannot be easily broken - return null as T; - } - throw new Error(`tried to read ExtensionPoint.T of ${this}`); - }, - toString() { - return `extensionPoint{${options.id}}`; - }, - $$type: '@backstage/ExtensionPoint', - }; -} - -/** - * The configuration options passed to {@link createBackendPlugin}. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins} - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ -export interface CreateBackendPluginOptions { - /** - * The ID of this plugin. - * - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ - pluginId: string; - register(reg: BackendPluginRegistrationPoints): void; -} - -/** - * Creates a new backend plugin. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/plugins | The architecture of plugins} - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ -export function createBackendPlugin( - options: CreateBackendPluginOptions, -): BackendFeatureCompat { - function getRegistrations() { - const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] = - []; - let init: InternalBackendPluginRegistration['init'] | undefined = undefined; - - options.register({ - registerExtensionPoint(ext, impl) { - if (init) { - throw new Error('registerExtensionPoint called after registerInit'); - } - extensionPoints.push([ext, impl]); - }, - registerInit(regInit) { - if (init) { - throw new Error('registerInit must only be called once'); - } - init = { - deps: regInit.deps, - func: regInit.init, - }; - }, - }); - - if (!init) { - throw new Error( - `registerInit was not called by register in ${options.pluginId}`, - ); - } - - return [ - { - type: 'plugin', - pluginId: options.pluginId, - extensionPoints, - init, - }, - ]; - } - - function backendFeatureCompatWrapper() { - return backendFeatureCompatWrapper; - } - - Object.assign(backendFeatureCompatWrapper, { - $$type: '@backstage/BackendFeature' as const, - version: 'v1', - getRegistrations, - }); - - return backendFeatureCompatWrapper as BackendFeatureCompat; -} - -/** - * The configuration options passed to {@link createBackendModule}. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules} - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ -export interface CreateBackendModuleOptions { - /** - * Should exactly match the `id` of the plugin that the module extends. - * - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ - pluginId: string; - - /** - * The ID of this module, used to identify the module and ensure that it is not installed twice. - */ - moduleId: string; - register(reg: BackendModuleRegistrationPoints): void; -} - -/** - * Creates a new backend module for a given plugin. - * - * @public - * @see {@link https://backstage.io/docs/backend-system/architecture/modules | The architecture of modules} - * @see {@link https://backstage.io/docs/backend-system/architecture/naming-patterns | Recommended naming patterns} - */ -export function createBackendModule( - options: CreateBackendModuleOptions, -): BackendFeatureCompat { - function getRegistrations() { - const extensionPoints: InternalBackendPluginRegistration['extensionPoints'] = - []; - let init: InternalBackendModuleRegistration['init'] | undefined = undefined; - - options.register({ - registerExtensionPoint(ext, impl) { - if (init) { - throw new Error('registerExtensionPoint called after registerInit'); - } - extensionPoints.push([ext, impl]); - }, - registerInit(regInit) { - if (init) { - throw new Error('registerInit must only be called once'); - } - init = { - deps: regInit.deps, - func: regInit.init, - }; - }, - }); - - if (!init) { - throw new Error( - `registerInit was not called by register in ${options.moduleId} module for ${options.pluginId}`, - ); - } - - return [ - { - type: 'module', - pluginId: options.pluginId, - moduleId: options.moduleId, - extensionPoints, - init, - }, - ]; - } - - function backendFeatureCompatWrapper() { - return backendFeatureCompatWrapper; - } - - Object.assign(backendFeatureCompatWrapper, { - $$type: '@backstage/BackendFeature' as const, - version: 'v1', - getRegistrations, - }); - - return backendFeatureCompatWrapper as BackendFeatureCompat; -} diff --git a/packages/backend-plugin-api/src/wiring/index.ts b/packages/backend-plugin-api/src/wiring/index.ts index 5197cec050..5f7960e9cd 100644 --- a/packages/backend-plugin-api/src/wiring/index.ts +++ b/packages/backend-plugin-api/src/wiring/index.ts @@ -14,17 +14,13 @@ * limitations under the License. */ -import type { - CreateBackendPluginOptions, - CreateBackendModuleOptions, - CreateExtensionPointOptions, -} from './factories'; +import { type CreateBackendModuleOptions } from './createBackendModule'; +import { type CreateBackendPluginOptions } from './createBackendPlugin'; +import { type CreateExtensionPointOptions } from './createExtensionPoint'; -export { - createBackendModule, - createBackendPlugin, - createExtensionPoint, -} from './factories'; +export { createBackendModule } from './createBackendModule'; +export { createBackendPlugin } from './createBackendPlugin'; +export { createExtensionPoint } from './createExtensionPoint'; export type { BackendModuleRegistrationPoints,