feat: Add include and exclude to backend.packages discovery
Signed-off-by: Jack Palmer <jackpalmer@spotify.com>
This commit is contained in:
@@ -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[] = [];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user