BackendPluginManager use default exports
for dynamic plugins on the new backend system. This allows making a plugin dynamic without any source code change (which was previously required to add a dedicated entrypoint). Signed-off-by: David Festal <dfestal@redhat.com>
This commit is contained in:
@@ -49,6 +49,6 @@ export class CommonJSModuleLoader implements ModuleLoader {
|
||||
}
|
||||
|
||||
async load(packagePath: string): Promise<any> {
|
||||
return await import(/* webpackIgnore: true */ packagePath);
|
||||
return await require(/* webpackIgnore: true */ packagePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,94 @@ describe('backend-plugin-manager', () => {
|
||||
>([]);
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should successfully load a new backend plugin by the default BackendFeature',
|
||||
packageManifest: {
|
||||
name: 'backend-dynamic-plugin-test',
|
||||
version: '0.0.0',
|
||||
backstage: {
|
||||
role: 'backend-plugin',
|
||||
},
|
||||
main: 'dist/index.cjs.js',
|
||||
},
|
||||
indexFile: {
|
||||
retativePath: ['dist', 'index.cjs.js'],
|
||||
content: `const alpha = { $$type: '@backstage/BackendFeature' }; exports["default"] = alpha;`,
|
||||
},
|
||||
expectedLogs(location) {
|
||||
return {
|
||||
infos: [
|
||||
{
|
||||
message: `loaded dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
checkLoadedPlugins(plugins) {
|
||||
expect(plugins).toMatchObject([
|
||||
{
|
||||
name: 'backend-dynamic-plugin-test',
|
||||
version: '0.0.0',
|
||||
role: 'backend-plugin',
|
||||
platform: 'node',
|
||||
installer: {
|
||||
kind: 'new',
|
||||
},
|
||||
},
|
||||
]);
|
||||
const installer: NewBackendPluginInstaller = (
|
||||
plugins[0] as BackendDynamicPlugin
|
||||
).installer as NewBackendPluginInstaller;
|
||||
expect(installer.install()).toEqual<
|
||||
BackendFeature | BackendFeature[]
|
||||
>({ $$type: '@backstage/BackendFeature' });
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should successfully load a new backend plugin by the default BackendFeatureFactory',
|
||||
packageManifest: {
|
||||
name: 'backend-dynamic-plugin-test',
|
||||
version: '0.0.0',
|
||||
backstage: {
|
||||
role: 'backend-plugin',
|
||||
},
|
||||
main: 'dist/index.cjs.js',
|
||||
},
|
||||
indexFile: {
|
||||
retativePath: ['dist', 'index.cjs.js'],
|
||||
content: `const alpha = () => { return { $$type: '@backstage/BackendFeature' } };
|
||||
alpha.$$type = '@backstage/BackendFeatureFactory';
|
||||
exports["default"] = alpha;`,
|
||||
},
|
||||
expectedLogs(location) {
|
||||
return {
|
||||
infos: [
|
||||
{
|
||||
message: `loaded dynamic backend plugin 'backend-dynamic-plugin-test' from '${location}'`,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
checkLoadedPlugins(plugins) {
|
||||
expect(plugins).toMatchObject([
|
||||
{
|
||||
name: 'backend-dynamic-plugin-test',
|
||||
version: '0.0.0',
|
||||
role: 'backend-plugin',
|
||||
platform: 'node',
|
||||
installer: {
|
||||
kind: 'new',
|
||||
},
|
||||
},
|
||||
]);
|
||||
const installer: NewBackendPluginInstaller = (
|
||||
plugins[0] as BackendDynamicPlugin
|
||||
).installer as NewBackendPluginInstaller;
|
||||
expect(installer.install()).toEqual<
|
||||
BackendFeature | BackendFeature[]
|
||||
>({ $$type: '@backstage/BackendFeature' });
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'should successfully load a new backend plugin module',
|
||||
packageManifest: {
|
||||
@@ -221,7 +309,7 @@ describe('backend-plugin-manager', () => {
|
||||
return {
|
||||
errors: [
|
||||
{
|
||||
message: `dynamic backend plugin 'backend-dynamic-plugin-test' could not be loaded from '${location}': the module should export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field.`,
|
||||
message: `dynamic backend plugin 'backend-dynamic-plugin-test' could not be loaded from '${location}': the module should either export a 'BackendFeature' or 'BackendFeatureFactory' as default export, or export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field as dynamic loading entrypoint.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -249,7 +337,7 @@ describe('backend-plugin-manager', () => {
|
||||
return {
|
||||
errors: [
|
||||
{
|
||||
message: `dynamic backend plugin 'backend-dynamic-plugin-test' could not be loaded from '${location}': the module should export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field.`,
|
||||
message: `dynamic backend plugin 'backend-dynamic-plugin-test' could not be loaded from '${location}': the module should either export a 'BackendFeature' or 'BackendFeatureFactory' as default export, or export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field as dynamic loading entrypoint.`,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -378,7 +466,7 @@ describe('backend-plugin-manager', () => {
|
||||
logger,
|
||||
async bootstrap(_: string, __: string[]): Promise<void> {},
|
||||
load: async (packagePath: string) =>
|
||||
await import(/* webpackIgnore: true */ packagePath),
|
||||
await require(/* webpackIgnore: true */ packagePath),
|
||||
});
|
||||
|
||||
const loadedPlugins: DynamicPlugin[] = await pluginManager.loadPlugins();
|
||||
|
||||
@@ -140,12 +140,25 @@ export class PluginManager implements BackendPluginProvider {
|
||||
`${plugin.location}/${plugin.manifest.main}`,
|
||||
);
|
||||
try {
|
||||
const { dynamicPluginInstaller } = await this.moduleLoader.load(
|
||||
packagePath,
|
||||
);
|
||||
const pluginModule = await this.moduleLoader.load(packagePath);
|
||||
|
||||
let dynamicPluginInstaller;
|
||||
if (isBackendFeature(pluginModule.default)) {
|
||||
dynamicPluginInstaller = {
|
||||
kind: 'new',
|
||||
install: () => pluginModule.default,
|
||||
};
|
||||
} else if (isBackendFeatureFactory(pluginModule.default)) {
|
||||
dynamicPluginInstaller = {
|
||||
kind: 'new',
|
||||
install: pluginModule.default,
|
||||
};
|
||||
} else {
|
||||
dynamicPluginInstaller = pluginModule.dynamicPluginInstaller;
|
||||
}
|
||||
if (!isBackendDynamicPluginInstaller(dynamicPluginInstaller)) {
|
||||
this.logger.error(
|
||||
`dynamic backend plugin '${plugin.manifest.name}' could not be loaded from '${plugin.location}': the module should export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field.`,
|
||||
`dynamic backend plugin '${plugin.manifest.name}' could not be loaded from '${plugin.location}': the module should either export a 'BackendFeature' or 'BackendFeatureFactory' as default export, or export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field as dynamic loading entrypoint.`,
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
@@ -263,3 +276,21 @@ export const dynamicPluginsFeatureDiscoveryServiceFactory =
|
||||
return new DynamicPluginsEnabledFeatureDiscoveryService(dynamicPlugins);
|
||||
},
|
||||
});
|
||||
|
||||
function isBackendFeature(value: unknown): value is BackendFeature {
|
||||
return (
|
||||
!!value &&
|
||||
typeof value === 'object' &&
|
||||
(value as BackendFeature).$$type === '@backstage/BackendFeature'
|
||||
);
|
||||
}
|
||||
|
||||
function isBackendFeatureFactory(
|
||||
value: unknown,
|
||||
): value is () => BackendFeature {
|
||||
return (
|
||||
!!value &&
|
||||
typeof value === 'function' &&
|
||||
(value as any).$$type === '@backstage/BackendFeatureFactory'
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user