refactor: apply review suggestions
Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
```
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
```
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -14,7 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export {
|
||||
featureDiscoveryLoader,
|
||||
featureDiscoveryServiceFactory,
|
||||
} from './alpha/featureDiscoveryServiceFactory';
|
||||
export { featureDiscoveryServiceFactory } from './alpha/featureDiscoveryServiceFactory';
|
||||
|
||||
@@ -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<string | undefined> {
|
||||
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<BackendFeature> }> {
|
||||
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'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<string>({ id: 'a' });
|
||||
const extB = createExtensionPoint<string>({ id: 'b' });
|
||||
const init = new BackendInitializer([
|
||||
...requiredRootFactories,
|
||||
rootLifecycleServiceFactory,
|
||||
createServiceFactory({
|
||||
service: coreServices.rootLogger,
|
||||
deps: {},
|
||||
factory: () => new MockLogger(),
|
||||
}),
|
||||
]);
|
||||
init.add(testPlugin);
|
||||
init.add(
|
||||
|
||||
@@ -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<ServiceOrExtensionPoint>;
|
||||
@@ -150,14 +150,26 @@ export class BackendInitializer {
|
||||
}
|
||||
|
||||
async #doStart(): Promise<void> {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
```
|
||||
|
||||
@@ -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:^",
|
||||
|
||||
@@ -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<string | undefined> {
|
||||
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<BackendFeature> }> {
|
||||
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 };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
},
|
||||
});
|
||||
@@ -21,3 +21,4 @@
|
||||
*/
|
||||
|
||||
export { createBackend } from './CreateBackend';
|
||||
export { discoveryFeatureLoader } from './discoveryFeatureLoader';
|
||||
|
||||
@@ -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<
|
||||
|
||||
@@ -37,7 +37,6 @@ export {
|
||||
dynamicPluginsServiceFactoryWithOptions,
|
||||
dynamicPluginsServiceRef,
|
||||
dynamicPluginsFeatureDiscoveryLoader,
|
||||
dynamicPluginsFeatureDiscoveryLoaderWithOptions,
|
||||
} from './plugin-manager';
|
||||
|
||||
export type {
|
||||
|
||||
@@ -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<DynamicPluginProvider>(
|
||||
{
|
||||
@@ -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 (
|
||||
|
||||
@@ -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<FeatureDiscoveryService>({
|
||||
|
||||
@@ -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:^"
|
||||
|
||||
Reference in New Issue
Block a user