Merge pull request #13124 from backstage/mob/feature

backend-app-api: Rename types
This commit is contained in:
Johan Haals
2022-08-15 13:24:15 +02:00
committed by GitHub
19 changed files with 242 additions and 141 deletions
+8
View File
@@ -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.
+9 -2
View File
@@ -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<void>;
}
@@ -85,6 +87,11 @@ export const schedulerFactory: ServiceFactory<
{}
>;
// @public (undocumented)
export type ServiceOrExtensionPoint<T = unknown> =
| ExtensionPoint<T>
| ServiceRef<T>;
// @public (undocumented)
export const tokenManagerFactory: ServiceFactory<
TokenManager,
@@ -15,19 +15,21 @@
*/
import {
BackendRegistrable,
BackendFeature,
ExtensionPoint,
ServiceRef,
} from '@backstage/backend-plugin-api';
import { BackendRegisterInit, ServiceHolder } from './types';
type ServiceOrExtensionPoint = ExtensionPoint<unknown> | ServiceRef<unknown>;
import {
BackendRegisterInit,
ServiceHolder,
ServiceOrExtensionPoint,
} from './types';
export class BackendInitializer {
#started = false;
#extensions = new Map<BackendRegistrable, unknown>();
#features = new Map<BackendFeature, unknown>();
#registerInits = new Array<BackendRegisterInit>();
#extensionPoints = new Map<ServiceOrExtensionPoint, unknown>();
#extensionPoints = new Map<ExtensionPoint<unknown>, unknown>();
#serviceHolder: ServiceHolder;
constructor(serviceHolder: ServiceHolder) {
@@ -38,42 +40,56 @@ 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) ||
(await this.#serviceHolder.get(ref as ServiceRef<unknown>)!(
pluginId,
)),
]),
),
);
}
const result = new Map<string, unknown>();
const missingRefs = new Set<ServiceOrExtensionPoint>();
add<TOptions>(extension: BackendRegistrable, options?: TOptions) {
if (this.#started) {
for (const [name, ref] of Object.entries(deps)) {
const extensionPoint = this.#extensionPoints.get(
ref as ExtensionPoint<unknown>,
);
if (extensionPoint) {
result.set(name, extensionPoint);
} else {
const factory = await this.#serviceHolder.get(
ref as ServiceRef<unknown>,
);
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(
'extension can not be added after the backend has started',
`No extension point or service available for the following ref(s): ${missing}`,
);
}
this.#extensions.set(extension, options);
return Object.fromEntries(result);
}
add<TOptions>(feature: BackendFeature, options?: TOptions) {
if (this.#started) {
throw new Error('feature can not be added after the backend has started');
}
this.#features.set(feature, options);
}
async start(): Promise<void> {
console.log(`Starting backend`);
if (this.#started) {
throw new Error('Backend has already started');
}
this.#started = true;
for (const [extension] of this.#extensions) {
const provides = new Set<ServiceRef<unknown>>();
for (const [feature] of this.#features) {
const provides = new Set<ExtensionPoint<unknown>>();
let registerInit: BackendRegisterInit | undefined = undefined;
console.log('Registering', extension.id);
extension.register({
feature.register({
registerExtensionPoint: (extensionPointRef, impl) => {
if (registerInit) {
throw new Error('registerExtensionPoint called after registerInit');
@@ -89,7 +105,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,15 +116,13 @@ 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}`,
);
}
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<BackendRegisterInit>) {
let registerInitsToOrder = registerInits.slice();
const orderedRegisterInits = new Array<BackendRegisterInit>();
@@ -131,18 +143,17 @@ 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);
}
}
if (unInitializedDependents.length === 0) {
console.log(`DEBUG: pushed ${registerInit.id} to results`);
orderedRegisterInits.push(registerInit);
toRemove.add(registerInit);
}
@@ -150,6 +161,7 @@ export class BackendInitializer {
registerInitsToOrder = registerInitsToOrder.filter(r => !toRemove.has(r));
}
return orderedRegisterInits;
}
}
@@ -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<void> {
+5 -1
View File
@@ -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';
+13 -5
View File
@@ -16,7 +16,8 @@
import {
AnyServiceFactory,
BackendRegistrable,
BackendFeature,
ExtensionPoint,
FactoryFunc,
ServiceRef,
} from '@backstage/backend-plugin-api';
@@ -26,15 +27,15 @@ import { BackstageBackend } from './BackstageBackend';
* @public
*/
export interface Backend {
add(extension: BackendRegistrable): void;
add(feature: BackendFeature): void;
start(): Promise<void>;
}
export interface BackendRegisterInit {
id: string;
consumes: Set<ServiceRef<unknown>>;
provides: Set<ServiceRef<unknown>>;
deps: { [name: string]: ServiceRef<unknown> };
consumes: Set<ServiceOrExtensionPoint>;
provides: Set<ServiceOrExtensionPoint>;
deps: { [name: string]: ServiceOrExtensionPoint };
init: (deps: { [name: string]: unknown }) => Promise<void>;
}
@@ -57,3 +58,10 @@ export function createSpecializedBackend(
): Backend {
return new BackstageBackend(options.services);
}
/**
* @public
*/
export type ServiceOrExtensionPoint<T = unknown> =
| ExtensionPoint<T>
| ServiceRef<T>;
+24 -24
View File
@@ -26,23 +26,11 @@ export type AnyServiceFactory = ServiceFactory<
>;
// @public (undocumented)
export interface BackendInitRegistry {
export interface BackendFeature {
// (undocumented)
registerExtensionPoint<TExtensionPoint>(
ref: ServiceRef<TExtensionPoint>,
impl: TExtensionPoint,
): void;
id: string;
// (undocumented)
registerInit<
Deps extends {
[name in string]: unknown;
},
>(options: {
deps: {
[name in keyof Deps]: ServiceRef<Deps[name]>;
};
init: (deps: Deps) => Promise<void>;
}): void;
register(reg: BackendRegistrationPoints): void;
}
// @public (undocumented)
@@ -53,7 +41,7 @@ export interface BackendModuleConfig<TOptions> {
pluginId: string;
// (undocumented)
register(
reg: Omit<BackendInitRegistry, 'registerExtensionPoint'>,
reg: Omit<BackendRegistrationPoints, 'registerExtensionPoint'>,
options: TOptions,
): void;
}
@@ -63,15 +51,27 @@ export interface BackendPluginConfig<TOptions> {
// (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<TExtensionPoint>(
ref: ExtensionPoint<TExtensionPoint>,
impl: TExtensionPoint,
): void;
// (undocumented)
register(reg: BackendInitRegistry): void;
registerInit<
Deps extends {
[name in string]: unknown;
},
>(options: {
deps: {
[name in keyof Deps]: ServiceRef<Deps[name]> | ExtensionPoint<Deps[name]>;
};
init(deps: Deps): Promise<void>;
}): void;
}
// @public (undocumented)
@@ -84,15 +84,15 @@ export const configServiceRef: ServiceRef<Config>;
export function createBackendModule<TOptions>(
config: BackendModuleConfig<TOptions>,
): undefined extends TOptions
? (options?: TOptions) => BackendRegistrable
: (options: TOptions) => BackendRegistrable;
? (options?: TOptions) => BackendFeature
: (options: TOptions) => BackendFeature;
// @public (undocumented)
export function createBackendPlugin<TOptions>(
config: BackendPluginConfig<TOptions>,
): undefined extends TOptions
? (options?: TOptions) => BackendRegistrable
: (options: TOptions) => BackendRegistrable;
? (options?: TOptions) => BackendFeature
: (options: TOptions) => BackendFeature;
// @public (undocumented)
export function createExtensionPoint<T>(options: {
@@ -15,8 +15,8 @@
*/
import {
BackendInitRegistry,
BackendRegistrable,
BackendRegistrationPoints,
BackendFeature,
ExtensionPoint,
} from './types';
@@ -39,18 +39,18 @@ export function createExtensionPoint<T>(options: {
/** @public */
export interface BackendPluginConfig<TOptions> {
id: string;
register(reg: BackendInitRegistry, options: TOptions): void;
register(reg: BackendRegistrationPoints, options: TOptions): void;
}
/** @public */
export function createBackendPlugin<TOptions>(
config: BackendPluginConfig<TOptions>,
): undefined extends TOptions
? (options?: TOptions) => BackendRegistrable
: (options: TOptions) => BackendRegistrable {
? (options?: TOptions) => BackendFeature
: (options: TOptions) => BackendFeature {
return (options?: TOptions) => ({
id: config.id,
register(register: BackendInitRegistry) {
register(register: BackendRegistrationPoints) {
return config.register(register, options!);
},
});
@@ -61,7 +61,7 @@ export interface BackendModuleConfig<TOptions> {
pluginId: string;
moduleId: string;
register(
reg: Omit<BackendInitRegistry, 'registerExtensionPoint'>,
reg: Omit<BackendRegistrationPoints, 'registerExtensionPoint'>,
options: TOptions,
): void;
}
@@ -70,11 +70,11 @@ export interface BackendModuleConfig<TOptions> {
export function createBackendModule<TOptions>(
config: BackendModuleConfig<TOptions>,
): 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) {
register(register: BackendRegistrationPoints) {
// TODO: Hide registerExtensionPoint
return config.register(register, options!);
},
@@ -21,7 +21,7 @@ export {
createExtensionPoint,
} from './factories';
export type {
BackendInitRegistry,
BackendRegistrable,
BackendRegistrationPoints,
BackendFeature,
ExtensionPoint,
} from './types';
@@ -36,19 +36,22 @@ export type ExtensionPoint<T> = {
};
/** @public */
export interface BackendInitRegistry {
export interface BackendRegistrationPoints {
registerExtensionPoint<TExtensionPoint>(
ref: ServiceRef<TExtensionPoint>,
ref: ExtensionPoint<TExtensionPoint>,
impl: TExtensionPoint,
): void;
registerInit<Deps extends { [name in string]: unknown }>(options: {
deps: { [name in keyof Deps]: ServiceRef<Deps[name]> };
init: (deps: Deps) => Promise<void>;
deps: {
[name in keyof Deps]: ServiceRef<Deps[name]> | ExtensionPoint<Deps[name]>;
};
init(deps: Deps): Promise<void>;
}): void;
}
/** @public */
export interface BackendRegistrable {
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;
}
+22 -14
View File
@@ -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<TServices extends any[]>(
options: TestBackendOptions<TServices>,
): Backend;
// @public (undocumented)
export function isDockerDisabledForTests(): boolean;
@@ -25,16 +20,29 @@ export function setupRequestMockHandlers(worker: {
}): void;
// @alpha (undocumented)
export function startTestBackend<TServices extends any[]>(
options: TestBackendOptions<TServices> & {
registrables?: BackendRegistrable[];
},
): Promise<void>;
export function startTestBackend<
TServices extends any[],
TExtensionPoints extends any[],
>(options: TestBackendOptions<TServices, TExtensionPoints>): Promise<void>;
// @alpha (undocumented)
export interface TestBackendOptions<TServices extends any[]> {
export interface TestBackendOptions<
TServices extends any[],
TExtensionPoints extends any[],
> {
// (undocumented)
services: readonly [
extensionPoints?: readonly [
...{
[index in keyof TExtensionPoints]: [
ExtensionPoint<TExtensionPoints[index]>,
Partial<TExtensionPoints[index]>,
];
},
];
// (undocumented)
features?: BackendFeature[];
// (undocumented)
services?: readonly [
...{
[index in keyof TServices]:
| AnyServiceFactory
@@ -16,16 +16,25 @@
import {
createBackendModule,
createExtensionPoint,
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({
it('should get a type error if service implementation does not match', async () => {
type Obj = { a: string; b: string };
const serviceRef = createServiceRef<Obj>({ id: 'a' });
const extensionPoint1 = createExtensionPoint<Obj>({ id: 'b1' });
const extensionPoint2 = createExtensionPoint<Obj>({ id: 'b2' });
const extensionPoint3 = createExtensionPoint<Obj>({ id: 'b3' });
const extensionPoint4 = createExtensionPoint<Obj>({ id: 'b4' });
const extensionPoint5 = createExtensionPoint<Obj>({ 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 () => {
@@ -68,7 +89,7 @@ describe('TestBackend', () => {
await startTestBackend({
services: [sf],
registrables: [testModule({})],
features: [testModule({})],
});
expect(testFn).toBeCalledWith('winning');
@@ -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,
BackendRegistrable,
BackendFeature,
ExtensionPoint,
} from '@backstage/backend-plugin-api';
/** @alpha */
export interface TestBackendOptions<TServices extends any[]> {
services: readonly [
export interface TestBackendOptions<
TServices extends any[],
TExtensionPoints extends any[],
> {
services?: readonly [
...{
[index in keyof TServices]:
| AnyServiceFactory
| [ServiceRef<TServices[index]>, Partial<TServices[index]>];
},
];
extensionPoints?: readonly [
...{
[index in keyof TExtensionPoints]: [
ExtensionPoint<TExtensionPoints[index]>,
Partial<TExtensionPoints[index]>,
];
},
];
features?: BackendFeature[];
}
/** @alpha */
export function createTestBackend<TServices extends any[]>(
options: TestBackendOptions<TServices>,
): Backend {
const factories = options.services?.map(serviceDef => {
export async function startTestBackend<
TServices extends any[],
TExtensionPoints extends any[],
>(options: TestBackendOptions<TServices, TExtensionPoints>): Promise<void> {
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<TServices extends any[]>(
}
return serviceDef as AnyServiceFactory;
});
return createSpecializedBackend({ services: factories ?? [] });
}
/** @alpha */
export async function startTestBackend<TServices extends any[]>(
options: TestBackendOptions<TServices> & {
registrables?: BackendRegistrable[];
},
): Promise<void> {
const { registrables = [], ...otherOptions } = options;
const backend = createTestBackend(otherOptions);
for (const reg of registrables) {
backend.add(reg);
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();
}
@@ -14,5 +14,5 @@
* limitations under the License.
*/
export { createTestBackend, startTestBackend } from './TestBackend';
export { startTestBackend } from './TestBackend';
export type { TestBackendOptions } from './TestBackend';
+2 -2
View File
@@ -5,7 +5,7 @@
```ts
/// <reference types="node" />
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<TParams extends unknown[] = unknown[]> =
PermissionRule<Entity, EntitiesSearchFilter, 'catalog-entity', TParams>;
// @alpha
export const catalogPlugin: (options?: unknown) => BackendRegistrable;
export const catalogPlugin: (options?: unknown) => BackendFeature;
// @public (undocumented)
export interface CatalogProcessingEngine {
+2 -2
View File
@@ -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<CatalogProcessingExtensionPoint>;
export const catalogProcessingExtensionPoint: ExtensionPoint<CatalogProcessingExtensionPoint>;
// @public (undocumented)
export type CatalogProcessor = {
+2 -2
View File
@@ -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<CatalogProcessingExtensionPoint>({
createExtensionPoint<CatalogProcessingExtensionPoint>({
id: 'catalog.processing',
});
+2 -2
View File
@@ -5,7 +5,7 @@
```ts
/// <reference types="node" />
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 {
@@ -23,8 +23,8 @@ describe('ScaffolderCatalogModule', () => {
it('should register the extension point', async () => {
const extensionPoint = { addProcessor: jest.fn() };
await startTestBackend({
services: [[catalogProcessingExtensionPoint, extensionPoint]],
registrables: [scaffolderCatalogModule({})],
extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]],
features: [scaffolderCatalogModule({})],
});
expect(extensionPoint.addProcessor).toHaveBeenCalledWith(