23 Commits
Author SHA1 Message Date
bbassieandClaude Opus 5.5 389004d652 Replace RequestError with the HTTP-style error classes
clusterio#999 removed RequestError. Handlers now throw NotFoundError,
ValidationError, ConflictError or UnavailableError, and exp_auto_restart
treats any ClientError from the host as a final refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 23:43:25 +00:00
bbassieandClaude Opus 5.5 afc1d35668 Load datastores with SubscribableDatastore.fromProvider
clusterio#1129 replaced DatastoreProvider.bootstrap with async factories
and Instance now takes its FactorioServer in the constructor, so the
instance tests pass their server stub there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 23:43:25 +00:00
bbassieandClaude Opus 5.5 e84bd97b99 Build plugin web bundles with Vite
clusterio#1130 replaced webpack with Vite and the web interface rejects
webpack-built plugin bundles. Each plugin now builds with clusterioPlugin()
from @clusterio/web_ui/vite.mjs, and the webpack configs and dependencies
are gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 23:43:25 +00:00
bbassieandClaude Opus 5.5 38fe2ed62a Address review on the exp_reports watch list
Save both datastores on the save hook so autosave covers them, the
controller also runs it at shutdown. Watch list names are case
insensitive, matching Factorio and Clusterio users. The new
exp_reports.announce_to_all_instances config, on by default, can limit
the join message to the instance the player joined. The Discord embed
lists the three newest unread reports and counts the rest in the footer,
since Discord rejects embeds with more than 25 fields. Both tables now
sort newest first by default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:01:44 +00:00
bbassieandClaude Opus 5.5 44ae3a5f0e Add a watch list and read state to exp_reports
Players on the watch list are announced to every online admin on every
instance when they join, and the join is posted to the Discord and JSON
webhooks. The Reports page has a second table for the watch list with an
Add button and a Delete button per row.

Reports can be marked read or unread from the web interface. Each report
row also has an Add to watch list button which opens the add form filled
in with the player and reason. Setting exp_reports.watch_unread_reports
treats players with unread reports as watched.

Reports now store created_at_ms because marking them read changes
updated_at_ms. Old reports.json files migrate on load with updated_at_ms
as the creation time. The report webhook moved from the datastore update
listener to the create handler so reading a report is not posted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:36:22 +00:00
Cooldude2606 82abc2c332 7.0.4 2026-10-17 16:54:48 +01:00
Cooldude2606 f3553dd570 Fix publish issue in some plugins 2026-10-17 00:05:24 +01:00
Cooldude2606 9d19f4eaea 7.0.2 2026-10-16 14:13:17 +01:00
BastiaanandClaude Opus 5.5 8d7d5ff10f Add a readme to every plugin and list them in the main readme (#505)
Commands, Permission Groups, Player Statistics, Reports, Roles, Scenario,
Server UPS and Module Utilities get a readme covering their config,
permissions, commands, web pages and Lua and Node API.

The existing readmes are brought up to date: Auto Restart now that
clusterio marks a restart after a plugin install, Discord Auth gets the
code timeout, command aliases and its Lua API, and the GUI guide says what
the plugin is and has its toolbar examples and an iterator name fixed.

The main readme gets a Plugins section listing each plugin by name and
package with a one line description.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 23:12:48 +01:00
BastiaanandClaude Opus 5.5 8e7ac3c872 Pick a long bracket the json does not close in rcon commands (#506)
exp_reports, exp_roles and exp_groups wrapped the json they send to Lua in
a fixed [=[ ]=] long bracket. A report reason, role name or group name
containing ]=] closed it early and the rest ran as Lua through /sc. Any
player can make a report, so any player could run code on the server.

Use the next level the json does not contain, like the other plugins do.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 17:07:47 +01:00
Bastiaan e25049397e Merge pull request #496 from bbassie/chore/optional-peer-deps
Mark controller and host as optional peer dependencies
2026-10-14 22:49:25 +01:00
BastiaanandClaude Opus 5.5 68a964cf57 Write plugins without state shared elsewhere as functions (#492)
The instance plugins of exp_groups, exp_reports, exp_roles, exp_scenario
and exp_server_ups, and the controller plugins of exp_reports and
exp_scenario, were classes only to hold their handlers. They are now
init functions with closures, like clusterio's own plugins.

The exp_roles and exp_groups controller plugins stay classes because
exp_scenario reaches their datastores through get(controller).
AutoRestart stays a class because its tests step check() with a fake
clock.

Tests spy on handle() and the hooks to call the handlers. The reports
webhook tests stub fetch instead of replacing postWebhook, and the
feature reconcile test writes the stored records to disk before start.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:50:15 +01:00
Cooldude2606 26a801da5e Bump node requirement 2026-10-13 21:06:46 +01:00
Cooldude2606 cdf0cd2e04 Add npmignore to enable packing 2026-10-13 21:01:48 +01:00
BastiaanandClaude Opus 5.5 ea9bf1fb78 Construct plugins with a static fromContext (#487)
Each plugin class now has a private constructor and a static async
fromContext(context) that loads its datastores, constructs the instance
and attaches its handlers and hooks. This replaces the public constructor
followed by init(), and AutoRestart's start().

Datastores are loaded before construction and passed to the constructor,
so the definite assignment assertions on them are gone. Tests build the
plugins through fromContext and pass a wrapped logger in the context
instead of overwriting plugin.logger before init().

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 01:19:53 +01:00
Cooldude2606 46bb15e4a6 Merge pull request #483 from bbassie/refactor/init-function-exports
Export init functions instead of plugin classes
2026-09-27 01:53:11 +01:00
BastiaanandClaude Opus 5.5 4489794e1a Require the exp_reports commands instead of loading them (#482)
Files under "load" in module.json are passed to event_handler.add_lib, so
they must return an event handler table. commands.lua registers the report
commands and returns nothing, so require gave true and every instance with
exp_reports enabled failed in on_load with "attempt to index local 'lib'
(a boolean value)". Listed under "require" it runs the same way the
exp_commands command files do.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 01:42:11 +01:00
bbassieandClaude Opus 5.5 0ff07c012c Export init functions instead of plugin classes
clusterio logs "using deprecated class export" for every node entrypoint
that exports a Base*Plugin subclass, and the web UI logs "using deprecated
class hooks" for WebPlugin classes. Every entrypoint now has a default
export function, like exp_auto_restart and the exp_roles web plugin.

The classes stay where they hold state, but no longer extend the base
classes. The constructor takes the load context and init() attaches the
hooks the base class used to attach from the on* methods, so the tests can
still call those methods directly. exp_server_ups sends its RCON command
through instance.sendRcon with the plugin name, which is what the removed
BaseInstancePlugin.sendRcon did.

The instance entrypoints of exp_commands, exp_gui, exp_legacy, exp_util
and exp_scenario were empty classes, kept only so the host loads the
plugin and patches its module into the save. An empty function does the
same.

The exp_groups web components read the subscriptions through exported
useGroups, useAssignments and useRoleMappings hooks instead of looking
the plugin up in control.plugins, which only holds class plugins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:10:51 +00:00
Cooldude2606 ddf8497fa3 Small fixes to exp_reports 2026-09-27 00:58:32 +01:00
bbassieandClaude Opus 5.5 fd0401e781 Format names of players who never joined the instance
Reports carry names from the controller, so the reported player, the
reporter or the caller may never have joined this server. The exp_util
formatter asserts the player exists, so those names now fall back to
plain white text. The test stub of the formatter now requires a LuaPlayer
like the real one, which made the existing receiver tests fail before the
fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 23:22:18 +00:00
bbassieandClaude Opus 5.5 12ac7052a1 Move exp_reports to ES modules and declare its permissions
Same changes the other packages got in #475 and #476. The permissions move
from definePermission calls into the plugin declaration, with the
Permissions augmentation in messages.ts so the web build sees it.
"type": "module", .js entrypoints and imports, webpack.config.cjs, and the
tap tests use import with --type-strip-only.

The web plugin now uses a default export function instead of the
deprecated BaseWebPlugin class, like exp_roles.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 23:22:18 +00:00
bbassieandClaude Opus 5.5 9ddb7b362b Move the report commands into exp_reports
The create-report, get-reports and clear-reports commands now live in the
plugin's own module next to the functions they call, with their locale.
The permissions keep their exp_scenario.command names because the command
authority derives them from the command name.

The Discord report alert in exp_scenario is gone since exp_reports posts to
its own webhook. The shared add_player stub takes an options table with
connected and admin defaulting to true and false, replacing the copy in
the exp_reports test environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 23:22:18 +00:00
bbassie bad98c5013 Add the exp_reports plugin and move the scenario onto it
Reports were the last legacy control module, kept in the lua state of
each instance. They are now an exp_reports plugin which stores them on
the controller: a report is immutable and holds the reported player, who
made it, the reason, the instance name and when it was made, and the only
operations are get, list, create and delete.

In game nothing is stored. The lua module sends each request over ipc,
the instance plugin asks the controller and hands the answer back with
rcon, and the module prints it to whoever asked if they are still online.
Answers are dropped if the instance stopped meanwhile. The module raises
on_player_reported with the count and reporters of every report against
the player, which report_jail and discord_alerts use, and the commands
and player list button call into it.

The web ui gets a /reports page: a table with search on the player,
reporter and reason, a filter on the instance, delete per row and a
create form which reports in the name of the web user.

New reports are posted to two optional webhooks configured on the
controller, a Discord channel hook as an embed and a generic JSON hook.
Failures are logged rather than failing the report.

Tests cover the messages, the controller against a real Controller, the
instance plugin against a real Instance, and the lua module through the
shared harness.
2026-09-06 16:44:23 +00:00