Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
node-version: 22
cache: npm
- run: npm ci
- run: npm run format:check
- run: npm run lint
- run: npm run typecheck
- run: npm test
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
cache: npm

- run: npm ci
- run: npm run format:check
- run: npm run lint
- run: npm run typecheck
- run: npm test
Expand Down
7 changes: 5 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@ ct-state.json
# Generated typed client (regenerated from the live OpenAPI spec)
src/api/schema.d.ts

# Test/coverage output
coverage/
# Test/coverage output — ROOT-anchored: a bare `coverage/` also matches `src/coverage/`
# (the `ct coverage` report module, #103) and would silently drop it from every commit.
/coverage/
.vitest/

# MkDocs output for docs/handbuch/ (built by .github/workflows/docs.yml, never committed)
site/
# …and the venv docs/README.md tells you to create here to build the section locally
.venv-docs/
52 changes: 30 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ code, and reconcile it against the ChurchTools API with Terraform-style
A ChurchTools instance's structure is normally maintained by clicking. That
works — until you need to answer questions clicking cannot:

| Clicking | With `ct` |
| --- | --- |
| "Who changed this group's rights, and why?" | `git log`, and the PR that changed it |
| "Set up the next campus like the last one" | Call the same blueprint function again |
| "Try it somewhere safe first" | `ct plan --env dev` → `ct apply --env dev` → promote |
| "Has anyone edited this by hand?" | `ct plan` reports drift against the last known state |
| "What will this actually change?" | `ct plan` prints it before anything is written |
| Clicking | With `ct` |
| ------------------------------------------- | ---------------------------------------------------- |
| "Who changed this group's rights, and why?" | `git log`, and the PR that changed it |
| "Set up the next campus like the last one" | Call the same blueprint function again |
| "Try it somewhere safe first" | `ct plan --env dev` → `ct apply --env dev` → promote |
| "Has anyone edited this by hand?" | `ct plan` reports drift against the last known state |
| "What will this actually change?" | `ct plan` prints it before anything is written |

## Show me

Expand Down Expand Up @@ -101,11 +101,13 @@ commands.
ct auth login --host https://mychurch.church.tools --token <personal-login-token>
ct auth status # who am I?

ct get groups # JSON to stdout — pipe into jq
ct get groups # JSON to stdout — pipe into jq (every page, not just the first)
ct adopt campus 0 # bring ONE existing resource under management
ct coverage # what the instance has that the config does not manage
ct state list # what is managed
ct plan # diff the config against ChurchTools (read-only)
ct apply # create + update in dependency order (confirm + backup first)
ct refresh --group <key> # make ChurchTools re-evaluate one auto-group now
```

`apply` reconciles **creates and updates** only, saving state after each action
Expand All @@ -116,23 +118,29 @@ confirmation — and a declaration marked `preventDestroy: true` blocks even tha

## What it manages

| Resource | DSL | Guide |
| --- | --- | --- |
| Campuses | `ct.campus` | [config guide](docs/configuration.md) |
| Groups (fields, campus, multi-parent hierarchy) | `ct.group` | [config guide](docs/configuration.md) |
| Group types, roles, age/target groups, relationship types, person statuses | `ct.groupType`, `ct.roleDefinition`, `ct.ageGroup`, `ct.targetGroup`, `ct.relationshipType`, `ct.personStatus` | reusable building blocks |
| Permissions (group-role, group-type-role, person-status) | `ct.groupRole`, `ct.groupTypeRole`, `ct.status` | [permissions](docs/handbuch/permissions.md) |
| Auto-groups (dynamic groups) | the `dynamic` block on a group | [dynamic groups](docs/handbuch/dynamic-groups.md) |
| Repeated structure, parametrized | a plain function over the DSL | [blueprints](docs/handbuch/blueprints.md) |
| Resource | DSL | Guide |
| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
| Campuses | `ct.campus` | [config guide](docs/configuration.md) |
| Groups (fields, campus, multi-parent hierarchy) | `ct.group` | [config guide](docs/configuration.md) |
| Group types, roles, age/target groups, relationship types, person statuses | `ct.groupType`, `ct.roleDefinition`, `ct.ageGroup`, `ct.targetGroup`, `ct.relationshipType`, `ct.personStatus` | reusable building blocks |
| Permissions (group-role, group-type-role, person-status) | `ct.groupRole`, `ct.groupTypeRole`, `ct.status` | [permissions](docs/handbuch/permissions.md) |
| Auto-groups (dynamic groups) | the `dynamic` block on a group | [dynamic groups](docs/handbuch/dynamic-groups.md) |
| Repeated structure, parametrized | a plain function over the DSL | [blueprints](docs/handbuch/blueprints.md) |

Read-only by design: the person master-data model, security levels and
custom-field *definitions* (`ct get person-masterdata`, `ct get data-fields`) —
custom-field _definitions_ (`ct get person-masterdata`, `ct get data-fields`) —
schema in scope, per-record values never. See
[field definitions](docs/handbuch/field-definitions.md).

References are logical throughout: `groupType: "ministry_team"` resolves to that
instance's id at plan time, so the same config drives a dev and a prod instance
unchanged. Numeric ids remain an escape hatch everywhere.
unchanged. Numeric ids remain an escape hatch everywhere — and where one is left
in place, `ct` says so rather than letting a host-specific id travel silently to
another instance.

`ct coverage` answers the other direction: what exists on the instance that the
config does not manage, and which of it could be declared today (per group _and_
role, with the blocking scope dimension named). `--json` makes it a CI gate.

## Environments and CI

Expand Down Expand Up @@ -166,10 +174,10 @@ ct apply --env prod # protected env: type the env name to confirm

## Two-repo model

| Repo | Contents |
| --- | --- |
| **`eqrm/ct-cli`** (this repo) | The tool: CLI, API client, plan/apply engine. Generic, reusable. |
| *your config repo* (private) | Your instance's desired-state config + state files. Depends on this tool. |
| Repo | Contents |
| ----------------------------- | ------------------------------------------------------------------------- |
| **`eqrm/ct-cli`** (this repo) | The tool: CLI, API client, plan/apply engine. Generic, reusable. |
| _your config repo_ (private) | Your instance's desired-state config + state files. Depends on this tool. |

Like Terraform, the tool never lives in the same repo as the infra config — the config
repo holds an organisation's actual structure and stays private. This repo is the tool
Expand Down
22 changes: 11 additions & 11 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ dynamic groups and field definitions behave, independent of any one instance.
These pages are published into a wider ChurchTools Handbuch, so they carry
`sources:` frontmatter and are gated by the staleness checker (#89).

| Page | About |
| --- | --- |
| [`handbuch/index.md`](handbuch/index.md) | Section overview |
| [`handbuch/permissions.md`](handbuch/permissions.md) | Grant domains, `domainId` semantics, scope resolution |
| [`handbuch/dynamic-groups.md`](handbuch/dynamic-groups.md) | Auto-groups, the ChurchQuery DSL, portable rulesets |
| Page | About |
| ---------------------------------------------------------------- | ------------------------------------------------------------- |
| [`handbuch/index.md`](handbuch/index.md) | Section overview |
| [`handbuch/permissions.md`](handbuch/permissions.md) | Grant domains, `domainId` semantics, scope resolution |
| [`handbuch/dynamic-groups.md`](handbuch/dynamic-groups.md) | Auto-groups, the ChurchQuery DSL, portable rulesets |
| [`handbuch/field-definitions.md`](handbuch/field-definitions.md) | Person master data, security levels, custom-field definitions |
| [`handbuch/blueprints.md`](handbuch/blueprints.md) | Parametrized, reusable structure |
| [`handbuch/blueprints.md`](handbuch/blueprints.md) | Parametrized, reusable structure |

**A page publishes only if it lives under `handbuch/` and is reachable from
`handbuch/mkdocs.yml`'s nav.** Everything else in `docs/` stays invisible.
Expand Down Expand Up @@ -48,9 +48,9 @@ A page with no code behaviour to track declares `sources: []` plus a

## Everything else — developer- and operator-facing, unpublished

| Page | About |
| --- | --- |
| [`api-coverage.md`](api-coverage.md) | Which ChurchTools endpoints support which CRUD verbs |
| [`group-field-decisions.md`](group-field-decisions.md) | Which group fields are managed vs. left to the CT UI, and why |
| Page | About |
| -------------------------------------------------------- | --------------------------------------------------------------------- |
| [`api-coverage.md`](api-coverage.md) | Which ChurchTools endpoints support which CRUD verbs |
| [`group-field-decisions.md`](group-field-decisions.md) | Which group fields are managed vs. left to the CT UI, and why |
| [`runbook-manual-surface.md`](runbook-manual-surface.md) | What `ct` cannot automate — API gaps and the manual steps around them |
| `superpowers/` | Historical implementation plans; kept as a record, never published |
| `superpowers/` | Historical implementation plans; kept as a record, never published |
Loading
Loading