mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-13 23:14:53 +09:00
* Add better plugin load errors * Remove check for permission * Account for packages using different main file
96 lines
2.9 KiB
TypeScript
96 lines
2.9 KiB
TypeScript
|
|
/**
|
|
* Helpers for loading plugins in Node.js
|
|
* @module lib/plugin_loader
|
|
*/
|
|
import path from "path";
|
|
import fs from "fs/promises";
|
|
import * as libErrors from "./errors";
|
|
import * as libPlugin from "./plugin";
|
|
import { logger } from "./logging";
|
|
|
|
|
|
/**
|
|
* Load plugin information
|
|
*
|
|
* Loads plugin info modules for the paths to the given plugins. Once
|
|
* loaded the info modules will not be reloaded should this function be
|
|
* called again.
|
|
*
|
|
* @param pluginList -
|
|
* Mapping of plugin name to require path for the plugins to load.
|
|
* @returns Array of plugin info modules.
|
|
*/
|
|
export async function loadPluginInfos(pluginList: Map<string, string>) {
|
|
let plugins: libPlugin.PluginNodeEnvInfo[] = [];
|
|
for (let [pluginName, pluginPath] of pluginList) {
|
|
let pluginInfo: libPlugin.PluginNodeEnvInfo;
|
|
let pluginPackage: { name?: string, version: string, main?: string, private?: boolean };
|
|
|
|
// Check if plugin path exists, otherwise remove it
|
|
try {
|
|
require.resolve(pluginPath);
|
|
} catch {
|
|
let errMsg = `Plugin path ${pluginPath} does not exist`;
|
|
try {
|
|
await fs.access(pluginPath, fs.constants.F_OK);
|
|
errMsg = `Plugin path ${pluginPath} missing index or main file`;
|
|
} catch {}
|
|
logger.error(`${errMsg}, not loading ${pluginName}`);
|
|
pluginList.delete(pluginName);
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
pluginInfo = require(pluginPath).plugin;
|
|
pluginPackage = require(path.posix.join(pluginPath, "package.json"));
|
|
} catch (err: any) {
|
|
throw new libErrors.PluginError(pluginName, err);
|
|
}
|
|
|
|
if (typeof pluginInfo !== "object") {
|
|
throw new libErrors.EnvironmentError(
|
|
`Expected plugin at ${pluginPath} to export an object named 'plugin' but got ${typeof pluginInfo}`,
|
|
);
|
|
}
|
|
|
|
if (pluginInfo.name !== pluginName) {
|
|
throw new libErrors.EnvironmentError(
|
|
`Expected plugin at ${pluginPath} to be named ${pluginName} but got ${pluginInfo.name}`
|
|
);
|
|
}
|
|
|
|
// migrate: ignore incompatible old plugins
|
|
if (pluginInfo.messages && !(pluginInfo.messages instanceof Array)) {
|
|
logger.warn(`Ignoring incompatible pre alpha.14 plugin ${pluginName}`);
|
|
continue;
|
|
}
|
|
|
|
pluginInfo.requirePath = pluginPath;
|
|
pluginInfo.version = pluginPackage.version;
|
|
pluginInfo.npmPackage = !pluginPackage.private && pluginPath === pluginPackage.name ? pluginPath : undefined;
|
|
plugins.push(pluginInfo);
|
|
}
|
|
return plugins;
|
|
}
|
|
|
|
export async function loadPluginClass<Class extends { new (...args: any): any }>(
|
|
pluginName: string,
|
|
requirePath: string,
|
|
className: string,
|
|
pluginClass: Class,
|
|
): Promise<Class> {
|
|
let entrypoint = require(requirePath);
|
|
if (!entrypoint[className]) {
|
|
throw new libErrors.PluginError(pluginName,
|
|
new Error(`Expected ${requirePath} to export a class named ${className}`)
|
|
);
|
|
}
|
|
if (!(entrypoint[className].prototype instanceof pluginClass)) {
|
|
throw new libErrors.PluginError(pluginName,
|
|
new Error(`Expected ${className} exported from ${requirePath} to be a subclass of ${pluginClass.name}`)
|
|
);
|
|
}
|
|
return entrypoint[className];
|
|
}
|