Skip to content

Commit

Permalink
Support resolvePackagePath for dynamic backend plugins.
Browse files Browse the repository at this point in the history
Signed-off-by: David Festal <dfestal@redhat.com>
  • Loading branch information
davidfestal committed Mar 11, 2024
1 parent 852a05e commit 082eb01
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 1 deletion.
8 changes: 7 additions & 1 deletion packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { metricsHandler } from './metrics';
import { statusCheckHandler } from '@backstage/backend-common';
import { RequestHandler } from 'express';
import * as path from 'path';
import { CommonJSModuleLoader } from './loader';

const backend = createBackend();

Expand Down Expand Up @@ -43,7 +44,12 @@ backend.add(
}),
);
backend.add(dynamicPluginsFeatureDiscoveryServiceFactory()); // overridden version of the FeatureDiscoveryService which provides features loaded by dynamic plugins
backend.add(dynamicPluginsServiceFactory());
backend.add(
dynamicPluginsServiceFactory({
moduleLoader: logger => new CommonJSModuleLoader(logger),
}),
);

backend.add(
dynamicPluginsSchemasServiceFactory({
schemaLocator(pluginPackage) {
Expand Down
133 changes: 133 additions & 0 deletions packages/backend/src/loader/CommonJSModuleLoader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import {
ModuleLoader,
ScannedPluginManifest,
} from '@backstage/backend-dynamic-feature-service';
import { LoggerService } from '@backstage/backend-plugin-api';
import path from 'path';
import * as fs from 'fs';

export class CommonJSModuleLoader implements ModuleLoader {
constructor(public readonly logger: LoggerService) {}

async bootstrap(
backstageRoot: string,
dynamicPluginsPaths: string[],
): Promise<void> {
const backstageRootNodeModulesPath = `${backstageRoot}/node_modules`;
const dynamicNodeModulesPaths = [
...dynamicPluginsPaths.map(p => path.resolve(p, 'node_modules')),
];
const ModuleObject = require('module');
const oldNodeModulePaths = ModuleObject._nodeModulePaths;
ModuleObject._nodeModulePaths = (from: string): string[] => {
const result: string[] = oldNodeModulePaths(from);
if (!dynamicPluginsPaths.some(p => from.startsWith(p))) {
return result;
}
const filtered = result.filter(nodeModulePath => {
return (
nodeModulePath === backstageRootNodeModulesPath ||
dynamicNodeModulesPaths.some(p => nodeModulePath.startsWith(p))
);
});
this.logger.debug(
`Overriding node_modules search path for dynamic plugin ${from} to: ${filtered}`,
);
return filtered;
};

let dynamicPluginPackages:
| { name: string; dependencies: string[]; path: string }[]
| undefined = undefined;

const oldResolveFileName = ModuleObject._resolveFilename;
ModuleObject._resolveFilename = (
request: string,
mod: NodeModule,
_: boolean,
options: any,
): any => {
let errorToThrow: any;
try {
return oldResolveFileName(request, mod, _, options);
} catch (e) {
errorToThrow = e;
this.logger.debug(
`Could not resolve '${request}' in the Core backstage backend application`,
e instanceof Error ? e : undefined,
);
}

if (
// we're searching for the folder of a backstage package (through @backstage/backend-common/resolvePackagePath) ?
// => - we're trying to resolve a `package.json`
request &&
request.endsWith('/package.json') &&
// - from the `backend-common` core backstage application module
mod?.path &&
!dynamicPluginsPaths.some(p => mod.path.startsWith(p)) &&
mod.path.includes(`backend-common`)
) {
this.logger.info(
`Trying to resolve '${request}' in the dynamic backend plugins`,
);

if (!dynamicPluginPackages) {
dynamicPluginPackages = [];
dynamicPluginsPaths.forEach(p => {
try {
const manifestFile = path.resolve(p, 'package.json');
const content = fs.readFileSync(manifestFile);
const manifest: ScannedPluginManifest = JSON.parse(
content.toString(),
);
dynamicPluginPackages!.push({
name: manifest.name,
dependencies: Object.keys(manifest.dependencies || {}),
path: p,
});
} catch (e) {
this.logger.error(
`Error when reading 'package.json' in '${p}'`,
e instanceof Error ? e : undefined,
);
}
});
}

const searchedPackageName = request.replace(/\/package.json$/, '');
const searchedPackageNameDynamic = `${searchedPackageName}-dynamic`;
for (const p of dynamicPluginPackages) {
// Case of a dynamic plugin package
if (
searchedPackageName === p.name ||
searchedPackageNameDynamic === p.name
) {
return path.resolve(p.path, 'package.json');
}

// Case of a dynamic plugin wrapper package
if (p.dependencies.includes(searchedPackageName)) {
const searchPath = path.resolve(p.path, 'node_modules');
try {
return require.resolve(searchedPackageName, {
paths: [searchPath],
});
} catch (e) {
this.logger.error(
`Error when resolving '${searchedPackageName}' with search path: '[${searchPath}]'`,
e instanceof Error ? e : undefined,
);
}
}
}
}

throw errorToThrow;
};
}

async load(packagePath: string): Promise<any> {
return await require(/* webpackIgnore: true */ packagePath);
}
}
1 change: 1 addition & 0 deletions packages/backend/src/loader/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { CommonJSModuleLoader } from './CommonJSModuleLoader';

0 comments on commit 082eb01

Please sign in to comment.