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:
2026-07-18 22:55:08 -07:00
parent 25d7a522cc
commit 05b28cb584
190 changed files with 2428 additions and 3337 deletions

View File

@@ -5,14 +5,14 @@ from __future__ import annotations
from pathlib import Path
from unittest.mock import patch
from castle_core.deploy import _build_run_cmd, _build_stop_cmd
from castle_core.manifest import RunCompose, RunContainer, RunNode, RunPython
from wildpc_core.deploy import _build_run_cmd, _build_stop_cmd
from wildpc_core.manifest import RunCompose, RunContainer, RunNode, RunPython
def test_python_runner_uses_uv_run_from_source(tmp_path: Path) -> None:
"""A python service with a real source dir launches via `uv run --project`."""
run = RunPython(launcher="python", program="my-svc")
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/uv"):
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/uv"):
cmd = _build_run_cmd("my-svc", run, {}, [], source_dir=tmp_path)
assert cmd == [
"/usr/bin/uv",
@@ -26,7 +26,7 @@ def test_python_runner_uses_uv_run_from_source(tmp_path: Path) -> None:
def test_python_runner_appends_args(tmp_path: Path) -> None:
run = RunPython(launcher="python", program="my-svc", args=["--flag", "x"])
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/uv"):
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/uv"):
cmd = _build_run_cmd("my-svc", run, {}, [], source_dir=tmp_path)
assert cmd[-2:] == ["--flag", "x"]
assert cmd[:5] == ["/usr/bin/uv", "run", "--project", str(tmp_path), "--no-dev"]
@@ -36,7 +36,7 @@ def test_python_runner_falls_back_to_path_without_source() -> None:
"""No resolvable source → PATH lookup of the script (no uv run)."""
run = RunPython(launcher="python", program="my-svc")
with patch(
"castle_core.deploy.shutil.which", return_value="/home/u/.local/bin/my-svc"
"wildpc_core.deploy.shutil.which", return_value="/home/u/.local/bin/my-svc"
):
cmd = _build_run_cmd("my-svc", run, {}, [], source_dir=None)
assert cmd == ["/home/u/.local/bin/my-svc"]
@@ -46,7 +46,7 @@ def test_python_runner_warns_when_unresolvable() -> None:
"""No source and not on PATH → a warning, bare program name as last resort."""
run = RunPython(launcher="python", program="my-svc")
messages: list[str] = []
with patch("castle_core.deploy.shutil.which", return_value=None):
with patch("wildpc_core.deploy.shutil.which", return_value=None):
cmd = _build_run_cmd("my-svc", run, {}, messages, source_dir=None)
assert cmd == ["my-svc"]
assert any("my-svc" in m for m in messages)
@@ -55,7 +55,7 @@ def test_python_runner_warns_when_unresolvable() -> None:
def test_node_runner_bakes_source_dir(tmp_path: Path) -> None:
"""A node service runs the script in its source dir via `--dir` (no unit cwd)."""
run = RunNode(launcher="node", script="gateway:watch:raw", package_manager="pnpm")
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/pnpm"):
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/pnpm"):
cmd = _build_run_cmd("oc", run, {}, [], source_dir=tmp_path)
assert cmd == ["/usr/bin/pnpm", "--dir", str(tmp_path), "run", "gateway:watch:raw"]
@@ -64,7 +64,7 @@ def test_node_runner_appends_args(tmp_path: Path) -> None:
run = RunNode(
launcher="node", script="start", package_manager="pnpm", args=["--port", "18789"]
)
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/pnpm"):
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/pnpm"):
cmd = _build_run_cmd("oc", run, {}, [], source_dir=tmp_path)
assert cmd == [
"/usr/bin/pnpm",
@@ -80,7 +80,7 @@ def test_node_runner_appends_args(tmp_path: Path) -> None:
def test_node_runner_without_source_omits_dir() -> None:
"""No resolvable source → bare `pnpm run` (package manager still PATH-resolved)."""
run = RunNode(launcher="node", script="start", package_manager="pnpm")
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/pnpm"):
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/pnpm"):
cmd = _build_run_cmd("oc", run, {}, [], source_dir=None)
assert cmd == ["/usr/bin/pnpm", "run", "start"]
@@ -88,8 +88,8 @@ def test_node_runner_without_source_omits_dir() -> None:
def test_container_secrets_use_env_file_not_argv() -> None:
"""Secrets go through --env-file; plain vars stay as -e; no secret in argv."""
run = RunContainer(launcher="container", image="img:latest", env={"PLAIN": "1"})
env_file = Path("/home/u/.castle/secrets/env/castle-svc.service.env")
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/docker"):
env_file = Path("/home/u/.wildpc/secrets/env/wildpc-svc.service.env")
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/docker"):
cmd = _build_run_cmd("svc", run, {"PORT": "9001"}, [], secret_env_file=env_file)
joined = " ".join(cmd)
assert "--env-file" in cmd
@@ -102,7 +102,7 @@ def test_container_secrets_use_env_file_not_argv() -> None:
def test_container_without_secrets_has_no_env_file() -> None:
run = RunContainer(launcher="container", image="img:latest")
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/docker"):
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/docker"):
cmd = _build_run_cmd("svc", run, {}, [], secret_env_file=None)
assert "--env-file" not in cmd
@@ -120,42 +120,42 @@ def test_container_placeholders_expand_in_run_fields() -> None:
ph = {
"name": "svc",
"port": "5432",
"data_dir": "/data/castle/svc",
"tls_dir": "/data/castle/svc/tls",
"data_dir": "/data/wildpc/svc",
"tls_dir": "/data/wildpc/svc/tls",
}
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/docker"):
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/docker"):
cmd = _build_run_cmd("svc", run, {}, [], placeholders=ph)
joined = " ".join(cmd)
assert "DATA=/data/castle/svc/x" in joined # env expanded
assert "/data/castle/svc/tls:/tls:ro" in joined # volume expanded
assert "DATA=/data/wildpc/svc/x" in joined # env expanded
assert "/data/wildpc/svc/tls:/tls:ro" in joined # volume expanded
assert "svc:5432" in cmd # arg expanded
def test_container_double_dollar_escapes_placeholder() -> None:
"""$${key} passes a literal ${key} through to the container's own shell/env
instead of castle expanding it (docker-compose-style escape)."""
instead of wildpc expanding it (docker-compose-style escape)."""
run = RunContainer(
launcher="container",
image="img:latest",
args=["sh", "-c", "exec myd --advertise $${name}:${port}"],
)
ph = {"name": "svc", "port": "5432"}
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/docker"):
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/docker"):
cmd = _build_run_cmd("svc", run, {}, [], placeholders=ph)
# castle expands ${port} but leaves $${name} as a literal ${name} for the shell
# wildpc expands ${port} but leaves $${name} as a literal ${name} for the shell
assert "exec myd --advertise ${name}:5432" in cmd
def test_compose_runner_up_with_resolved_file(tmp_path: Path) -> None:
"""A compose service runs `docker compose -p <project> -f <abs-file> up`."""
run = RunCompose(launcher="compose", file="docker-compose.yml")
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/docker"):
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/docker"):
cmd = _build_run_cmd("supabase", run, {}, [], source_dir=tmp_path)
assert cmd == [
"/usr/bin/docker",
"compose",
"-p",
"castle-supabase",
"wildpc-supabase",
"-f",
str(tmp_path / "docker-compose.yml"),
"up",
@@ -165,8 +165,8 @@ def test_compose_runner_up_with_resolved_file(tmp_path: Path) -> None:
def test_compose_runner_secrets_never_hit_argv(tmp_path: Path) -> None:
"""Compose reads env from the process (systemd), not --env-file/-e — argv is clean."""
run = RunCompose(launcher="compose", file="docker-compose.yml")
env_file = Path("/home/u/.castle/secrets/env/castle-supabase.service.env")
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/docker"):
env_file = Path("/home/u/.wildpc/secrets/env/wildpc-supabase.service.env")
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/docker"):
cmd = _build_run_cmd(
"supabase", run, {"PORT": "8000"}, [], source_dir=tmp_path,
secret_env_file=env_file,
@@ -179,7 +179,7 @@ def test_compose_runner_secrets_never_hit_argv(tmp_path: Path) -> None:
def test_compose_project_name_override(tmp_path: Path) -> None:
run = RunCompose(launcher="compose", file="stack.yml", project_name="myproj")
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/docker"):
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/docker"):
cmd = _build_run_cmd("s", run, {}, [], source_dir=tmp_path)
assert cmd[:6] == [
"/usr/bin/docker", "compose", "-p", "myproj", "-f", str(tmp_path / "stack.yml"),
@@ -189,13 +189,13 @@ def test_compose_project_name_override(tmp_path: Path) -> None:
def test_compose_stop_cmd_is_down(tmp_path: Path) -> None:
"""The teardown command mirrors up but ends in `down` (same project + file)."""
run = RunCompose(launcher="compose", file="docker-compose.yml")
with patch("castle_core.deploy.shutil.which", return_value="/usr/bin/docker"):
with patch("wildpc_core.deploy.shutil.which", return_value="/usr/bin/docker"):
stop = _build_stop_cmd("supabase", run, tmp_path)
assert stop == [
"/usr/bin/docker",
"compose",
"-p",
"castle-supabase",
"wildpc-supabase",
"-f",
str(tmp_path / "docker-compose.yml"),
"down",