* Send only recipe notification changes on inventory download
Lua includes its current cleared recipe list in the download request. The instance plugin diffs it against the stored list and sends back the names to add and remove instead of the full list. Lua rebuilds the full list from the snapshot kept in the download record and runs the existing deserializer, so the end state is unchanged.
Also stop the serializer from appending to the stored failed recipe list in place, which grew the list on every upload without a download in between.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Test the inventory sync download handler
Covers the empty response, the full list without a snapshot, the delta with a snapshot, and the IPC registration.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Apply recipe notification deltas directly in Lua
Review feedback: the download field now always carries the delta and the deserializer applies it against the current state instead of rebuilding the full list first. The previous failed recipe names are passed in so they survive the round trip. The version check moved to the call site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Fix failed not being cleared
* Send only the local state on download init
The current state sent with the download request no longer includes the failed names from the previous deserialization. Stored names unknown on this instance always end up in the add list, so the deserializer recomputes the failed list from that on every download and needs no previous state.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Convert inventory sync test to ES module
Master migrated to ESM after this branch was created, so the CommonJS test failed to load on the merge commit.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
Config files hold the auth secret and tokens, so the default 0o644 lets
every user on the system read them. Config.save, the ctl config written
by the controller bootstrap, ctl host create-config and the config
migration in create now pass mode 0o600.
safeOutputFile also applies the mode to the temporary file it writes,
since writeFile only honours mode on creation and a leftover temporary
from a failed write would otherwise keep its old mode.
Closes#1005
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Master migrated to ESM after this branch was created, so the CommonJS test failed to load on the merge commit.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds a permissions array to PluginDeclaration, a type-level Permissions
registry that plugins extend with module augmentation, and
registerPluginPermissions() called from all entrypoints. checkPermission
and the web UI hasPermission helpers now take PermissionName so typos are
compile errors. Core definitions move into a corePermissions array that
the interface derives its keys from.
Closes#987
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Cooldude2606 <25043174+Cooldude2606@users.noreply.github.com>
The current state sent with the download request no longer includes the failed names from the previous deserialization. Stored names unknown on this instance always end up in the add list, so the deserializer recomputes the failed list from that on every download and needs no previous state.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Migrate the usage of CommonJS specific code and imports to ESM. Roughly
summarised this:
- Changes all require() calls to import statement or expressions.
- Changes usage of module.exports to export statements.
- Replaces require.resolve with import.meta.resolve.
- Replaces conditional imports with conditional subpath imports.
- Replaces module === require.main with import.meta.main.
- Adds the full filename with extention to all relative imports.
- Changes all usage of __dirname and __filename to their import.meta
equivalents.
- Sets "type": "module" in package.json files.
- Removes usage of "use strict".
- Adds .ts an an alias of .js for the webpack build.
Due to differences in how require and import resolves files the plugin
entrypoints will have to include the filename and extension after this
migration in order to work.
Review feedback: the download field now always carries the delta and the deserializer applies it against the current state instead of rebuilding the full list first. The previous failed recipe names are passed in so they survive the round trip. The version check moved to the call site.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
resolveHost, resolveInstance, resolveModPack and retrieveRole treated any
integer like argument as an id and never looked it up as a name. An
instance created as "10" could not be referenced by name afterwards. The
lookup now treats the argument as an id only if an entry with that id
exists, and otherwise falls back to a name match.
Closes#886
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Values in the assetsByChunkName object are arrays not strings. This
caused the dev mode to fail after 2f4e0c4418 added an additional step
filtering out the static/ prefix from what it expected to be a string.
Use a fictional restart_test mock plugin added as a before step instead
of global_chat for the .checkRestartRequired() test. Prevents the test
from failing if the integration tests are not also run.
Also fixes the fixture not accounting for setting load_plugin causing
restartRequired to be set to true due to the integration tests having
already set it to the same value.
The ESM import of yargs does not support the singleton way of using
yargs, instead we have to call the yargs function to get a parser that
can be augmented with the options.
The .argv property has also been deprecated. Replace it with a call to
.parse() instead.
Covers the empty response, the full list without a snapshot, the delta with a snapshot, and the IPC registration.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Lua includes its current cleared recipe list in the download request. The instance plugin diffs it against the stored list and sends back the names to add and remove instead of the full list. Lua rebuilds the full list from the snapshot kept in the download record and runs the existing deserializer, so the end state is unchanged.
Also stop the serializer from appending to the stored failed recipe list in place, which grew the list on every upload without a download in between.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Generic error handlers such as FactorioServer.handle() and the event
handler catch in Link only log err.stack, so the ajv errors attached to
an InvalidMessage were lost unless the catch site checked for the class
explicitly. Appending the errors to the stack in the constructor makes
every stack-logging handler show which field failed validation without
special casing each one.
Fixes#986
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Accounts with core.instance.assign get a host selector, defaulting to
unassigned. Picking a host offers "Start after creation", which creates a
new save on first run when no save is copied, and together with a config
to copy it offers "Copy Save". Copying a config also offers a server
name, prefilled from the source, which overwrites factorio.settings.name
so the clone does not show up under the source name in the server
browser.
useInstanceConfig now clears the config while loading and drops errors,
so an account without core.instance.get_config just does not get the
server name field.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The built-in shortcuts are not toggleable in their prototypes and
set_shortcut_toggled only affects custom Lua shortcuts, so the state
behind them is synced instead: allow_dispatching_robots on the
character, inhibit_movement_bonus on the armor grid, and every field of
game_view_settings. map_view_settings is write-only and cannot be read.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The create instance modal can now copy the active save of the instance
whose config is copied, optionally saving the running game first, and
start the new instance once the copy is done. The new instance is
assigned to the host of the source so the save can be transferred.
Saving a running game is a new InstanceSaveGameRequest handled by the
host through /server-save, which waits for the save to finish. It is
also exposed as clusterioctl instance save-game.
Closes#405
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The toggle state of the personal roboport and equipment movement bonus
shortcuts lives on the player and was not part of the serialized player
data, so it reset when switching instances.
Closes#898
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add option in the controller config to set where static assets are
loaded from in order to support deployments where the static assets are
server via a CDN while the API is directly accessed.
This is useful because some CDNs put low limits on how large requests
bodies can be, which causes issues with uploading large mods and saves.
The codecov/patch check flagged the new routes code as uncovered. The
unit tests never hit /api/plugins, so cover the static/ stripping and the
manifest error paths directly.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
staticRoot was set to the same value as webRoot, so the static/ prefix
ended up scattered across consumers: index.html relied on the manifest
path containing it, the export stores hardcoded `${staticRoot}static/`,
and bootstrap loaded plugin bundles from webRoot instead. Now staticRoot
is `${webRoot}static/` and the controller strips the static/ prefix from
bundle paths before handing them to the page and to /api/plugins.
Plugins that fetch assets with `${staticRoot}static/...` need to drop the
static/ part.
Fixes#980
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same setup as established in ExpCluster, emmylua_check with factorio
typedefs generated by fmtk luals-addon. The moduleMap resolves
modules/clusterio and modules/<plugin> requires to their source under
packages/host and plugins, and the mod scaffolding under
packages/host/lua to bare sibling requires.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The largest source was compat.lua returning the setmetatable call
directly, which made emmylua infer the module type from the metatable
rather than the LibCompat class, hiding every field from consumers.
The remaining fixes are nil guards on gui element and character
accesses, type annotations for research_sync's script data and
player_auth's rcon arguments, casts where the api's read/write union
types are wider than the values at runtime, and scoped diagnostic
disables for the vendored json.lua and the version fallback code that
touches apis removed from the current typedefs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The smoke test only proves the handshake and a few core requests still
work, while compat most often breaks because the serialization of a
link message changed. Dump the registered link messages of the local
and published lib in separate processes (the lib forbids loading two
copies in one) and diff their src, dst and json schemas. A changed
message fails the test, added and removed messages only produce a
warning annotation since breaking compat is unavoidable there.
Requested by Cooldude2606 in review of #978.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runs a smoke test in both directions between the current code and the
latest release published to npm: the host connects to the controller and
gets an instance assigned. Testing only against the latest release relies
on each release being compatible with the previous one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Support enemy_expansion.build_base_unit_dispatch_cooldown in exchange strings
Factorio 2.1.13 added build_base_unit_dispatch_cooldown as the last
field of enemy_expansion in the map exchange string, and rejects map
settings without it. Track the full version in the reader state and
read the field for 2.1.13 and later. Adds a fixture string generated
with 2.1.14 and uses it in the load-scenario integration test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Replace version flags with versionAtLeast in exchange string reader
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
useTableQueryState returns pagination=false unless a pagination config
is passed, and SavesList did not pass one after #930, so the saves table
rendered all saves on one page.
Fixes#967
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* Filter dependency resolution by the mod pack's Factorio version
The dependency resolver picked the newest release satisfying the version
range regardless of which Factorio version it targeted, so a 2.0 mod pack
could end up with 2.1 dependencies. Only consider portal releases and
local mods whose factorio_version matches the mod pack.
Fixes#965
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Move dependency resolver tests to test/messages/mod.test.js
Also update the existing local mod fixtures to carry a factorio_version
and drop enable-all-feature-flags from the live expectations: the 2.0
releases of pyalternativeenergy only list it as optional, it was only
pulled in before because 2.1 releases were being selected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
ensure_character asserted on player.create_character() returning false
without any context (#912). The most likely trigger is the player being
physically inside a space platform hub (or otherwise stuck in remote view)
on the destination server: the character in the hub is destroyed while
downloading and the god controller ends up on the platform surface where
a character can not be created.
- Explicitly exit remote view and move the god controller off platform
surfaces before create_character; restore_position re-enters the platform
- Include controller/surface/connection state in the error message
- On deserialization failure treat the player as a temporary inventory
(sync = false) so the incomplete state is not uploaded over synced data
- Release the acquisition on leave when nothing is uploaded so the player
is not left "busy" on other instances
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>