diff --git a/.changeset/breezy-flowers-dance.md b/.changeset/breezy-flowers-dance.md new file mode 100644 index 0000000000..51bd9db5f1 --- /dev/null +++ b/.changeset/breezy-flowers-dance.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-app-api': patch +--- + +Deprecate the `featureDiscoveryServiceFactory` in favor of using `featureDiscoveryLoader` instead. diff --git a/.changeset/calm-brooms-fail.md b/.changeset/calm-brooms-fail.md new file mode 100644 index 0000000000..d99a479d93 --- /dev/null +++ b/.changeset/calm-brooms-fail.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-dynamic-feature-service': patch +--- + +Deprecate the `dynamicPluginsServiceRef`, `dynamicPluginsServiceFactory` and `dynamicPluginsServiceFactoryWithOptions` in favor of using `dynamicPluginsFeatureDiscoveryLoader` or `dynamicPluginsFeatureDiscoveryLoaderWithOptions` instead. diff --git a/.changeset/fuzzy-mails-walk.md b/.changeset/fuzzy-mails-walk.md new file mode 100644 index 0000000000..d00d372415 --- /dev/null +++ b/.changeset/fuzzy-mails-walk.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-plugin-api': patch +--- + +Deprecate the `featureDiscoveryServiceRef` in favor of using `@backstage/backend-app-api#featureDiscoveryLoader` instead. diff --git a/packages/backend-app-api/api-report-alpha.md b/packages/backend-app-api/api-report-alpha.md index 2633321472..8ff4e61390 100644 --- a/packages/backend-app-api/api-report-alpha.md +++ b/packages/backend-app-api/api-report-alpha.md @@ -3,10 +3,14 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { FeatureDiscoveryService } from '@backstage/backend-plugin-api/alpha'; import { ServiceFactory } from '@backstage/backend-plugin-api'; -// @alpha (undocumented) +// @public +export const featureDiscoveryLoader: BackendFeature; + +// @alpha @deprecated (undocumented) export const featureDiscoveryServiceFactory: ServiceFactory< FeatureDiscoveryService, 'root', diff --git a/packages/backend-app-api/src/alpha.ts b/packages/backend-app-api/src/alpha.ts index a334214738..41bf4950d4 100644 --- a/packages/backend-app-api/src/alpha.ts +++ b/packages/backend-app-api/src/alpha.ts @@ -14,4 +14,7 @@ * limitations under the License. */ -export { featureDiscoveryServiceFactory } from './alpha/featureDiscoveryServiceFactory'; +export { + featureDiscoveryLoader, + featureDiscoveryServiceFactory, +} from './alpha/featureDiscoveryServiceFactory'; diff --git a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts index c16a3f6534..e901f0c367 100644 --- a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts +++ b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts @@ -19,6 +19,7 @@ import { RootConfigService, RootLoggerService, coreServices, + createBackendFeatureLoader, createServiceFactory, } from '@backstage/backend-plugin-api'; import { @@ -153,7 +154,10 @@ class PackageDiscoveryService implements FeatureDiscoveryService { } } -/** @alpha */ +/** + * @alpha + * @deprecated The `featureDiscoveryServiceFactory` is deprecated in favor of using {@link featureDiscoveryLoader} instead. + */ export const featureDiscoveryServiceFactory = createServiceFactory({ service: featureDiscoveryServiceRef, deps: { @@ -165,6 +169,36 @@ export const featureDiscoveryServiceFactory = createServiceFactory({ }, }); +/** + * A loader that discovers backend features from the current package.json and its dependencies. + * + * @public + * + * @example + * Using the `featureDiscoveryLoader` loader in a backend instance: + * ```ts + * //... + * import { createBackend } from '@backstage/backend-defaults'; + * import { featureDiscoveryLoader } from '@backstage/backend-app-api'; + * + * const backend = createBackend(); + * backend.add(featureDiscoveryLoader); + * //... + * backend.start(); + * ``` + */ +export const featureDiscoveryLoader = createBackendFeatureLoader({ + deps: { + config: coreServices.rootConfig, + logger: coreServices.rootLogger, + }, + async loader({ config, logger }) { + const service = new PackageDiscoveryService(config, logger); + const { features } = await service.getBackendFeatures(); + return features; + }, +}); + function isBackendFeature(value: unknown): value is BackendFeature { return ( !!value && diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 955e463bb0..65fc21ccde 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -20,32 +20,23 @@ import { loggerServiceFactory } from '@backstage/backend-defaults/logger'; import { createServiceRef, createServiceFactory, - coreServices, createBackendPlugin, createBackendModule, createExtensionPoint, createBackendFeatureLoader, } from '@backstage/backend-plugin-api'; import { BackendInitializer } from './BackendInitializer'; +import { mockServices } from '@backstage/backend-test-utils'; -class MockLogger { - debug() {} - info() {} - warn() {} - error() {} - child() { - return this; - } -} +const requiredRootFactories = [ + mockServices.rootConfig.factory(), + mockServices.rootLogger.factory(), +]; const baseFactories = [ + ...requiredRootFactories, lifecycleServiceFactory, rootLifecycleServiceFactory, - createServiceFactory({ - service: coreServices.rootLogger, - deps: {}, - factory: () => new MockLogger(), - }), loggerServiceFactory, ]; @@ -399,7 +390,7 @@ describe('BackendInitializer', () => { }); it('should forward errors when plugins fail to start', async () => { - const init = new BackendInitializer([]); + const init = new BackendInitializer(requiredRootFactories); init.add( createBackendPlugin({ pluginId: 'test', @@ -419,7 +410,7 @@ describe('BackendInitializer', () => { }); it('should forward errors when modules fail to start', async () => { - const init = new BackendInitializer([]); + const init = new BackendInitializer(requiredRootFactories); init.add(testPlugin); init.add( createBackendModule({ @@ -441,7 +432,7 @@ describe('BackendInitializer', () => { }); it('should reject duplicate plugins', async () => { - const init = new BackendInitializer([]); + const init = new BackendInitializer(requiredRootFactories); init.add( createBackendPlugin({ pluginId: 'test', @@ -470,7 +461,7 @@ describe('BackendInitializer', () => { }); it('should reject duplicate modules', async () => { - const init = new BackendInitializer([]); + const init = new BackendInitializer(requiredRootFactories); init.add(testPlugin); init.add( createBackendModule({ @@ -505,12 +496,8 @@ describe('BackendInitializer', () => { const extA = createExtensionPoint({ id: 'a' }); const extB = createExtensionPoint({ id: 'b' }); const init = new BackendInitializer([ + ...requiredRootFactories, rootLifecycleServiceFactory, - createServiceFactory({ - service: coreServices.rootLogger, - deps: {}, - factory: () => new MockLogger(), - }), ]); init.add(testPlugin); init.add( diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.ts b/packages/backend-app-api/src/wiring/BackendInitializer.ts index b2173fb105..37b424c8ae 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.ts @@ -34,11 +34,11 @@ import type { // eslint-disable-next-line @backstage/no-relative-monorepo-imports import type { InternalServiceFactory } from '../../../backend-plugin-api/src/services/system/types'; import { ForwardedError, ConflictError } from '@backstage/errors'; -import { featureDiscoveryServiceRef } from '@backstage/backend-plugin-api/alpha'; import { DependencyGraph } from '../lib/DependencyGraph'; import { ServiceRegistry } from './ServiceRegistry'; import { createInitializationLogger } from './createInitializationLogger'; import { unwrapFeature } from './helpers'; +import { featureDiscoveryLoader } from '../alpha/featureDiscoveryServiceFactory'; export interface BackendRegisterInit { consumes: Set; @@ -150,25 +150,14 @@ export class BackendInitializer { } async #doStart(): Promise { + this.add(featureDiscoveryLoader); + this.#serviceRegistry.checkForCircularDeps(); for (const feature of this.#registeredFeatures) { this.#addFeature(await feature); } - const featureDiscovery = await this.#serviceRegistry.get( - featureDiscoveryServiceRef, - 'root', - ); - - if (featureDiscovery) { - const { features } = await featureDiscovery.getBackendFeatures(); - for (const feature of features) { - this.#addFeature(unwrapFeature(feature)); - } - this.#serviceRegistry.checkForCircularDeps(); - } - await this.#applyBackendFeatureLoaders(this.#registeredFeatureLoaders); // Initialize all root scoped services diff --git a/packages/backend-dynamic-feature-service/api-report.md b/packages/backend-dynamic-feature-service/api-report.md index 148825dce1..355a7efc94 100644 --- a/packages/backend-dynamic-feature-service/api-report.md +++ b/packages/backend-dynamic-feature-service/api-report.md @@ -112,7 +112,15 @@ export interface DynamicPluginsFactoryOptions { moduleLoader?(logger: LoggerService): ModuleLoader; } -// @public (undocumented) +// @public +export const dynamicPluginsFeatureDiscoveryLoader: BackendFeature; + +// @public +export const dynamicPluginsFeatureDiscoveryLoaderWithOptions: ( + options?: DynamicPluginsFactoryOptions, +) => BackendFeature; + +// @public @deprecated (undocumented) export const dynamicPluginsFeatureDiscoveryServiceFactory: ServiceFactory< FeatureDiscoveryService, 'root', @@ -154,19 +162,19 @@ export const dynamicPluginsSchemasServiceFactoryWithOptions: ( options?: DynamicPluginsSchemasOptions, ) => ServiceFactory; -// @public (undocumented) +// @public @deprecated (undocumented) export const dynamicPluginsServiceFactory: ServiceFactory< DynamicPluginProvider, 'root', 'singleton' >; -// @public (undocumented) +// @public @deprecated (undocumented) export const dynamicPluginsServiceFactoryWithOptions: ( options?: DynamicPluginsFactoryOptions, ) => ServiceFactory; -// @public (undocumented) +// @public @deprecated (undocumented) export const dynamicPluginsServiceRef: ServiceRef< DynamicPluginProvider, 'root', diff --git a/packages/backend-dynamic-feature-service/src/manager/index.ts b/packages/backend-dynamic-feature-service/src/manager/index.ts index 542bab68ac..51d9915a7c 100644 --- a/packages/backend-dynamic-feature-service/src/manager/index.ts +++ b/packages/backend-dynamic-feature-service/src/manager/index.ts @@ -36,6 +36,8 @@ export { dynamicPluginsServiceFactory, dynamicPluginsServiceFactoryWithOptions, dynamicPluginsServiceRef, + dynamicPluginsFeatureDiscoveryLoader, + dynamicPluginsFeatureDiscoveryLoaderWithOptions, } from './plugin-manager'; export type { diff --git a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts index 40746ff440..97ceb08a34 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts @@ -30,6 +30,7 @@ import { BackendFeature, LoggerService, coreServices, + createBackendFeatureLoader, createServiceFactory, createServiceRef, } from '@backstage/backend-plugin-api'; @@ -214,6 +215,7 @@ export class DynamicPluginManager implements DynamicPluginProvider { /** * @public + * @deprecated The `featureDiscoveryService` is deprecated in favor of using {@link dynamicPluginsFeatureDiscoveryLoader} or {@link dynamicPluginsFeatureDiscoveryLoaderWithOptions} instead. */ export const dynamicPluginsServiceRef = createServiceRef( { @@ -231,6 +233,7 @@ export interface DynamicPluginsFactoryOptions { /** * @public + * @deprecated Use {@link dynamicPluginsFeatureDiscoveryLoaderWithOptions} instead. */ export const dynamicPluginsServiceFactoryWithOptions = ( options?: DynamicPluginsFactoryOptions, @@ -253,6 +256,7 @@ export const dynamicPluginsServiceFactoryWithOptions = ( /** * @public + * @deprecated Use {@link dynamicPluginsFeatureDiscoveryLoader} instead. */ export const dynamicPluginsServiceFactory = dynamicPluginsServiceFactoryWithOptions(); @@ -292,6 +296,7 @@ class DynamicPluginsEnabledFeatureDiscoveryService /** * @public + * @deprecated The `featureDiscoveryService` is deprecated in favor of using {@link dynamicPluginsFeatureDiscoveryLoader} or {@link dynamicPluginsFeatureDiscoveryLoaderWithOptions} instead. */ export const dynamicPluginsFeatureDiscoveryServiceFactory = createServiceFactory({ @@ -305,6 +310,69 @@ export const dynamicPluginsFeatureDiscoveryServiceFactory = }, }); +/** + * A function that returns a backend feature loader that uses the dynamic plugins system to discover features. + * + * @public + * + * @example + * Using the `dynamicPluginsFeatureDiscoveryLoaderWithOptions` loader in a backend instance: + * ```ts + * //... + * import { createBackend } from '@backstage/backend-defaults'; + * import { dynamicPluginsFeatureDiscoveryLoaderWithOptions } from '@backstage/backend-dynamic-feature-service'; + * import { myCustomModuleLoader } from './myCustomModuleLoader'; + * + * const backend = createBackend(); + * backend.add(dynamicPluginsFeatureDiscoveryLoaderWithOptions({ + * moduleLoader: myCustomModuleLoader + * })); + * //... + * backend.start(); + * ``` + */ +export const dynamicPluginsFeatureDiscoveryLoaderWithOptions = ( + options?: DynamicPluginsFactoryOptions, +) => + createBackendFeatureLoader({ + deps: { + config: coreServices.rootConfig, + logger: coreServices.rootLogger, + }, + async loader({ config, logger }) { + const manager = await DynamicPluginManager.create({ + config, + logger, + preferAlpha: true, + moduleLoader: options?.moduleLoader?.(logger), + }); + const service = new DynamicPluginsEnabledFeatureDiscoveryService(manager); + const { features } = await service.getBackendFeatures(); + return features; + }, + }); + +/** + * A backend feature loader that uses the dynamic plugins system to discover features. + * + * @public + * + * @example + * Using the `dynamicPluginsFeatureDiscoveryLoader` loader in a backend instance: + * ```ts + * //... + * import { createBackend } from '@backstage/backend-defaults'; + * import { dynamicPluginsFeatureDiscoveryLoader } from '@backstage/backend-dynamic-feature-service'; + * + * const backend = createBackend(); + * backend.add(dynamicPluginsFeatureDiscoveryLoader); + * //... + * backend.start(); + * ``` + */ +export const dynamicPluginsFeatureDiscoveryLoader = + dynamicPluginsFeatureDiscoveryLoaderWithOptions(); + function isBackendFeature(value: unknown): value is BackendFeature { return ( !!value && diff --git a/packages/backend-plugin-api/api-report-alpha.md b/packages/backend-plugin-api/api-report-alpha.md index 0f4348336a..dea357502e 100644 --- a/packages/backend-plugin-api/api-report-alpha.md +++ b/packages/backend-plugin-api/api-report-alpha.md @@ -14,7 +14,7 @@ export interface FeatureDiscoveryService { }>; } -// @alpha +// @alpha @deprecated export const featureDiscoveryServiceRef: ServiceRef< FeatureDiscoveryService, 'root', diff --git a/packages/backend-plugin-api/src/alpha.ts b/packages/backend-plugin-api/src/alpha.ts index baee739f49..e57bd19de1 100644 --- a/packages/backend-plugin-api/src/alpha.ts +++ b/packages/backend-plugin-api/src/alpha.ts @@ -27,6 +27,7 @@ export interface FeatureDiscoveryService { /** * An optional service that can be used to dynamically load in additional BackendFeatures at runtime. * @alpha + * @deprecated The `featureDiscoveryServiceRef` is deprecated in favor of using {@link @backstage/backend-app-api#featureDiscoveryLoader} instead. */ export const featureDiscoveryServiceRef = createServiceRef({