Phase 2b: serve static frontends in place from repo dist
The Caddyfile generator now emits static-frontend routes that root directly at <source>/<build.outputs[0]> (e.g. /data/repos/castle/app/dist) instead of a copy under ~/.castle/artifacts/content. Removes _copy_app_static and the central content-dir staging entirely; rebuilds are live without a copy step. - caddyfile.py: manifest-driven static routes (castle-app at /, others at /<name>) - ReactViteHandler.install just builds in place (no copy); uninstall is a no-op - is_active(static frontend) = repo dist exists - test isolation updated for the generator's config use Dashboard + power-graph-app verified serving from repo dist; 168 tests pass.
This commit is contained in:
@@ -2,19 +2,23 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
import castle_core.generators.caddyfile as caddyfile_mod
|
||||
from castle_core.generators.caddyfile import generate_caddyfile_from_registry
|
||||
from castle_core.registry import DeployedComponent, NodeConfig, NodeRegistry
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _isolate_content_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Use a temp dir for CONTENT_DIR so tests don't depend on real ~/.castle."""
|
||||
monkeypatch.setattr(caddyfile_mod, "CONTENT_DIR", tmp_path / "content")
|
||||
def _isolate_config(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Isolate the generator from the real ~/.castle config so static-frontend
|
||||
routes don't leak into these registry-focused tests."""
|
||||
import castle_core.config as config_mod
|
||||
|
||||
def _no_config(*args: object, **kwargs: object) -> object:
|
||||
raise FileNotFoundError("isolated in tests")
|
||||
|
||||
monkeypatch.setattr(config_mod, "load_config", _no_config)
|
||||
|
||||
|
||||
def _make_registry(
|
||||
|
||||
@@ -34,14 +34,21 @@ class TestIsActive:
|
||||
config = load_config(castle_root)
|
||||
assert lifecycle.is_active("does-not-exist", config) is False
|
||||
|
||||
def test_static_frontend_checks_content_dir(self, castle_root: Path, tmp_path: Path) -> None:
|
||||
def test_static_frontend_active_when_dist_built(self, castle_root: Path, tmp_path: Path) -> None:
|
||||
from castle_core.manifest import BuildSpec
|
||||
|
||||
config = load_config(castle_root)
|
||||
repo = tmp_path / "fe"
|
||||
config.programs["fe"] = config.programs["test-tool"].model_copy(
|
||||
update={"id": "fe", "behavior": "frontend", "source": "/tmp/fe"}
|
||||
update={
|
||||
"id": "fe",
|
||||
"behavior": "frontend",
|
||||
"source": str(repo),
|
||||
"build": BuildSpec(commands=[["pnpm", "build"]], outputs=["dist"]),
|
||||
}
|
||||
)
|
||||
content = tmp_path / "content"
|
||||
(content / "fe").mkdir(parents=True)
|
||||
with patch.object(lifecycle, "CONTENT_DIR", content):
|
||||
assert lifecycle.is_active("fe", config) is True
|
||||
with patch.object(lifecycle, "CONTENT_DIR", tmp_path / "empty"):
|
||||
assert lifecycle.is_active("fe", config) is False
|
||||
# No dist yet → inactive
|
||||
assert lifecycle.is_active("fe", config) is False
|
||||
# Built dist → served in place → active
|
||||
(repo / "dist").mkdir(parents=True)
|
||||
assert lifecycle.is_active("fe", config) is True
|
||||
|
||||
Reference in New Issue
Block a user