feat: Add include and exclude to backend.packages discovery

Signed-off-by: Jack Palmer <jackpalmer@spotify.com>
This commit is contained in:
Jack Palmer
2023-08-30 13:07:32 +01:00
parent eaba7ba1bd
commit 032622bdcb
2 changed files with 240 additions and 7 deletions
@@ -34,6 +34,7 @@ describe('featureDiscoveryServiceFactory', () => {
dependencies: {
'detected-plugin': '0.0.0',
'detected-module': '0.0.0',
'another-detected-plugin': '0.0.0',
},
}),
},
@@ -84,6 +85,29 @@ describe('featureDiscoveryServiceFactory', () => {
});
`,
},
[resolvePath(rootDir, 'node_modules/another-detected-plugin')]: {
'package.json': JSON.stringify({
name: 'another-detected-plugin',
main: 'index.js',
backstage: {
role: 'backend-plugin',
},
}),
'index.js': `
const { createBackendPlugin, coreServices } = require('@backstage/backend-plugin-api');
exports.detectedPlugin = createBackendPlugin({
pluginId: 'another-detected',
register(env) {
env.registerInit({
deps: { identity: coreServices.identity },
async init({ identity }) {
identity.getIdentity('another-detected-plugin');
},
});
},
});
`,
},
});
});
@@ -91,7 +115,7 @@ describe('featureDiscoveryServiceFactory', () => {
mockFs.restore();
});
it('should detect plugin and module packages', async () => {
it('should detect plugin and module packages when "all" is specified', async () => {
const fn = jest.fn().mockResolvedValue({});
await startTestBackend({
@@ -110,5 +134,191 @@ describe('featureDiscoveryServiceFactory', () => {
expect(fn).toHaveBeenCalledWith('detected-plugin');
expect(fn).toHaveBeenCalledWith('detected-module');
expect(fn).toHaveBeenCalledWith('another-detected-plugin');
});
it('detects only the packages that are listed as included', async () => {
const fn = jest.fn().mockResolvedValue({});
await startTestBackend({
features: [
createServiceFactory({
service: coreServices.identity,
deps: {},
factory: () => ({ getIdentity: fn }),
}),
featureDiscoveryServiceFactory(),
mockServices.rootConfig.factory({
data: {
backend: {
packages: {
include: ['detected-plugin', 'another-detected-plugin'],
},
},
},
}),
],
});
expect(fn).toHaveBeenCalledWith('detected-plugin');
expect(fn).toHaveBeenCalledWith('another-detected-plugin');
expect(fn).not.toHaveBeenCalledWith('detected-module');
});
it('does not detect packages when included is an empty list', async () => {
const fn = jest.fn().mockResolvedValue({});
await startTestBackend({
features: [
createServiceFactory({
service: coreServices.identity,
deps: {},
factory: () => ({ getIdentity: fn }),
}),
featureDiscoveryServiceFactory(),
mockServices.rootConfig.factory({
data: {
backend: {
packages: {
include: [],
},
},
},
}),
],
});
expect(fn).not.toHaveBeenCalledWith('detected-plugin');
expect(fn).not.toHaveBeenCalledWith('another-detected-plugin');
expect(fn).not.toHaveBeenCalledWith('detected-module');
});
it('does not detect an excluded packages', async () => {
const fn = jest.fn().mockResolvedValue({});
await startTestBackend({
features: [
createServiceFactory({
service: coreServices.identity,
deps: {},
factory: () => ({ getIdentity: fn }),
}),
featureDiscoveryServiceFactory(),
mockServices.rootConfig.factory({
data: {
backend: {
packages: {
exclude: ['detected-plugin', 'detected-module'],
},
},
},
}),
],
});
expect(fn).not.toHaveBeenCalledWith('detected-plugin');
expect(fn).not.toHaveBeenCalledWith('detected-module');
expect(fn).toHaveBeenCalledWith('another-detected-plugin');
});
it('does not excluded packages when it is an empty list', async () => {
const fn = jest.fn().mockResolvedValue({});
await startTestBackend({
features: [
createServiceFactory({
service: coreServices.identity,
deps: {},
factory: () => ({ getIdentity: fn }),
}),
featureDiscoveryServiceFactory(),
mockServices.rootConfig.factory({
data: {
backend: {
packages: {
exclude: [],
},
},
},
}),
],
});
expect(fn).toHaveBeenCalledWith('detected-plugin');
expect(fn).toHaveBeenCalledWith('detected-module');
expect(fn).toHaveBeenCalledWith('another-detected-plugin');
});
it('does not detect packages that are included and excluded', async () => {
const fn = jest.fn().mockResolvedValue({});
await startTestBackend({
features: [
createServiceFactory({
service: coreServices.identity,
deps: {},
factory: () => ({ getIdentity: fn }),
}),
featureDiscoveryServiceFactory(),
mockServices.rootConfig.factory({
data: {
backend: {
packages: {
include: [
'detected-plugin',
'detected-module',
'another-detected-plugin',
],
exclude: ['detected-plugin'],
},
},
},
}),
],
});
expect(fn).not.toHaveBeenCalledWith('detected-plugin');
expect(fn).toHaveBeenCalledWith('detected-module');
expect(fn).toHaveBeenCalledWith('another-detected-plugin');
});
it('does not detect any packages when "packages" is empty', async () => {
const fn = jest.fn().mockResolvedValue({});
await startTestBackend({
features: [
createServiceFactory({
service: coreServices.identity,
deps: {},
factory: () => ({ getIdentity: fn }),
}),
featureDiscoveryServiceFactory(),
mockServices.rootConfig.factory({
data: { backend: { packages: {} } },
}),
],
});
expect(fn).not.toHaveBeenCalled();
});
it('does not detect any packages when "packages" is not present', async () => {
const fn = jest.fn().mockResolvedValue({});
await startTestBackend({
features: [
createServiceFactory({
service: coreServices.identity,
deps: {},
factory: () => ({ getIdentity: fn }),
}),
featureDiscoveryServiceFactory(),
mockServices.rootConfig.factory({
data: { backend: {} },
}),
],
});
expect(fn).not.toHaveBeenCalled();
});
});
@@ -60,8 +60,33 @@ async function findClosestPackageDir(
class PackageDiscoveryService implements FeatureDiscoveryService {
constructor(private readonly config: RootConfigService) {}
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> }> {
if (this.config.getOptionalString('backend.packages') !== 'all') {
const packagesConfig = this.config.getOptional('backend.packages');
if (!packagesConfig || Object.keys(packagesConfig).length === 0) {
return { features: [] };
}
@@ -69,11 +94,9 @@ class PackageDiscoveryService implements FeatureDiscoveryService {
if (!packageDir) {
throw new Error('Package discovery failed to find package.json');
}
const { dependencies } = require(resolvePath(
packageDir,
'package.json',
)) as BackstagePackageJson;
const dependencyNames = Object.keys(dependencies || {});
const dependencyNames = this.getDependencyNames(
resolvePath(packageDir, 'package.json'),
);
const features: BackendFeature[] = [];