feat: deployment-scoped requires + UI editors, gateway nav, services filter
Model:
- Drop `requires` from ProgramSpec; requires is now deployment-only.
Collapse Requirement to {ref, bind} with kind defaulting to "deployment"
(drop the unused `version`). System-package preconditions stay on the
program as `system_dependencies`, synthesized into {kind: system} for the
functional check. relations.py/deploy.py read only the deployment's requires.
- create.py seeds a stack's substrate dependency onto the deployment, not the
program. Docs + tests updated.
UI:
- Add a `requires` editor (useRequires) on service, job, and static detail
pages — edit service→service deps (ref + optional bind env var).
- Restore the gateway route table "Open" column: a kind-aware in-app link to
each route's deployment.
- Reformat the reach control: name baked into each option
(localhost:PORT (local) / <name>.<domain> (internal) / <name>.<pubdomain> (public)).
- Services page: sort statics in with systemd services by name, add a
search + kind-filter bar mirroring the programs page.
This commit is contained in:
@@ -39,12 +39,12 @@ STACK_BUILD_OUTPUTS: dict[str, str] = {
|
||||
"react-vite": "dist",
|
||||
}
|
||||
|
||||
# Substrate a stack's apps depend on — seeded as a `requires` at creation so the
|
||||
# relationship graph shows it. This keeps `stack` uncoupled from the runtime model:
|
||||
# the stack declares the edge once here; the graph only ever reads the encoded
|
||||
# Substrate a stack's apps depend on — seeded as a deployment `requires` at creation
|
||||
# so the relationship graph shows it. This keeps `stack` uncoupled from the runtime
|
||||
# model: the stack declares the edge once here; the graph only ever reads the encoded
|
||||
# `requires`, never the stack. See docs/relationships.md.
|
||||
STACK_REQUIRES: dict[str, list[Requirement]] = {
|
||||
"supabase": [Requirement(kind="deployment", ref="supabase")],
|
||||
"supabase": [Requirement(ref="supabase")],
|
||||
}
|
||||
|
||||
|
||||
@@ -120,13 +120,18 @@ def run_create(args: argparse.Namespace) -> int:
|
||||
source=str(project_dir),
|
||||
stack=stack,
|
||||
build=build,
|
||||
# Seed the stack's substrate dependency (e.g. supabase) as a real `requires`.
|
||||
requires=list(STACK_REQUIRES.get(stack or "", [])),
|
||||
)
|
||||
# The stack's substrate dependency (e.g. supabase) is a deployment-to-deployment
|
||||
# `requires` — seeded on the deployment so the relationship graph shows the edge.
|
||||
seeded_requires = list(STACK_REQUIRES.get(stack or "", []))
|
||||
if kind == "tool":
|
||||
# A PATH-managed deployment: installed via `uv tool install`, no unit/route.
|
||||
config.tools[name] = PathDeployment(
|
||||
id=name, manager="path", program=name, description=description
|
||||
id=name,
|
||||
manager="path",
|
||||
program=name,
|
||||
description=description,
|
||||
requires=seeded_requires,
|
||||
)
|
||||
elif kind == "static":
|
||||
# A caddy-managed static deployment: no systemd unit, served from the build dir.
|
||||
@@ -136,6 +141,7 @@ def run_create(args: argparse.Namespace) -> int:
|
||||
program=name,
|
||||
root=static_root or "dist",
|
||||
description=description,
|
||||
requires=seeded_requires,
|
||||
)
|
||||
elif kind == "service":
|
||||
prefix = name.replace("-", "_").upper()
|
||||
@@ -151,6 +157,7 @@ def run_create(args: argparse.Namespace) -> int:
|
||||
health_path="/health",
|
||||
)
|
||||
),
|
||||
requires=seeded_requires,
|
||||
proxy=True, # expose at <name>.<gateway.domain>
|
||||
manage=ManageSpec(systemd=SystemdSpec()),
|
||||
# python-fastapi scaffold reads env_prefix MY_SERVICE_ — map castle's
|
||||
|
||||
@@ -105,12 +105,13 @@ class TestCreateCommand:
|
||||
comp = config.programs["guestbook"]
|
||||
assert comp.stack == "supabase"
|
||||
assert comp.build is not None and comp.build.outputs == ["public"]
|
||||
# The supabase stack seeds a `requires` on the substrate so the graph shows
|
||||
# the dependency (stack stays uncoupled — it just declares the edge once).
|
||||
assert [(r.kind, r.ref) for r in comp.requires] == [("deployment", "supabase")]
|
||||
dep = config.statics["guestbook"]
|
||||
assert dep.manager == "caddy"
|
||||
assert dep.root == "public"
|
||||
# The supabase stack seeds a `requires` on the substrate (on the deployment)
|
||||
# so the graph shows the dependency (stack stays uncoupled — it just declares
|
||||
# the edge once).
|
||||
assert [(r.kind, r.ref) for r in dep.requires] == [("deployment", "supabase")]
|
||||
assert config.deployments_of("guestbook") == [("guestbook", "static")]
|
||||
|
||||
def test_create_duplicate_fails(self, castle_root: Path, capsys: object) -> None:
|
||||
|
||||
Reference in New Issue
Block a user