Move export for plugin declaration (#555)

Change the expected exported name of plugin declarations to be "plugin"
at the main entrypoint instead of the default export.  This makes the
plugin's code organisation more flexible especially when using CommonJS
JavaScript modules as they can export additional symbols from the
plugin declaration file.
This commit is contained in:
Hornwitser authored and GitHub committed 2024-01-18 20:31:52 +01:00
1 parent 8884116ee9
commit b0f6c95fca
33 files changed
+71 -62

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ Clusterio uses a field based configuration system to manage settings for the con
Plugins can add their own config fields for the controller and instances configs.
The fields are defined early in the startup, and fields for disabled plugins will still be created.
The built-in config fields are defined in [packages/lib/config/definitions.js](/packages/lib/config/definitions.js) while plugins can add their own config field definitions to the `controllerConfigFields` and `instanceConfigFields` properties of the `info.js` export.
The built-in config fields are defined in [packages/lib/config/definitions.js](/packages/lib/config/definitions.js) while plugins can add their own config field definitions to the `controllerConfigFields` and `instanceConfigFields` properties of the `plugin` export.
A `<plugin_name>.load_plugin` field is automatically created for each plugin and it's used to enable/disable the loading plugins on startup.
+12 -14
View File
@@ -25,7 +25,7 @@ The plugin classes have pre-defined hooks that are called during various stages
The basic file structure of a plugin is the following.
plugin_name/
+- info.js
+- index.js
+- package.json
+- controller.js
+- instance.js
@@ -44,14 +44,12 @@ A possible workflow for developing plugins is to place the plugin in a sub-direc
To add it to `plugin-list.json` so that it gets loaded use the `plugin add <path>` sub-command to either clusteriocontroller, clusteriohost or clusterioctl.
Note that it's important that the path starts with ./ or ../ (use .\ or ..\ on Windows).
For a plugin the most important file is the `info.js` file.
Without it the plugin will not recognized by Clusterio.
Here's an example of it:
For a plugin to be recognised by Clusterio it needs to export an entry named `plugin` from its main entrypoint.
By default the main entrypoint is the `index.js` file, but this may be changed by setting the `"main"` entry to a different file in `package.json`.
Here's an example of `index.js`:
```js
const lib = require("@clusterio/lib"); // For messages
module.exports = {
module.exports.plugin = {
name: "foo_frobber",
title: "Foo Frobber",
description: "Does advanced frobnication",
@@ -202,8 +200,8 @@ For plugin hooks expections thrown are automatically catched and logged, but for
## Plugin Configuration
Clusterio provides a configuration system that handles storing, distributing, editing and validating config fields for you.
You can take advantage of it by declaring config fields under either `controllerConfigFields` or `instanceConfigFields` in the `info.js` export.
For example in info.js:
You can take advantage of it by declaring config fields under either `controllerConfigFields` or `instanceConfigFields` in the `plugin` export.
For example in index.js:
```ts
import type * as lib from "@clusterio/lib";
@@ -330,9 +328,9 @@ The requests are similar to HTTP requests, except both parties of a link may ini
### Defining Events
Events are defined as properties of the messages object exported by `info.js` that map to instances of the `Event` class from `lib/link`.
Events are defined as properties of the messages object exported on `plugin` that map to instances of the `Event` class from `lib/link`.
The name of the property correspond to the handler invoked on the plugin class.
The Event constructor takes an object of properties that define the event, for example the following could be defined in `info.js`:
The Event constructor takes an object of properties that define the event, for example the following could be defined in `plugin`:
```js
messages: {
@@ -404,10 +402,10 @@ Use a request if you need a confirmation that the message was received.
### Definining Requests
Requests are defined as properties of the messages object exported by `info.js` that map to instances of the `Request` class from `lib/link`.
Requests are defined as properties of the messages object exported by `plugin` that map to instances of the `Request` class from `lib/link`.
The name of the property corresponds to the handler invoked on the plugin class.
The Request constructor takes an object of properties that define the event.
For example, the following could be defined in `info.js`:
For example, the following could be defined in `plugin`:
```js
messages: {
@@ -484,7 +482,7 @@ Same as the requestProperties only for the response sent back by the target.
Link messages are sent by calling the `.send()` method on the Event/Request instance with the link you want to send it over and the data to send.
For `InstancePlugin` code the link to the host is the `instance` itself, which is accessible through the `.instance` property of the `InstancePlugin`.
The `.info` property of the plugin class exposes the data exported from the plugin's `info.js` module.
The `.info` property of the plugin class exposes the data exported from the plugin's `plugin` export.
In other words:
```js
@@ -13,8 +13,8 @@ import type HostConnection from "./HostConnection";
* Controller plugins are subclasses of this class which get instantiated by
* the controller on startup when the plugin is enabled in the config.
* To be discovered the class must be exported under the name `ControllerPlugin`
* in the module specified by the `controllerEntrypoint` in the plugin's info.js
* file.
* in the module specified by the `controllerEntrypoint` in the plugin's
* `plugin` export.
*/
export default class BaseControllerPlugin {
/**
+1 -1
View File
@@ -6,7 +6,7 @@ import type { CommandTree, Logger, PluginNodeEnvInfo } from "@clusterio/lib";
* Ctl plugins are subclasses of this class which get instantiated by
* clusterioctl in order to extend its functionallity. To be discovered the
* class must be exported under the name `CtlPlugin` in the module
* specified by the `ctlEntrypoint` in the plugin's info.js file.
* specified by the `ctlEntrypoint` in the plugin's `plugin` export.
*/
export default class BaseCtlPlugin {
/**
+1 -1
View File
@@ -9,7 +9,7 @@ import type Host from "./Host";
* Host plugins are subclasses of this class which get instantiated by
* the host when it starts up with the plugin enabled in the config. To be
* discovered the class must be exported under the name `HostPlugin` in the
* module specified by the `hostEntrypoint` in the plugin's info.js file.
* module specified by the `hostEntrypoint` in the plugin's `plugin` export.
*/
export default class BaseHostPlugin {
/**
+1 -1
View File
@@ -11,7 +11,7 @@ import type Host from "./Host";
* the host when it brings up an instance with the plugin enabled in the
* config. To be discovered the class must be exported under the name
* `InstancePlugin` in the module specified by the `instanceEntrypoint` in
* the plugin's info.js file.
* the plugin's `plugin` export.
*
* Instances may be started and stopped many times, and many instances may
* be running at the same time, each of which will have their own instance
+1 -2
View File
@@ -7,8 +7,7 @@ import type { Logger } from "./logging";
import type { FieldDefinition } from "./config";
import type { PlayerStats } from "./data";
// TODO Add proper typing for plugins
/* Used to define the export of info.ts from plugins */
/** Used to define the plugin export in plugins */
export type PluginDeclaration = {
name: string;
title: string;
+7 -1
View File
@@ -27,12 +27,18 @@ export async function loadPluginInfos(pluginList: Map<string, string>) {
let pluginPackage: { name?: string, version: string, main?: string, private?: boolean };
try {
pluginInfo = require(pluginPath).default;
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}`
+7 -1
View File
@@ -59,13 +59,19 @@ export async function handlePluginCommand(
let pluginInfo: { name: string };
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
pluginInfo = require(pluginPath).default;
pluginInfo = require(pluginPath).plugin;
} catch (err: any) {
logger.error(`Unable to import plugin info from ${args.path}:\n${err.stack}`);
process.exitCode = 1;
return;
}
if (typeof pluginInfo !== "object") {
logger.error("Plugin does not correctly export a 'plugin' object");
process.exitCode = 1;
return;
}
if (pluginList.has(pluginInfo.name)) {
logger.error(`Plugin with the same ${pluginInfo.name} already exists`);
process.exitCode = 1;
+1 -1
View File
@@ -52,7 +52,7 @@ async function loadPluginInfos(): Promise<lib.PluginWebpackEnvInfo[]> {
throw new Error(`Plugin did not expose its container via plugin_${meta.name}`);
}
await container.init(__webpack_share_scopes__.default);
let pluginInfo = (await container.get("./info"))().default;
let pluginInfo = (await container.get("./"))().plugin;
pluginInfo.container = container;
pluginInfo.package = (await container.get("./package.json"))();
pluginInfo.enabled = meta.enabled;
@@ -2,7 +2,7 @@ import type * as lib from "@clusterio/lib";
import { ChatEvent } from "./messages";
export default {
export const plugin: lib.PluginDeclaration = {
name: "global_chat",
title: "Global Chat",
description: "Forwards chat between instances.",
@@ -12,4 +12,4 @@ export default {
messages: [
ChatEvent,
],
} satisfies lib.PluginDeclaration;
};
+1 -1
View File
@@ -6,7 +6,7 @@
"homepage": "https://github.com/clusterio/clusterio#readme",
"license": "MIT",
"repository": "clusterio/clusterio",
"main": "dist/plugin/info.js",
"main": "dist/plugin/index.js",
"scripts": {
"prepare": "tsc --build && webpack-cli --env production",
"test": "echo \"Error: run tests from root\" && exit 1"
+1 -1
View File
@@ -5,7 +5,7 @@ const lib = require("@clusterio/lib");
const mock = require("../../../test/mock");
const lines = require("../../../test/lib/factorio/lines");
const instance = require("../dist/plugin/instance");
const info = require("../dist/plugin/info").default;
const info = require("../dist/plugin/index").plugin;
const { ChatEvent } = require("../dist/plugin/messages");
+1 -1
View File
@@ -16,7 +16,7 @@ module.exports = (env = {}) => merge(common(env), {
name: "global_chat",
library: { type: "var", name: "plugin_global_chat" },
exposes: {
"./info": "./info.ts",
"./": "./index.ts",
"./package.json": "./package.json",
},
shared: {
@@ -17,7 +17,7 @@ lib.definePermission({
grantByDefault: true,
});
export default {
export const plugin: lib.PluginDeclaration = {
name: "inventory_sync",
title: "Inventory sync",
description: "Synchronizes players inventories between instances",
@@ -54,4 +54,4 @@ export default {
],
webEntrypoint: "./web",
routes: ["/inventory"],
} satisfies lib.PluginDeclaration;
};
+1 -1
View File
@@ -6,7 +6,7 @@
"homepage": "https://github.com/clusterio/factorioClusterio#readme",
"license": "MIT",
"repository": "clusterio/factorioClusterio",
"main": "dist/plugin/info.js",
"main": "dist/plugin/index.js",
"scripts": {
"prepare": "tsc --build && webpack-cli --env production",
"test": "echo \"Error: run tests from root\" && exit 1"
+1 -1
View File
@@ -16,7 +16,7 @@ module.exports = (env = {}) => merge(common(env), {
name: "inventory_sync",
library: { type: "var", name: "plugin_inventory_sync" },
exposes: {
"./info": "./info.ts",
"./": "./index.ts",
"./package.json": "./package.json",
"./web": "./web/index.tsx",
},
@@ -11,7 +11,7 @@ declare module "@clusterio/lib" {
}
}
export default {
export const plugin: lib.PluginDeclaration = {
name: "player_auth",
title: "Player Auth",
description: "Provides authentication to the cluster via logging into a Factorio server.",
@@ -37,4 +37,4 @@ export default {
messages.FetchPlayerCodeRequest,
messages.SetVerifyCodeRequest,
],
} satisfies lib.PluginDeclaration;
};
+1 -1
View File
@@ -6,7 +6,7 @@
"homepage": "https://github.com/clusterio/clusterio#readme",
"license": "MIT",
"repository": "clusterio/clusterio",
"main": "dist/plugin/info.js",
"main": "dist/plugin/index.js",
"scripts": {
"prepare": "tsc --build && webpack-cli --env production",
"test": "echo \"Error: run tests from root\" && exit 1"
+1 -1
View File
@@ -7,7 +7,7 @@ const mock = require("../../../test/mock");
const controller = require("../dist/plugin/controller");
const instance = require("../dist/plugin/instance");
const info = require("../dist/plugin/info").default;
const info = require("../dist/plugin/index").plugin;
const { FetchPlayerCodeRequest, SetVerifyCodeRequest } = require("../dist/plugin/messages");
const lib = require("@clusterio/lib");
+1 -1
View File
@@ -16,7 +16,7 @@ module.exports = (env = {}) => merge(common(env), {
name: "player_auth",
library: { type: "var", name: "plugin_player_auth" },
exposes: {
"./info": "./info.ts",
"./": "./index.ts",
"./package.json": "./package.json",
"./web": "./web/index.tsx",
},
@@ -2,7 +2,7 @@ import type * as lib from "@clusterio/lib";
import * as messages from "./messages";
export default {
export const plugin: lib.PluginDeclaration = {
name: "research_sync",
title: "Research Sync",
description: "Synchronises technology research progress between instances.",
@@ -15,4 +15,4 @@ export default {
messages.FinishedEvent,
messages.SyncTechnologiesRequest,
],
} satisfies lib.PluginDeclaration;
};
+1 -1
View File
@@ -6,7 +6,7 @@
"homepage": "https://github.com/clusterio/clusterio#readme",
"license": "MIT",
"repository": "clusterio/clusterio",
"main": "dist/plugin/info.js",
"main": "dist/plugin/index.js",
"scripts": {
"prepare": "tsc --build && webpack-cli --env production",
"test": "echo \"Error: run tests from root\" && exit 1"
+1 -1
View File
@@ -16,7 +16,7 @@ module.exports = (env = {}) => merge(common(env), {
name: "research_sync",
library: { type: "var", name: "plugin_research_sync" },
exposes: {
"./info": "./info.ts",
"./": "./index.ts",
"./package.json": "./package.json",
},
shared: {
@@ -6,7 +6,7 @@ declare module "@clusterio/lib" {
}
}
export default {
export const plugin: lib.PluginDeclaration = {
name: "statistics_exporter",
title: "Statistics Exporter",
description:
@@ -22,4 +22,4 @@ export default {
initialValue: 1,
},
},
} satisfies lib.PluginDeclaration;
};
+1 -1
View File
@@ -6,7 +6,7 @@
"homepage": "https://github.com/clusterio/clusterio#readme",
"license": "MIT",
"repository": "clusterio/clusterio",
"main": "dist/plugin/info.js",
"main": "dist/plugin/index.js",
"scripts": {
"prepare": "tsc --build && webpack-cli --env production",
"test": "echo \"Error: run tests from root\" && exit 1"
+1 -1
View File
@@ -3,7 +3,7 @@ const assert = require("assert").strict;
const mock = require("../../../test/mock");
const instance = require("../dist/plugin/instance");
const info = require("../dist/plugin/info").default;
const info = require("../dist/plugin/index").plugin;
describe("statistics_exporter plugin", function() {
@@ -16,7 +16,7 @@ module.exports = (env = {}) => merge(common(env), {
name: "statistics_exporter",
library: { type: "var", name: "plugin_statistics_exporter" },
exposes: {
"./info": "./info.ts",
"./": "./index.ts",
"./package.json": "./package.json",
},
shared: {
@@ -18,7 +18,7 @@ lib.definePermission({
grantByDefault: true,
});
export default {
export const plugin: lib.PluginDeclaration = {
name: "subspace_storage",
title: "Subspace Storage",
description: "Provides shared storage across instances for the Subspace Storage mod",
@@ -58,4 +58,4 @@ export default {
],
webEntrypoint: "./web",
routes: ["/storage"],
} satisfies lib.PluginDeclaration;
};
+1 -1
View File
@@ -6,7 +6,7 @@
"homepage": "https://github.com/clusterio/clusterio#readme",
"license": "MIT",
"repository": "clusterio/clusterio",
"main": "dist/plugin/info.js",
"main": "dist/plugin/index.js",
"scripts": {
"prepare": "tsc --build && webpack-cli --env production",
"test": "echo \"Error: run tests from root\" && exit 1"
+1 -1
View File
@@ -16,7 +16,7 @@ module.exports = (env = {}) => merge(common(env), {
name: "subspace_storage",
library: { type: "var", name: "plugin_subspace_storage" },
exposes: {
"./info": "./info.ts",
"./": "./index.ts",
"./package.json": "./package.json",
"./web": "./web/index.tsx",
},
+5 -5
View File
@@ -12,11 +12,11 @@ const lib = require("@clusterio/lib");
const { LineSplitter, ConsoleTransport, logger } = lib;
// Make sure permissions from plugins are loaded
require("../../plugins/global_chat/dist/plugin/info");
require("../../plugins/player_auth/dist/plugin/info");
require("../../plugins/research_sync/dist/plugin/info");
require("../../plugins/statistics_exporter/dist/plugin/info");
require("../../plugins/subspace_storage/dist/plugin/info");
require("../../plugins/global_chat/dist/plugin/index");
require("../../plugins/player_auth/dist/plugin/index");
require("../../plugins/research_sync/dist/plugin/index");
require("../../plugins/statistics_exporter/dist/plugin/index");
require("../../plugins/subspace_storage/dist/plugin/index");
const subspaceStorageVersion = "1.99.8";
const subspaceStorageGitHubTag = "1.99.6"; // Tag subspaceStorageVersion was released under on GitHub
+5 -5
View File
@@ -18,18 +18,18 @@ describe("lib/plugin_loader", function() {
before(async function() {
async function writePlugin(pluginPath, infoName) {
await fs.outputFile(
path.join(pluginPath, "info.js"),
`module.exports.default = { name: "${infoName}" };`
path.join(pluginPath, "index.js"),
`module.exports.plugin = { name: "${infoName}" };`
);
await fs.outputFile(
path.join(pluginPath, "package.json"),
'{ "version": "0.0.1", "main":"info.js" }'
'{ "version": "0.0.1" }'
);
}
await writePlugin(testPlugin, "test");
await writePlugin(brokenPlugin, "broken");
await fs.outputFile(path.join(brokenPlugin, "info.js"), "Syntax Error");
await fs.outputFile(path.join(brokenPlugin, "index.js"), "Syntax Error");
await writePlugin(invalidPlugin, "wrong");
});
@@ -49,7 +49,7 @@ describe("lib/plugin_loader", function() {
let brokenMessage;
try {
// eslint-disable-next-line node/global-require
require(path.resolve(brokenPlugin, "info.js"));
require(path.resolve(brokenPlugin));
} catch (err) {
brokenMessage = err.message;
}