feat: publish isolated site artifacts
Test / test (pull_request) Successful in 5s

This commit is contained in:
Evelyn Chen
2026-08-29 21:28:28 +00:00
parent f1f780f5a3
commit 7aca96e320
10 changed files with 1032 additions and 109 deletions
+57 -4
View File
@@ -5,7 +5,7 @@ import subprocess
import tempfile
from pathlib import Path
from utils import EXCLUDE_FILES, env, parse_site_yaml, run
from utils import EXCLUDE_FILES, env, parse_site_yaml, run_args
def build_static(site_dir, cfg):
@@ -34,11 +34,11 @@ def build_static(site_dir, cfg):
elif cfg["type"] == "hugo":
print(f"Building Hugo site from {src}")
run(f"hugo --source {src} --destination {html_dir}")
run_args(["hugo", "--source", str(src), "--destination", str(html_dir)])
elif cfg["type"] == "mkdocs":
print(f"Building MkDocs site from {src}")
run(f"cd {src} && mkdocs build -d {html_dir}")
run_args(["mkdocs", "build", "-d", str(html_dir)], cwd=src)
if cfg.get("tidy", True):
print("Running tidy on HTML files...")
@@ -54,6 +54,56 @@ def build_static(site_dir, cfg):
print(f"Build complete — content at {html_dir}")
def stage_artifacts(site_dir, cfg):
"""Snapshot caller-built outputs for the deploy phase."""
staging_dir = site_dir / ".site-publish"
if staging_dir.is_symlink():
raise SystemExit("ERROR: .site-publish must not be a symbolic link")
if staging_dir.exists():
shutil.rmtree(staging_dir)
staging_dir.mkdir()
root = site_dir.resolve()
for name, artifact in cfg["artifacts"].items():
source = (site_dir / artifact["source"]).resolve()
try:
source.relative_to(root)
except ValueError:
raise SystemExit(f"ERROR: artifact {name}.source escapes the repository")
if not source.is_dir():
raise SystemExit(f"ERROR: artifact {name}.source is not a directory: {artifact['source']}")
files = [entry for entry in source.rglob("*") if entry.is_file()]
if not files:
raise SystemExit(f"ERROR: artifact {name}.source has no files: {artifact['source']}")
for entry in source.rglob("*"):
if entry.is_symlink():
try:
entry.resolve(strict=True).relative_to(root)
except (FileNotFoundError, ValueError):
raise SystemExit(
f"ERROR: artifact {name}.source contains an unsafe symlink: {entry.relative_to(source)}"
)
destination = staging_dir / name
destination.mkdir()
routes = [route for route in cfg["routes"] if route["artifact"] == name]
targets = set()
print(f"Staging artifact {name} from {artifact['source']}")
for route in routes:
route_root = destination / route["path"].lstrip("/")
for entry in files:
relative = entry.relative_to(source)
target = route_root / relative
if target in targets:
raise SystemExit(
f"ERROR: artifact {name} routes map more than once to {target.relative_to(destination)}"
)
targets.add(target)
target.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(entry, target, follow_symlinks=True)
print(f"Build complete — staged artifacts at {staging_dir}")
def cmd_build():
site_dir = Path(env("SITE_DIR"))
cfg = parse_site_yaml(site_dir)
@@ -62,4 +112,7 @@ def cmd_build():
print("Site disabled — skipping build")
return
build_static(site_dir, cfg)
if cfg["mode"] == "multi":
stage_artifacts(site_dir, cfg)
else:
build_static(site_dir, cfg)