feat: harden CASAN production install lifecycle
This commit is contained in:
@@ -25,6 +25,35 @@ class ScaffoldError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def runtime_exclusions(component: str) -> set[str]:
|
||||
path = SOURCE_ROOT / "packaging" / "runtime-layout.json"
|
||||
try:
|
||||
layout = json.loads(path.read_text(encoding="utf-8"))
|
||||
values = layout["components"][component]["exclude"]
|
||||
except (OSError, ValueError, KeyError, TypeError) as error:
|
||||
raise ScaffoldError(f"production runtime layout is unavailable: {error}") from error
|
||||
return {
|
||||
str(value).replace("\\", "/").strip("/")
|
||||
for value in values
|
||||
if isinstance(value, str) and value.strip("/")
|
||||
}
|
||||
|
||||
|
||||
def excluded(relative: Path, exclusions: set[str]) -> bool:
|
||||
value = relative.as_posix()
|
||||
return (
|
||||
any(part in {
|
||||
"__pycache__", "node_modules", "dist", "build", "coverage",
|
||||
} for part in relative.parts)
|
||||
or relative.name == ".DS_Store"
|
||||
or relative.suffix in {".pyc", ".pyo", ".log", ".tmp"}
|
||||
or any(
|
||||
value == item or value.startswith(item.rstrip("/") + "/")
|
||||
for item in exclusions
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def atomic_write(path: Path, content: bytes, mode: int = 0o644) -> str:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
if path.is_symlink():
|
||||
@@ -89,14 +118,17 @@ def copy_domain_pack(target: Path, slug: str, name: str) -> tuple[int, int]:
|
||||
def install_harness(target: Path) -> tuple[int, int]:
|
||||
created = unchanged = 0
|
||||
harness_source = SOURCE_ROOT / "packages" / "casan-harness"
|
||||
exclusions = runtime_exclusions("harness")
|
||||
for source in sorted(harness_source.rglob("*")):
|
||||
if not source.is_file() or "__pycache__" in source.parts or source.suffix == ".pyc":
|
||||
if not source.is_file():
|
||||
continue
|
||||
relative = source.relative_to(harness_source)
|
||||
if excluded(relative, exclusions):
|
||||
continue
|
||||
destination = target / "packages" / "casan-harness" / relative
|
||||
# The target registry is adoption state, not immutable harness code. Preserve it
|
||||
# after the first install so repeated scaffolds and upgrades remain idempotent.
|
||||
if relative.as_posix() == "level5/project-registry.json" and destination.exists():
|
||||
if relative.as_posix() == "config/project-registry.json" and destination.exists():
|
||||
unchanged += 1
|
||||
continue
|
||||
mode = stat.S_IMODE(source.stat().st_mode)
|
||||
@@ -114,7 +146,7 @@ def install_harness(target: Path) -> tuple[int, int]:
|
||||
|
||||
|
||||
def register_project(target: Path, slug: str, name: str) -> None:
|
||||
registry_path = target / "packages" / "casan-harness" / "level5" / "project-registry.json"
|
||||
registry_path = target / "packages" / "casan-harness" / "config" / "project-registry.json"
|
||||
data = json.loads(registry_path.read_text(encoding="utf-8"))
|
||||
harness_version = next((item.get("harness_version") for item in data.get("projects", []) if item.get("harness_version")), "1.0.0")
|
||||
# A shipped harness may carry source-hub examples. Never register dangling
|
||||
|
||||
Reference in New Issue
Block a user