`reach --help` renders from a declaration table and imports nothing. The cost of help is now flat as the registry grows, which is the property that has to hold going from one domain to a dozen. The trap is real and was confirmed in typer's vendored source rather than assumed from upstream Click: TyperGroup.format_commands loops over list_commands calling get_command on each, purely to read a short help string off the loaded command. With lazy loading underneath, that imports every domain in the registry to render --help — while the output looks entirely correct. Nothing observable changes; only the import graph does. So the test asserts on sys.modules, and it was proven to fail before being trusted. Disabling the format_commands override made it fail and name the cause, listing all five leaked check modules. It also carries a positive control — invoking a domain must import its service — because without one, "nothing was imported" would pass equally for a loader that is simply broken, and it fails on an empty registry, which would otherwise satisfy everything vacuously. The check domain is created here because the test needs a subject: a stub raising NotImplementedError would have been committed dead code. That takes the port out of T-1262, which is rescoped to what it still owns — pydantic schemas, byte-for-byte output parity on the drift path, and the failure tests. The old tooling/check-client-version script stays in place and stays wired to the pre-push hook; the deprecation window is deliberate. One Typer behaviour worth knowing before every future domain: a single-command app collapses into a bare command, so `reach check client-version` failed with "unexpected extra argument" until the router got a callback. Same mechanism as the root callback, different symptom. Help now works at every level, closing item 5 of T-1248. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
71 lines
2.5 KiB
Python
71 lines
2.5 KiB
Python
"""Transport for the `check` domain — args in, delegate, format out.
|
|
|
|
**Zero logic lives here.** Every command in this file should read as: parse,
|
|
call a service, turn the result into output and an exit code. If a command
|
|
grows a branch that is about the *problem* rather than about *presentation*,
|
|
that branch belongs in `service.py`.
|
|
|
|
The service import is deliberately at module level: by the time this module is
|
|
imported at all, `reach` has already decided to run a `check` command, so there
|
|
is nothing left to defer. Laziness lives one level up, in `main.py`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import typer
|
|
|
|
from tooling.core import console
|
|
from tooling.domains.check import service
|
|
|
|
app = typer.Typer(
|
|
name="check",
|
|
help="Consistency gates — the checks the push hook runs.",
|
|
no_args_is_help=True,
|
|
add_completion=False,
|
|
rich_markup_mode=None,
|
|
)
|
|
|
|
|
|
@app.callback()
|
|
def _domain() -> None:
|
|
"""Keeps `check` a group.
|
|
|
|
Typer collapses a single-command app into a bare command, so without this
|
|
`reach check client-version` fails with "unexpected extra argument". Every
|
|
domain router needs this until it has two or more verbs — and keeping it
|
|
afterwards costs nothing and stops the shape changing under you.
|
|
"""
|
|
|
|
|
|
@app.command("client-version")
|
|
def client_version() -> None:
|
|
"""Fail if the client's baked version has drifted from project.yaml."""
|
|
result = service.client_version()
|
|
|
|
if result.problem:
|
|
console.verdict(
|
|
f"check-client-version: {result.problem}",
|
|
ok=False,
|
|
fix="check that project.yaml and client/project.godot exist and are readable",
|
|
)
|
|
raise typer.Exit(1)
|
|
|
|
if not result.ok:
|
|
console.verdict(
|
|
"check-client-version: version drift\n"
|
|
f" project.yaml {result.yaml_version}\n"
|
|
f" client/project.godot {result.godot_version}\n"
|
|
"\n"
|
|
"This matters beyond cosmetics: the Atlas disk cache keys its\n"
|
|
"invalidation on this version, so a stale mirror makes a shipped\n"
|
|
"build serve canvases generated by code it no longer runs (T-1239).",
|
|
ok=False,
|
|
fix=(
|
|
"set config/version in client/project.godot's [application] "
|
|
f"section to {result.yaml_version} — project.yaml is the source of truth"
|
|
),
|
|
)
|
|
raise typer.Exit(1)
|
|
|
|
console.verdict(f"check-client-version: OK — {result.yaml_version}")
|