Skip to content

Add new config keys to existing files at startup - #52

Merged
Pixnop merged 1 commit into
devfrom
feat/config-upgrade
Sep 5, 2026
Merged

Pixnop merged 1 commit into
devfrom
feat/config-upgrade

Conversation

@Pixnop

@Pixnop Pixnop commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

A hosting provider running Pulse in production asked for this. Their admins upgrade the mod, read the changelog, and then find the new keys nowhere in the pulse.json they already have. LoadModConfig fills a missing key from the class default in memory and never writes it back, so the Attribution block added in this cycle only ever appears in a file created from scratch. Same story for ServiceName in pulse-otlp.json.

Both mods now compare their file against the config object the loader handed back, right after loading it, and rewrite the file when a key is missing. That object already carries every value the admin set, so the rewrite adds the missing defaults and changes nothing else. One notification names what was added.

The comparison runs the other way too. A key in the file that the config class knows nothing about, a typo or a key some release retired, does not survive the rewrite, so it gets a warning of its own rather than vanishing without a word. When the file is already complete, nothing is written at all: no rewrite, no modification time change, which matters on a host that mounts ModConfig read-only or keeps it under version control. Text that does not parse as a JSON object is left alone for the same reason.

What an admin upgrading from 0.1.0 sees, lifted off a scenario's own server log:

[Notification] Pulse added these keys to pulse.json with their defaults: Enabled, Bind, RuntimeMetrics, ChunksRefreshSeconds, Attribution. Everything already in the file was kept as it was.
[Warning] Pulse does not know these keys in pulse.json, and rewriting the file has just dropped them: Colour. Check them for typos.

Pulse.Otlp still does not reference Pulse.dll. The two mods meet at a meter name and nothing else, on purpose, so the comparison is shared as a linked source file and each assembly compiles its own internal copy of it. What it compares against is JsonUtil.ToPrettyString, which is the exact JsonConvert.SerializeObject(obj, Formatting.Indented) call StoreModConfig writes with, so the keys weighed are the keys a rewrite produces. No new package reference anywhere: System.Text.Json.Nodes out of the runtime does the key walk, and the base mod stays a single dependency-free dll.

Tests go from 183 to 195, all green locally. Ten unit tests cover the comparison itself, including key order, formatting, differing values, nesting, unknown keys at both depths, comments and trailing commas, and text that is not JSON at all. Two scenarios boot a real server on a seeded partial file and read the result back off the ModConfig data path: the base one checks that the admin's port survived into both the file and the bound socket, that Attribution arrived with its defaults, and that the unknown key is gone; the OTLP one checks that ServiceName was filled in. The mutation check gains three mutations for the new code and reports 41/41 killed.

LoadModConfig fills a missing key from the class default in memory and leaves the file alone, so an admin upgrading from 0.1.0 never saw the Attribution block appear in pulse.json, nor ServiceName in pulse-otlp.json, and had to learn about them from the README and type them in.

ConfigUpgrade compares the file on disk against the config object the loader handed back, recursively and by key name only, and reports both what the file is missing and what it carries that the config knows nothing about. When something is missing the file is rewritten from that object, which already holds every value the admin set, so the write only ever adds; the log then names the keys that were added. An unknown key does not survive that rewrite, so it gets a warning of its own rather than disappearing quietly. A file that is already complete is not written at all, which keeps a read-only or version-tracked ModConfig untouched.

The comparison is against JsonUtil.ToPrettyString, which is the same JsonConvert.SerializeObject(obj, Formatting.Indented) call StoreModConfig writes with, so the keys weighed here are exactly the keys a rewrite produces. Pulse.Otlp compiles the helper from the same source file rather than referencing Pulse.dll, since the two mods still meet at nothing but a meter name.
@Pixnop
Pixnop merged commit 9a8157d into dev Sep 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant