diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index d1aa511794..75890b4370 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -5,13 +5,26 @@ "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { - "access": "public", - "main": "dist/index.cjs.js", - "types": "dist/index.d.ts" + "access": "public" }, "backstage": { "role": "node-library" }, + "exports": { + ".": "./src/index.ts", + "./alpha": "./src/alpha.ts", + "./package.json": "./package.json" + }, + "typesVersions": { + "*": { + "alpha": [ + "src/alpha.ts" + ], + "package.json": [ + "package.json" + ] + } + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -36,6 +49,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/cli-common": "workspace:^", + "@backstage/cli-node": "workspace:^", "@backstage/config": "workspace:^", "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", diff --git a/packages/backend-app-api/src/alpha.ts b/packages/backend-app-api/src/alpha.ts new file mode 100644 index 0000000000..da77c7cfc5 --- /dev/null +++ b/packages/backend-app-api/src/alpha.ts @@ -0,0 +1,118 @@ +/* + * Copyright 2023 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 { + BackendFeature, + RootConfigService, + coreServices, + 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 LOADED_PACKAGE_ROLES = ['backend-plugin', 'backend-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}`, + ); +} + +/** @alpha */ +export class PackageDiscoveryService implements FeatureDiscoveryService { + constructor(private readonly config: RootConfigService) {} + + async getBackendFeatures(): Promise<{ features: Array }> { + // if (this.config.getOptionalString('backend.packages') !== 'all') { + // return { features: [] }; + // } + + console.log(`DEBUG: executing backend feature discovery`); + const packageDir = await findClosestPackageDir(process.argv[1]); + if (!packageDir) { + throw new Error('NOOOOOOOOOOOOOOOOOOOOOOOOOO'); + } + console.log(`DEBUG: packageDir=`, packageDir); + const { dependencies } = require(resolvePath(packageDir, 'package.json')); + const dependencyNames = Object.keys(dependencies); + console.log(`DEBUG: dependencyNames=`, dependencyNames); + + const features: BackendFeature[] = []; + + for (const name of dependencyNames) { + const depPkg = require(`${name}/package.json`) as BackstagePackageJson; + if (!LOADED_PACKAGE_ROLES.includes(depPkg?.backstage?.role ?? '')) { + continue; + } + const depModule = require(name); // @backstage/plugin-catalog-backend + console.log(`DEBUG: loaded ${name} depModule=`, depModule); + Object.values(depModule).filter(exportValue => { + if ( + exportValue && + typeof exportValue === 'object' && + (exportValue as any).$$type === '@backstage/BackendFeature' + ) { + features.push(exportValue as BackendFeature); + } + if ( + typeof exportValue === 'function' && + (exportValue as any).$$type === '@backstage/BackendFeatureFactory' + ) { + features.push(exportValue() as BackendFeature); + } + }); + } + + console.log(`DEBUG: features=`, features); + return { features }; + } +} + +/** @alpha */ +export const packageFeatureDiscoveryServiceFactory = createServiceFactory({ + service: featureDiscoveryServiceRef, + deps: { + config: coreServices.rootConfig, + }, + factory({ config }) { + return new PackageDiscoveryService(config); + }, +}); diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index af4f483db0..f36f90998b 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "@backstage/backend-defaults": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/plugin-adr-backend": "workspace:^", "@backstage/plugin-app-backend": "workspace:^", diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts index 928c933af0..41584e9a29 100644 --- a/packages/backend-plugin-api/src/wiring/factories.ts +++ b/packages/backend-plugin-api/src/wiring/factories.ts @@ -78,6 +78,11 @@ export interface BackendPluginConfig { register(reg: BackendPluginRegistrationPoints): void; } +export const catalogPlugin = createBackendPlugin({ + pluginId: 'catalog', + register() {}, +}); + /** * Creates a new backend plugin. * @@ -89,7 +94,7 @@ export function createBackendPlugin( config: BackendPluginConfig | ((...params: TOptions) => BackendPluginConfig), ): (...params: TOptions) => BackendFeature { const configCallback = typeof config === 'function' ? config : () => config; - return (...options: TOptions): InternalBackendFeature => { + const factory = (...options: TOptions): InternalBackendFeature => { const c = configCallback(...options); let registrations: InternalBackendPluginRegistration[]; @@ -144,6 +149,9 @@ export function createBackendPlugin( }, }; }; + + factory.$$type = '@backstage/BackendFeatureFactory'; + return factory; } /** diff --git a/yarn.lock b/yarn.lock index 88a07ef408..07c182f207 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3292,6 +3292,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:^" @@ -25169,6 +25170,7 @@ __metadata: resolution: "example-backend-next@workspace:packages/backend-next" dependencies: "@backstage/backend-defaults": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/plugin-adr-backend": "workspace:^"