Rename Castle -> Wild PC across the repo
Repo-side rename only (Phases 1-3 of the migration plan); the live box (~/.castle, systemd units, /data/castle, domains) is a separate cutover. - Slug `castle` -> `wildpc`: CLI command, module names (wildpc_core/cli/api), dist names, entry point `wildpc = wildpc_cli.main:main`. - Identifiers: CastleConfig/NATSClient/DirError/MDNS -> Wildpc*. - Env/constants: CASTLE_* -> WILDPC_*; ~/.castle -> ~/.wildpc, castle.yaml -> wildpc.yaml, /data/castle -> /data/wildpc. - Systemd UNIT_PREFIX castle- -> wildpc-; own programs castle-api/gateway/etc. - Display prose "Castle" -> "Wild PC" in docs, agent-guide files, README, frontend. - Package dirs and bootstrap yaml renamed via git mv; lockfiles regenerated; redundant nested uv.lock files dropped (workspace root lock is authoritative). Tests: core 273, cli 47, wildpc-api 120 all pass. Frontend type-checks + builds. Fixed a stale test fixture (secret_env_path kind arg) broken pre-rename.
This commit is contained in:
101
cli/src/wildpc_cli/commands/run_cmd.py
Normal file
101
cli/src/wildpc_cli/commands/run_cmd.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""wildpc run - run a program or service in the foreground.
|
||||
|
||||
Unified: if the target is a program with a declared `run` command, run that in
|
||||
the foreground (dev-run). Otherwise fall back to the deployed-service run from
|
||||
the registry.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from wildpc_core.generators.systemd import secret_env_path
|
||||
from wildpc_core.registry import REGISTRY_PATH, load_registry
|
||||
|
||||
from wildpc_cli.config import load_config
|
||||
|
||||
|
||||
def _load_secret_env(name: str) -> dict[str, str]:
|
||||
"""Read a deployment's generated secret env file (KEY=value lines), if any.
|
||||
|
||||
Secrets are kept out of the registry, so a foreground run merges them from the
|
||||
file systemd/docker would otherwise load, matching the deployed environment.
|
||||
"""
|
||||
path = secret_env_path(name)
|
||||
if not path.exists():
|
||||
return {}
|
||||
out: dict[str, str] = {}
|
||||
for line in path.read_text().splitlines():
|
||||
if "=" in line and not line.startswith("#"):
|
||||
key, val = line.split("=", 1)
|
||||
out[key] = val
|
||||
return out
|
||||
|
||||
|
||||
def _run_program(name: str, extra: list[str]) -> int | None:
|
||||
"""Run a program's declared `run` command in the foreground.
|
||||
|
||||
Returns the exit code, or None if the program has no declared run command
|
||||
(so the caller can fall back to the deployed-service path).
|
||||
"""
|
||||
config = load_config()
|
||||
prog = config.programs.get(name)
|
||||
if prog is None or prog.commands is None or prog.commands.run is None:
|
||||
return None
|
||||
if not prog.source:
|
||||
print(f"Error: program '{name}' has no source directory.")
|
||||
return 1
|
||||
cwd = Path(prog.source)
|
||||
cmds = prog.commands.run
|
||||
rc = 0
|
||||
for i, argv in enumerate(cmds):
|
||||
# Append extra args to the final command in the sequence.
|
||||
full = list(argv) + (extra if i == len(cmds) - 1 else [])
|
||||
print(f"Running {name}: {' '.join(full)}")
|
||||
rc = subprocess.run(full, cwd=cwd).returncode
|
||||
if rc != 0:
|
||||
break
|
||||
return rc
|
||||
|
||||
|
||||
def run_run(args: argparse.Namespace) -> int:
|
||||
"""Run a program's declared run, or a deployed service, in the foreground.
|
||||
|
||||
Scoped by `resource`: `program run` runs the program's declared `run`;
|
||||
`service run` runs the deployed service's command from the registry.
|
||||
"""
|
||||
name = args.name
|
||||
extra_args = getattr(args, "extra", []) or []
|
||||
resource = getattr(args, "resource", None)
|
||||
|
||||
# Program: declared run command.
|
||||
if resource in (None, "program"):
|
||||
prog_rc = _run_program(name, extra_args)
|
||||
if prog_rc is not None:
|
||||
return prog_rc
|
||||
if resource == "program":
|
||||
print(f"Error: program '{name}' has no declared `run` command.")
|
||||
return 1
|
||||
|
||||
# Service: deployed command from the registry.
|
||||
if not REGISTRY_PATH.exists():
|
||||
print("Error: no registry found. Run 'wildpc apply' first.")
|
||||
return 1
|
||||
|
||||
registry = load_registry()
|
||||
matches = registry.named(name)
|
||||
if not matches:
|
||||
print(f"Error: '{name}' is not a deployed service. Run 'wildpc apply' first.")
|
||||
return 1
|
||||
|
||||
# A name may span kinds; run the one that has a run command (service/job).
|
||||
deployed = next((d for d in matches if d.run_cmd), matches[0])
|
||||
cmd = list(deployed.run_cmd) + extra_args
|
||||
env = dict(os.environ)
|
||||
env.update(deployed.env)
|
||||
env.update(_load_secret_env(name))
|
||||
print(f"Running {name}: {' '.join(cmd)}")
|
||||
return subprocess.run(cmd, env=env).returncode
|
||||
Reference in New Issue
Block a user