Files
factorio-clusterio/packages/lib/src/plugin.ts
T
bbassieandClaude Opus 5.5 aec0fc7a1e Let plugins migrate the data core stores for them
Plugins had no way to update the config fields and permissions core stores
for them, so renaming inventory_sync needed migrations in core packages.

A plugin can now declare a migrationEntrypoint. It's loaded on the
controller and hosts before configs and roles are read, for every plugin in
the plugin list, and attaches handlers to sync hooks for the controller,
host and instance configs and roles. lib gets helpers to rename config
fields and permissions, and each config gets a hidden <plugin>.version field
set after load so migrations can tell which version saved the data.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 15:33:27 +00:00

144 lines
3.9 KiB
TypeScript

/**
* Plugin interfaces and utilities.
* @module lib/plugin
*/
import type { Static } from "@sinclair/typebox";
import type { Logger } from "./logging.js";
import type { ConfigSchema, FieldDefinition } from "./config/index.js";
import type { PermissionDefinition } from "./permissions.js";
import type { PlayerStats, Role } from "./data/index.js";
import type { SyncHook } from "./SyncHook.js";
export const PluginFeatureFlags = [
/** The plugin requires module code to be patched into the save */
"SavePatching",
/** The plugin requires access to script commands over rcon */
"ScriptCommands",
] as const;
/** Used to define the plugin export in plugins */
export type PluginDeclaration = {
name: string;
title: string;
description?: string;
controllerEntrypoint?: string;
controllerConfigFields?: Record<string, FieldDefinition>;
hostEntrypoint?: string;
hostConfigFields?: Record<string, FieldDefinition>;
instanceEntrypoint?: string;
instanceConfigFields?: Record<string, FieldDefinition>;
ctlEntrypoint?: string;
webEntrypoint?: string;
ctlConfigFields?: Record<string, FieldDefinition>;
migrationEntrypoint?: string;
features?: (typeof PluginFeatureFlags)[number][];
messages?: any[];
permissions?: PermissionDefinition[];
routes?: string[];
}
/**
* Check if a plugin is expected to ship a web build.
*
* Mirrors the rule used by the create tool: a web build is generated when the
* plugin has a web or controller entrypoint or defines config fields.
*/
export function pluginNeedsWebBuild(info: PluginDeclaration) {
return Boolean(
info.webEntrypoint
|| info.controllerEntrypoint
|| info.controllerConfigFields
|| info.hostConfigFields
|| info.instanceConfigFields
|| info.ctlConfigFields
);
}
export type PluginNodeEnvInfo = PluginDeclaration & {
/**
* Path to the folder with the static files that should be hosted on the web
* server in order for the web interface to be able to load the plugin.
*/
webStaticPath: string;
/**
* Absolute path to the package.json file for the plugin.
*/
packagePath: string;
requirePath: string;
version: string;
manifest: any;
/**
* NPM package this plugin is published as. Not present if the package
* is private or the path used to load it does not match the name of the
* package.
*/
npmPackage?: string;
};
export type PluginWebpackEnvInfo = PluginDeclaration & {
container?: any;
package?: any;
enabled?: boolean;
error?: string;
};
/**
* Information about the event.
*/
export interface PlayerEvent {
type: "join" | "leave" | "import" | "promote" | "demote" | "ban" | "unban" | "whitelisted" | "unwhitelisted";
/** Name of the player that caused the event */
name: string,
/**
* Only present for type "leave" and "ban". Reason for player leaving the
* game, one of the possible reasons in defines.disconnect_reason
* or "server_quit" if the server exits while the player is online.
* When type is "ban" this is the reason given for the ban.
*/
reason?: string;
/**
* Statistics recorded for this player on the instance this event
* originated from.
*/
stats: PlayerStats,
}
export type PluginLoadContext<
Context extends object,
Info extends PluginDeclaration = PluginNodeEnvInfo
> = Context & {
logger: Logger;
plugin: Info;
};
/** Hooks for migrating data that Clusterio stores on behalf of plugins */
export type MigrationHooks = {
controllerConfig: SyncHook<ConfigSchema>;
hostConfig: SyncHook<ConfigSchema>;
instanceConfig: SyncHook<ConfigSchema>;
role: SyncHook<Static<typeof Role.jsonSchema>>;
};
export type MigrationContext = PluginLoadContext<{ hooks: MigrationHooks }>;
export type PluginClass<
Context extends object,
Info extends PluginDeclaration,
> = {
new (...args: any[]): any;
fromContext(context: PluginLoadContext<Context, Info>): {
init(): Promise<void>;
detachHooks(): void;
};
};
export type PluginType =
Extract<keyof PluginDeclaration, `${string}Entrypoint`> extends `${infer P}Entrypoint` ? P : never;