feat: Phase 2 — ct adopt + JSON state file - #10
Merged
Merged
Conversation
Selective, opt-in management: bring one existing ChurchTools resource under management and record it in a committable JSON state file. Nothing outside the state file is visible to the tool. - src/state/state.ts: State schema (version, host, resources keyed by logical key), load/save, and an idempotent upsert (re-adopting the same type+id updates in place, re-keys on key change, rejects key collisions). All id comparisons are null-checked — CT ids can be 0 (Mainz campus). - src/resources/registry.ts: adoptable-type registry (campus, group, group-type) with item paths from the Phase 0 matrix, key derivation, managed-field snapshots, and TS-as-code config-snippet rendering. - ct adopt <type> <id> [--key] [--state] [--dry-run]: fetch by id, snapshot, upsert into state; --dry-run previews the generated config entry without writing. Host guard refuses to mix instances. - ct state list: show the managed set. - 17 new tests (state upsert/idempotency/id-0/rekey/collision, registry slug/paths/snippet, adopt write/idempotency/dry-run/unknown-type) = 34. Refs #4
The entrypoint guard compared import.meta.url (the module realpath) against file://+argv[1] (the symlink path). Invoked through the npm-link / Homebrew `ct` symlink these never matched, so `ct --help` exited 0 printing nothing. Resolve both sides to their realpath via a tested isMainModule() helper. Refs #4
Correctness:
- adopt: reject non-integer ids strictly (/^\d+$/) — Number.parseInt
accepted "0x10"→0, "3abc"→3, "2.9"→2, silently adopting the wrong id.
- state: validate the state file's shape (top-level object, version, host,
resources) and surface friendly errors instead of cryptic TypeErrors on a
hand-edited/committed file (e.g. missing "resources", top-level null,
invalid JSON).
- state: move the host-mismatch guard into loadState so every command
inherits it — `ct state list` previously listed another instance's
resources with no warning.
- adopt: load + validate the state file (host guard included) BEFORE
authedSession()/GET, so a mismatched instance never triggers a live
authenticated request against the wrong host.
- registry: strip NFKD combining marks in slug() so German names slug to
their base letters ("Zürich"→"zurich") instead of gaining underscores.
- registry: snapshot group ids from a nested `information` object OR the top
level, so the baseline isn't silently empty if the API shape differs.
- isMain: resolve each path independently in the realpath fallback so an
unresolvable argv1 still compares against the module's realpath.
Cleanup:
- adopt: drop the redundant second resolveConfig() (single source now).
- registry: derive itemPath from a single collection-path literal per entry.
Refs #4
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the first desired-state capability (#4): bring existing ChurchTools structure under management, selectively, into a committable JSON state file. Also fixes a symlink-entrypoint bug that made the global
ctsilent.Phase 2
src/state/state.ts) —{ version, host, resources }keyed by logical key, storing CT id + a managed-field snapshot (diff baseline). Idempotentupsert: re-adopting the same(type,id)updates in place, re-keys on key change, rejects a key taken by a different resource. All id comparisons null-checked — CT ids can be0(Mainz isid: 0).src/resources/registry.ts) —campus,group,group-typewith item paths from the Phase 0 matrix, key derivation, field snapshots, and TS-as-code config-snippet rendering.ct adopt <type> <id> [--key] [--state] [--dry-run]+ct state list.--dry-runpreviews the generated config entry without writing; a host guard refuses to mix instances.Bug fix (was affecting main)
The entrypoint guard compared
import.meta.url(realpath) tofile://+argv[1](symlink path), so the global/brewctsymlink ran but printed nothing (exit 0, 0 bytes). Fixed with a testedisMainModule()that realpaths both sides.Tests — 39
Phase 2 (state upsert/idempotency/id 0/rekey/collision, registry, adopt write/dry-run/unknown-type) + entrypoint symlink cases. typecheck · lint · build green.
Live-verified ✅
Against the instance (CT 3.134.0):
ct adopt campus 0→ state written;state listshows it; re-adopt = "Updated", single entry. Globalct --helpandct auth statusconfirmed working after the fix.Refs #4