Files
factorio-clusterio/packages/lib/src/migrations.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

80 lines
2.4 KiB
TypeScript

/**
* Helpers for plugin migrations
* @module lib/migrations
*/
import type { Static } from "@sinclair/typebox";
import type { ConfigSchema } from "./config/classes.js";
import type Role from "./data/Role.js";
type SerializedRole = Static<typeof Role.jsonSchema>;
/**
* Rename a field in a serialized config
*
* The value is moved if the old field exists and the new one doesn't. The
* old field is removed either way.
*
* @param config - Serialized config to update.
* @param from - Current name of the field.
* @param to - New name of the field.
* @returns The config passed in.
*/
export function renameConfigField(config: ConfigSchema, from: string, to: string) {
if (Object.hasOwn(config, from)) {
if (!Object.hasOwn(config, to)) {
config[to] = config[from];
}
delete config[from];
}
return config;
}
/**
* Rename all fields starting with a prefix in a serialized config
*
* @example
* lib.renameConfigPrefix(config, "inventory_sync.", "player_sync.");
*
* @param config - Serialized config to update.
* @param from - Current prefix of the fields.
* @param to - New prefix of the fields.
* @returns The config passed in.
*/
export function renameConfigPrefix(config: ConfigSchema, from: string, to: string) {
for (const name of Object.keys(config)) {
if (name.startsWith(from)) {
renameConfigField(config, name, to + name.slice(from.length));
}
}
return config;
}
/**
* Rename a permission in a serialized role
*
* @param role - Serialized role to update.
* @param from - Current name of the permission.
* @param to - New name of the permission.
* @returns The role passed in.
*/
export function renamePermission(role: SerializedRole, from: string, to: string) {
return renamePermissionsWith(role, name => (name === from ? to : name));
}
/**
* Rename all permissions starting with a prefix in a serialized role
*
* @param role - Serialized role to update.
* @param from - Current prefix of the permissions.
* @param to - New prefix of the permissions.
* @returns The role passed in.
*/
export function renamePermissionPrefix(role: SerializedRole, from: string, to: string) {
return renamePermissionsWith(role, name => (name.startsWith(from) ? to + name.slice(from.length) : name));
}
function renamePermissionsWith(role: SerializedRole, rename: (name: string) => string) {
role.permissions = [...new Set(role.permissions.map(rename))];
return role;
}