diff --git a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts index d1159aec50..f054c1a365 100644 --- a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts +++ b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.test.ts @@ -34,7 +34,7 @@ describe('featureDiscoveryServiceFactory', () => { dependencies: { 'detected-plugin': '0.0.0', 'detected-module': '0.0.0', - 'another-detected-plugin': '0.0.0', + 'detected-plugin-with-alpha': '0.0.0', }, }), }, @@ -85,23 +85,33 @@ describe('featureDiscoveryServiceFactory', () => { }); `, }, - [resolvePath(rootDir, 'node_modules/another-detected-plugin')]: { + [resolvePath(rootDir, 'node_modules/detected-plugin-with-alpha')]: { 'package.json': JSON.stringify({ - name: 'another-detected-plugin', + name: 'detected-plugin-with-alpha', main: 'index.js', + exports: { + '.': { + default: 'index.js', + }, + './alpha': { + default: 'alpha.js', + }, + './package.json': './package.json', + }, backstage: { role: 'backend-plugin', }, }), - 'index.js': ` + 'index.js': `exports.detectedPlugin = undefined;`, + 'alpha.js': ` const { createBackendPlugin, coreServices } = require('@backstage/backend-plugin-api'); - exports.detectedPlugin = createBackendPlugin({ - pluginId: 'another-detected', + exports.detectedPluginAlpha = createBackendPlugin({ + pluginId: 'detected-alpha', register(env) { env.registerInit({ deps: { identity: coreServices.identity }, async init({ identity }) { - identity.getIdentity('another-detected-plugin'); + identity.getIdentity('detected-plugin-with-alpha'); }, }); }, @@ -134,7 +144,7 @@ describe('featureDiscoveryServiceFactory', () => { expect(fn).toHaveBeenCalledWith('detected-plugin'); expect(fn).toHaveBeenCalledWith('detected-module'); - expect(fn).toHaveBeenCalledWith('another-detected-plugin'); + expect(fn).toHaveBeenCalledWith('detected-plugin-with-alpha'); }); it('detects only the packages that are listed as included', async () => { @@ -152,7 +162,7 @@ describe('featureDiscoveryServiceFactory', () => { data: { backend: { packages: { - include: ['detected-plugin', 'another-detected-plugin'], + include: ['detected-plugin', 'detected-plugin-with-alpha'], }, }, }, @@ -161,7 +171,7 @@ describe('featureDiscoveryServiceFactory', () => { }); expect(fn).toHaveBeenCalledWith('detected-plugin'); - expect(fn).toHaveBeenCalledWith('another-detected-plugin'); + expect(fn).toHaveBeenCalledWith('detected-plugin-with-alpha'); expect(fn).not.toHaveBeenCalledWith('detected-module'); }); @@ -189,7 +199,7 @@ describe('featureDiscoveryServiceFactory', () => { }); expect(fn).not.toHaveBeenCalledWith('detected-plugin'); - expect(fn).not.toHaveBeenCalledWith('another-detected-plugin'); + expect(fn).not.toHaveBeenCalledWith('detected-plugin-with-alpha'); expect(fn).not.toHaveBeenCalledWith('detected-module'); }); @@ -218,7 +228,7 @@ describe('featureDiscoveryServiceFactory', () => { expect(fn).not.toHaveBeenCalledWith('detected-plugin'); expect(fn).not.toHaveBeenCalledWith('detected-module'); - expect(fn).toHaveBeenCalledWith('another-detected-plugin'); + expect(fn).toHaveBeenCalledWith('detected-plugin-with-alpha'); }); it('does not excluded packages when it is an empty list', async () => { @@ -246,7 +256,7 @@ describe('featureDiscoveryServiceFactory', () => { expect(fn).toHaveBeenCalledWith('detected-plugin'); expect(fn).toHaveBeenCalledWith('detected-module'); - expect(fn).toHaveBeenCalledWith('another-detected-plugin'); + expect(fn).toHaveBeenCalledWith('detected-plugin-with-alpha'); }); it('does not detect packages that are included and excluded', async () => { @@ -267,7 +277,7 @@ describe('featureDiscoveryServiceFactory', () => { include: [ 'detected-plugin', 'detected-module', - 'another-detected-plugin', + 'detected-plugin-with-alpha', ], exclude: ['detected-plugin'], }, @@ -279,7 +289,7 @@ describe('featureDiscoveryServiceFactory', () => { expect(fn).not.toHaveBeenCalledWith('detected-plugin'); expect(fn).toHaveBeenCalledWith('detected-module'); - expect(fn).toHaveBeenCalledWith('another-detected-plugin'); + expect(fn).toHaveBeenCalledWith('detected-plugin-with-alpha'); }); it('does not detect any packages when "packages" is empty', async () => { diff --git a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts index 5f12f9c8f5..2e216c287a 100644 --- a/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts +++ b/packages/backend-app-api/src/alpha/featureDiscoveryServiceFactory.ts @@ -27,6 +27,7 @@ import { import { resolve as resolvePath, dirname } from 'path'; import fs from 'fs-extra'; import { BackstagePackageJson } from '@backstage/cli-node'; +import { JsonObject, JsonValue } from '@backstage/types'; const LOADED_PACKAGE_ROLES = ['backend-plugin', 'backend-plugin-module']; @@ -107,13 +108,31 @@ class PackageDiscoveryService implements FeatureDiscoveryService { if (!LOADED_PACKAGE_ROLES.includes(depPkg?.backstage?.role ?? '')) { continue; } - const depModule = require(require.resolve(name, { paths: [packageDir] })); - for (const exportValue of Object.values(depModule)) { - if (isBackendFeature(exportValue)) { - features.push(exportValue); - } - if (isBackendFeatureFactory(exportValue)) { - features.push(exportValue()); + + const exportedModulePaths = [ + require.resolve(name, { + paths: [packageDir], + }), + ]; + + // Find modules exported as alpha + try { + exportedModulePaths.push( + require.resolve(`${name}/alpha`, { paths: [packageDir] }), + ); + } catch (e) { + /* No alpha exports so ignore */ + } + + for (const modulePath of exportedModulePaths) { + const module = require(modulePath); + for (const exportValue of Object.values(module)) { + if (isBackendFeature(exportValue)) { + features.push(exportValue); + } + if (isBackendFeatureFactory(exportValue)) { + features.push(exportValue()); + } } } }