diff --git a/.changeset/breezy-flowers-dance.md b/.changeset/breezy-flowers-dance.md index 51bd9db5f1..faa9fd0697 100644 --- a/.changeset/breezy-flowers-dance.md +++ b/.changeset/breezy-flowers-dance.md @@ -2,4 +2,4 @@ '@backstage/backend-app-api': patch --- -Deprecate the `featureDiscoveryServiceFactory` in favor of using `featureDiscoveryLoader` instead. +Deprecate the `featureDiscoveryServiceFactory` in favor of using `@backstage/backend-defaults#discoveryFeatureLoader` instead. diff --git a/.changeset/calm-brooms-fail.md b/.changeset/calm-brooms-fail.md index d99a479d93..a87b5ad38c 100644 --- a/.changeset/calm-brooms-fail.md +++ b/.changeset/calm-brooms-fail.md @@ -2,4 +2,37 @@ '@backstage/backend-dynamic-feature-service': patch --- -Deprecate the `dynamicPluginsServiceRef`, `dynamicPluginsServiceFactory` and `dynamicPluginsServiceFactoryWithOptions` in favor of using `dynamicPluginsFeatureDiscoveryLoader` or `dynamicPluginsFeatureDiscoveryLoaderWithOptions` instead. +Deprecate the `dynamicPluginsServiceRef`, `dynamicPluginsServiceFactory` and `dynamicPluginsServiceFactoryWithOptions` in favor of using the `dynamicPluginsFeatureDiscoveryLoader` to discover dynamic features in a new backend system. + +See usage examples below: + +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(); +``` + +Passing options to the `dynamicPluginsFeatureDiscoveryLoader` loader in a backend instance: + +```ts +import { createBackend } from '@backstage/backend-defaults'; +import { dynamicPluginsFeatureDiscoveryLoader } from '@backstage/backend-dynamic-feature-service'; +import { myCustomModuleLoader } from './myCustomModuleLoader'; +//... + +const backend = createBackend(); +backend.add( + dynamicPluginsFeatureDiscoveryLoader({ + moduleLoader: myCustomModuleLoader, + }), +); +//... +backend.start(); +``` diff --git a/.changeset/fuzzy-mails-walk.md b/.changeset/fuzzy-mails-walk.md index d00d372415..b46fbd59c4 100644 --- a/.changeset/fuzzy-mails-walk.md +++ b/.changeset/fuzzy-mails-walk.md @@ -2,4 +2,4 @@ '@backstage/backend-plugin-api': patch --- -Deprecate the `featureDiscoveryServiceRef` in favor of using `@backstage/backend-app-api#featureDiscoveryLoader` instead. +Deprecate the `featureDiscoveryServiceRef` in favor of using the new `discoveryFeatureLoader` instead. diff --git a/.changeset/sharp-items-study.md b/.changeset/sharp-items-study.md new file mode 100644 index 0000000000..409e504a50 --- /dev/null +++ b/.changeset/sharp-items-study.md @@ -0,0 +1,19 @@ +--- +'@backstage/backend-defaults': minor +--- + +Exports the `discoveryFeatureLoader` as a replacement for the deprecated `featureDiscoveryService`. +The `discoveryFeatureLoader` is a new backend system [feature loader](https://backstage.io/docs/backend-system/architecture/feature-loaders/) that discovers backend features from the current `package.json` and its dependencies. +Here is an example using the `discoveryFeatureLoader` loader in a new backend instance: + +```ts +import { createBackend } from '@backstage/backend-defaults'; +import { discoveryFeatureLoader } from '@backstage/backend-defaults'; +//... + +const backend = createBackend(); +//... +backend.add(discoveryFeatureLoader); +//... +backend.start(); +``` diff --git a/packages/backend-app-api/api-report-alpha.md b/packages/backend-app-api/api-report-alpha.md index 8ff4e61390..163cc325a1 100644 --- a/packages/backend-app-api/api-report-alpha.md +++ b/packages/backend-app-api/api-report-alpha.md @@ -3,13 +3,9 @@ > 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'; -// @public -export const featureDiscoveryLoader: BackendFeature; - // @alpha @deprecated (undocumented) export const featureDiscoveryServiceFactory: ServiceFactory< FeatureDiscoveryService, diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 27cbb0b916..93087e40e5 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -53,7 +53,6 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/cli-common": "workspace:^", - "@backstage/cli-node": "workspace:^", "@backstage/config": "workspace:^", "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", @@ -66,7 +65,6 @@ "cors": "^2.8.5", "express": "^4.17.1", "express-promise-router": "^4.1.0", - "fs-extra": "^11.2.0", "helmet": "^6.0.0", "jose": "^5.0.0", "knex": "^3.0.0", @@ -91,7 +89,6 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/compression": "^1.7.0", - "@types/fs-extra": "^11.0.0", "@types/http-errors": "^2.0.0", "@types/minimist": "^1.2.0", "@types/morgan": "^1.9.0", diff --git a/packages/backend-app-api/src/alpha.ts b/packages/backend-app-api/src/alpha.ts index 41bf4950d4..a334214738 100644 --- a/packages/backend-app-api/src/alpha.ts +++ b/packages/backend-app-api/src/alpha.ts @@ -14,7 +14,4 @@ * limitations under the License. */ -export { - featureDiscoveryLoader, - featureDiscoveryServiceFactory, -} from './alpha/featureDiscoveryServiceFactory'; +export { 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 e901f0c367..05de659083 100644 --- a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts +++ b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts @@ -15,148 +15,16 @@ */ import { - BackendFeature, - RootConfigService, - RootLoggerService, coreServices, - createBackendFeatureLoader, createServiceFactory, } from '@backstage/backend-plugin-api'; -import { - featureDiscoveryServiceRef, - FeatureDiscoveryService, -} from '@backstage/backend-plugin-api/alpha'; -import { resolve as resolvePath, dirname } from 'path'; -import fs from 'fs-extra'; -import { BackstagePackageJson } from '@backstage/cli-node'; - -const DETECTED_PACKAGE_ROLES = [ - 'node-library', - 'backend', - 'backend-plugin', - 'backend-plugin-module', -]; - -/** @internal */ -async function findClosestPackageDir( - searchDir: string, -): Promise { - let path = searchDir; - - // Some confidence check to avoid infinite loop - for (let i = 0; i < 1000; i++) { - const packagePath = resolvePath(path, 'package.json'); - const exists = await fs.pathExists(packagePath); - if (exists) { - return path; - } - - const newPath = dirname(path); - if (newPath === path) { - return undefined; - } - path = newPath; - } - - throw new Error( - `Iteration limit reached when searching for root package.json at ${searchDir}`, - ); -} - -/** @internal */ -class PackageDiscoveryService implements FeatureDiscoveryService { - constructor( - private readonly config: RootConfigService, - private readonly logger: RootLoggerService, - ) {} - - getDependencyNames(path: string) { - const { dependencies } = require(path) as BackstagePackageJson; - const packagesConfig = this.config.getOptional('backend.packages'); - - const dependencyNames = Object.keys(dependencies || {}); - - if (packagesConfig === 'all') { - return dependencyNames; - } - - const includedPackagesConfig = this.config.getOptionalStringArray( - 'backend.packages.include', - ); - - const includedPackages = includedPackagesConfig - ? new Set(includedPackagesConfig) - : dependencyNames; - const excludedPackagesSet = new Set( - this.config.getOptionalStringArray('backend.packages.exclude'), - ); - - return [...includedPackages].filter(name => !excludedPackagesSet.has(name)); - } - - async getBackendFeatures(): Promise<{ features: Array }> { - const packagesConfig = this.config.getOptional('backend.packages'); - if (!packagesConfig || Object.keys(packagesConfig).length === 0) { - return { features: [] }; - } - - const packageDir = await findClosestPackageDir(process.argv[1]); - if (!packageDir) { - throw new Error('Package discovery failed to find package.json'); - } - const dependencyNames = this.getDependencyNames( - resolvePath(packageDir, 'package.json'), - ); - - const features: BackendFeature[] = []; - - for (const name of dependencyNames) { - const depPkg = require(require.resolve(`${name}/package.json`, { - paths: [packageDir], - })) as BackstagePackageJson; - if ( - !depPkg?.backstage?.role || - !DETECTED_PACKAGE_ROLES.includes(depPkg.backstage.role) - ) { - continue; // Not a backstage backend package, ignore - } - - const exportedModulePaths = [ - require.resolve(name, { - paths: [packageDir], - }), - ]; - - // Find modules exported as alpha - try { - exportedModulePaths.push( - require.resolve(`${name}/alpha`, { paths: [packageDir] }), - ); - } catch { - /* ignore */ - } - - for (const modulePath of exportedModulePaths) { - const mod = require(modulePath); - - if (isBackendFeature(mod.default)) { - this.logger.info(`Detected: ${name}`); - features.push(mod.default); - } - if (isBackendFeatureFactory(mod.default)) { - this.logger.info(`Detected: ${name}`); - features.push(mod.default()); - } - } - } - - return { features }; - } -} +import { featureDiscoveryServiceRef } from '@backstage/backend-plugin-api/alpha'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { PackageDiscoveryService } from '../../../backend-defaults/src/PackageDiscoveryService'; /** * @alpha - * @deprecated The `featureDiscoveryServiceFactory` is deprecated in favor of using {@link featureDiscoveryLoader} instead. + * @deprecated The `featureDiscoveryServiceFactory` is deprecated in favor of using {@link @backstage/backend-defaults#discoveryFeatureLoader} instead. */ export const featureDiscoveryServiceFactory = createServiceFactory({ service: featureDiscoveryServiceRef, @@ -168,51 +36,3 @@ export const featureDiscoveryServiceFactory = createServiceFactory({ return new PackageDiscoveryService(config, logger); }, }); - -/** - * 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 && - ['object', 'function'].includes(typeof value) && - (value as BackendFeature).$$type === '@backstage/BackendFeature' - ); -} - -function isBackendFeatureFactory( - value: unknown, -): value is () => BackendFeature { - return ( - !!value && - typeof value === 'function' && - (value as any).$$type === '@backstage/BackendFeatureFactory' - ); -} diff --git a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts index 65fc21ccde..955e463bb0 100644 --- a/packages/backend-app-api/src/wiring/BackendInitializer.test.ts +++ b/packages/backend-app-api/src/wiring/BackendInitializer.test.ts @@ -20,23 +20,32 @@ 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'; -const requiredRootFactories = [ - mockServices.rootConfig.factory(), - mockServices.rootLogger.factory(), -]; +class MockLogger { + debug() {} + info() {} + warn() {} + error() {} + child() { + return this; + } +} const baseFactories = [ - ...requiredRootFactories, lifecycleServiceFactory, rootLifecycleServiceFactory, + createServiceFactory({ + service: coreServices.rootLogger, + deps: {}, + factory: () => new MockLogger(), + }), loggerServiceFactory, ]; @@ -390,7 +399,7 @@ describe('BackendInitializer', () => { }); it('should forward errors when plugins fail to start', async () => { - const init = new BackendInitializer(requiredRootFactories); + const init = new BackendInitializer([]); init.add( createBackendPlugin({ pluginId: 'test', @@ -410,7 +419,7 @@ describe('BackendInitializer', () => { }); it('should forward errors when modules fail to start', async () => { - const init = new BackendInitializer(requiredRootFactories); + const init = new BackendInitializer([]); init.add(testPlugin); init.add( createBackendModule({ @@ -432,7 +441,7 @@ describe('BackendInitializer', () => { }); it('should reject duplicate plugins', async () => { - const init = new BackendInitializer(requiredRootFactories); + const init = new BackendInitializer([]); init.add( createBackendPlugin({ pluginId: 'test', @@ -461,7 +470,7 @@ describe('BackendInitializer', () => { }); it('should reject duplicate modules', async () => { - const init = new BackendInitializer(requiredRootFactories); + const init = new BackendInitializer([]); init.add(testPlugin); init.add( createBackendModule({ @@ -496,8 +505,12 @@ 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 37b424c8ae..2f2edb4d0e 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,14 +150,26 @@ 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( + // TODO: Let's leave this in place and remove it once the deprecated service is removed. We can do that post-1.0 since it's alpha + 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-defaults/api-report.md b/packages/backend-defaults/api-report.md index ab9cc5775c..def38beda7 100644 --- a/packages/backend-defaults/api-report.md +++ b/packages/backend-defaults/api-report.md @@ -4,7 +4,11 @@ ```ts import { Backend } from '@backstage/backend-app-api'; +import { BackendFeature } from '@backstage/backend-plugin-api'; // @public (undocumented) export function createBackend(): Backend; + +// @public +export const discoveryFeatureLoader: BackendFeature; ``` diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 3280a3cf9d..85926e4c97 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -124,6 +124,7 @@ "@backstage/backend-dev-utils": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/cli-common": "workspace:^", + "@backstage/cli-node": "workspace:^", "@backstage/config": "workspace:^", "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/packages/backend-defaults/src/PackageDiscoveryService.ts b/packages/backend-defaults/src/PackageDiscoveryService.ts new file mode 100644 index 0000000000..d7d127f4fa --- /dev/null +++ b/packages/backend-defaults/src/PackageDiscoveryService.ts @@ -0,0 +1,170 @@ +/* + * Copyright 2024 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 fs from 'fs-extra'; +import { resolve as resolvePath, dirname } from 'path'; + +import { + BackendFeature, + RootConfigService, + RootLoggerService, +} from '@backstage/backend-plugin-api'; +import { FeatureDiscoveryService } from '@backstage/backend-plugin-api/alpha'; +import { BackstagePackageJson } from '@backstage/cli-node'; + +const DETECTED_PACKAGE_ROLES = [ + 'node-library', + 'backend', + 'backend-plugin', + 'backend-plugin-module', +]; + +/** @internal */ +function isBackendFeature(value: unknown): value is BackendFeature { + return ( + !!value && + ['object', 'function'].includes(typeof value) && + (value as BackendFeature).$$type === '@backstage/BackendFeature' + ); +} + +/** @internal */ +function isBackendFeatureFactory( + value: unknown, +): value is () => BackendFeature { + return ( + !!value && + typeof value === 'function' && + (value as any).$$type === '@backstage/BackendFeatureFactory' + ); +} + +/** @internal */ +async function findClosestPackageDir( + searchDir: string, +): Promise { + let path = searchDir; + + // Some confidence check to avoid infinite loop + for (let i = 0; i < 1000; i++) { + const packagePath = resolvePath(path, 'package.json'); + const exists = await fs.pathExists(packagePath); + if (exists) { + return path; + } + + const newPath = dirname(path); + if (newPath === path) { + return undefined; + } + path = newPath; + } + + throw new Error( + `Iteration limit reached when searching for root package.json at ${searchDir}`, + ); +} + +/** @internal */ +export class PackageDiscoveryService implements FeatureDiscoveryService { + constructor( + private readonly config: RootConfigService, + private readonly logger: RootLoggerService, + ) {} + + getDependencyNames(path: string) { + const { dependencies } = require(path) as BackstagePackageJson; + const packagesConfig = this.config.getOptional('backend.packages'); + + const dependencyNames = Object.keys(dependencies || {}); + + if (packagesConfig === 'all') { + return dependencyNames; + } + + const includedPackagesConfig = this.config.getOptionalStringArray( + 'backend.packages.include', + ); + + const includedPackages = includedPackagesConfig + ? new Set(includedPackagesConfig) + : dependencyNames; + const excludedPackagesSet = new Set( + this.config.getOptionalStringArray('backend.packages.exclude'), + ); + + return [...includedPackages].filter(name => !excludedPackagesSet.has(name)); + } + + async getBackendFeatures(): Promise<{ features: Array }> { + const packagesConfig = this.config.getOptional('backend.packages'); + if (!packagesConfig || Object.keys(packagesConfig).length === 0) { + return { features: [] }; + } + + const packageDir = await findClosestPackageDir(process.argv[1]); + if (!packageDir) { + throw new Error('Package discovery failed to find package.json'); + } + const dependencyNames = this.getDependencyNames( + resolvePath(packageDir, 'package.json'), + ); + + const features: BackendFeature[] = []; + + for (const name of dependencyNames) { + const depPkg = require(require.resolve(`${name}/package.json`, { + paths: [packageDir], + })) as BackstagePackageJson; + if ( + !depPkg?.backstage?.role || + !DETECTED_PACKAGE_ROLES.includes(depPkg.backstage.role) + ) { + continue; // Not a backstage backend package, ignore + } + + const exportedModulePaths = [ + require.resolve(name, { + paths: [packageDir], + }), + ]; + + // Find modules exported as alpha + try { + exportedModulePaths.push( + require.resolve(`${name}/alpha`, { paths: [packageDir] }), + ); + } catch { + /* ignore */ + } + + for (const modulePath of exportedModulePaths) { + const mod = require(modulePath); + + if (isBackendFeature(mod.default)) { + this.logger.info(`Detected: ${name}`); + features.push(mod.default); + } + if (isBackendFeatureFactory(mod.default)) { + this.logger.info(`Detected: ${name}`); + features.push(mod.default()); + } + } + } + + return { features }; + } +} diff --git a/packages/backend-defaults/src/discoveryFeatureLoader.ts b/packages/backend-defaults/src/discoveryFeatureLoader.ts new file mode 100644 index 0000000000..dc3c9c1d90 --- /dev/null +++ b/packages/backend-defaults/src/discoveryFeatureLoader.ts @@ -0,0 +1,51 @@ +/* + * Copyright 2024 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 { + coreServices, + createBackendFeatureLoader, +} from '@backstage/backend-plugin-api'; +import { PackageDiscoveryService } from './PackageDiscoveryService'; + +/** + * A loader that discovers backend features from the current package.json and its dependencies. + * + * @public + * + * @example + * Using the `discoveryFeatureLoader` loader in a backend instance: + * ```ts + * //... + * import { createBackend } from '@backstage/backend-defaults'; + * import { discoveryFeatureLoader } from '@backstage/backend-defaults'; + * + * const backend = createBackend(); + * backend.add(discoveryFeatureLoader); + * //... + * backend.start(); + * ``` + */ +export const discoveryFeatureLoader = 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; + }, +}); diff --git a/packages/backend-defaults/src/index.ts b/packages/backend-defaults/src/index.ts index b00c865524..5f26df8921 100644 --- a/packages/backend-defaults/src/index.ts +++ b/packages/backend-defaults/src/index.ts @@ -21,3 +21,4 @@ */ export { createBackend } from './CreateBackend'; +export { discoveryFeatureLoader } from './discoveryFeatureLoader'; diff --git a/packages/backend-dynamic-feature-service/api-report.md b/packages/backend-dynamic-feature-service/api-report.md index 355a7efc94..f6972da50b 100644 --- a/packages/backend-dynamic-feature-service/api-report.md +++ b/packages/backend-dynamic-feature-service/api-report.md @@ -113,12 +113,10 @@ export interface DynamicPluginsFactoryOptions { } // @public -export const dynamicPluginsFeatureDiscoveryLoader: BackendFeature; - -// @public -export const dynamicPluginsFeatureDiscoveryLoaderWithOptions: ( +export const dynamicPluginsFeatureDiscoveryLoader: (( options?: DynamicPluginsFactoryOptions, -) => BackendFeature; +) => BackendFeature) & + BackendFeature; // @public @deprecated (undocumented) export const dynamicPluginsFeatureDiscoveryServiceFactory: ServiceFactory< diff --git a/packages/backend-dynamic-feature-service/src/manager/index.ts b/packages/backend-dynamic-feature-service/src/manager/index.ts index 51d9915a7c..d24a41a7a3 100644 --- a/packages/backend-dynamic-feature-service/src/manager/index.ts +++ b/packages/backend-dynamic-feature-service/src/manager/index.ts @@ -37,7 +37,6 @@ export { 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 97ceb08a34..fef0b6000d 100644 --- a/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts +++ b/packages/backend-dynamic-feature-service/src/manager/plugin-manager.ts @@ -215,7 +215,7 @@ export class DynamicPluginManager implements DynamicPluginProvider { /** * @public - * @deprecated The `featureDiscoveryService` is deprecated in favor of using {@link dynamicPluginsFeatureDiscoveryLoader} or {@link dynamicPluginsFeatureDiscoveryLoaderWithOptions} instead. + * @deprecated The `featureDiscoveryService` is deprecated in favor of using {@link dynamicPluginsFeatureDiscoveryLoader} instead. */ export const dynamicPluginsServiceRef = createServiceRef( { @@ -233,7 +233,7 @@ export interface DynamicPluginsFactoryOptions { /** * @public - * @deprecated Use {@link dynamicPluginsFeatureDiscoveryLoaderWithOptions} instead. + * @deprecated Use {@link dynamicPluginsFeatureDiscoveryLoader} instead. */ export const dynamicPluginsServiceFactoryWithOptions = ( options?: DynamicPluginsFactoryOptions, @@ -296,7 +296,7 @@ class DynamicPluginsEnabledFeatureDiscoveryService /** * @public - * @deprecated The `featureDiscoveryService` is deprecated in favor of using {@link dynamicPluginsFeatureDiscoveryLoader} or {@link dynamicPluginsFeatureDiscoveryLoaderWithOptions} instead. + * @deprecated The `featureDiscoveryService` is deprecated in favor of using {@link dynamicPluginsFeatureDiscoveryLoader} instead. */ export const dynamicPluginsFeatureDiscoveryServiceFactory = createServiceFactory({ @@ -310,28 +310,7 @@ 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 = ( +const dynamicPluginsFeatureDiscoveryLoaderWithOptions = ( options?: DynamicPluginsFactoryOptions, ) => createBackendFeatureLoader({ @@ -369,9 +348,27 @@ export const dynamicPluginsFeatureDiscoveryLoaderWithOptions = ( * //... * backend.start(); * ``` + * + * @example + * Passing options to the `dynamicPluginsFeatureDiscoveryLoader` loader in a backend instance: + * ```ts + * //... + * import { createBackend } from '@backstage/backend-defaults'; + * import { dynamicPluginsFeatureDiscoveryLoader } from '@backstage/backend-dynamic-feature-service'; + * import { myCustomModuleLoader } from './myCustomModuleLoader'; + * + * const backend = createBackend(); + * backend.add(dynamicPluginsFeatureDiscoveryLoader({ + * moduleLoader: myCustomModuleLoader + * })); + * //... + * backend.start(); + * ``` */ -export const dynamicPluginsFeatureDiscoveryLoader = - dynamicPluginsFeatureDiscoveryLoaderWithOptions(); +export const dynamicPluginsFeatureDiscoveryLoader = Object.assign( + dynamicPluginsFeatureDiscoveryLoaderWithOptions, + dynamicPluginsFeatureDiscoveryLoaderWithOptions(), +); function isBackendFeature(value: unknown): value is BackendFeature { return ( diff --git a/packages/backend-plugin-api/src/alpha.ts b/packages/backend-plugin-api/src/alpha.ts index e57bd19de1..3f26c66fe9 100644 --- a/packages/backend-plugin-api/src/alpha.ts +++ b/packages/backend-plugin-api/src/alpha.ts @@ -27,7 +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. + * @deprecated The `featureDiscoveryServiceRef` is deprecated in favor of using {@link @backstage/backend-defaults#discoveryFeatureLoader} instead. */ export const featureDiscoveryServiceRef = createServiceRef({ diff --git a/yarn.lock b/yarn.lock index 7218090260..e3692fd785 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3468,7 +3468,6 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/cli-common": "workspace:^" - "@backstage/cli-node": "workspace:^" "@backstage/config": "workspace:^" "@backstage/config-loader": "workspace:^" "@backstage/errors": "workspace:^" @@ -3477,7 +3476,6 @@ __metadata: "@backstage/types": "workspace:^" "@manypkg/get-packages": ^1.1.3 "@types/compression": ^1.7.0 - "@types/fs-extra": ^11.0.0 "@types/http-errors": ^2.0.0 "@types/minimist": ^1.2.0 "@types/morgan": ^1.9.0 @@ -3488,7 +3486,6 @@ __metadata: cors: ^2.8.5 express: ^4.17.1 express-promise-router: ^4.1.0 - fs-extra: ^11.2.0 helmet: ^6.0.0 http-errors: ^2.0.0 jose: ^5.0.0 @@ -3626,6 +3623,7 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/cli-common": "workspace:^" + "@backstage/cli-node": "workspace:^" "@backstage/config": "workspace:^" "@backstage/config-loader": "workspace:^" "@backstage/errors": "workspace:^"