Files
Nexus/deploy/prune_frontend_assets.py
T
2026-06-01 12:31:55 +08:00

87 lines
2.6 KiB
Python

#!/usr/bin/env python3
"""Remove orphaned files under web/app/assets/ not referenced by current Vite build.
Vite/Rolldown minified bundles reference chunks by bare filename (e.g. ServersPage-xxx.js)
without import() paths — scan file contents for known asset basenames.
"""
from __future__ import annotations
import re
import sys
from pathlib import Path
ASSET_NAME_RE = re.compile(
r"[A-Za-z0-9][A-Za-z0-9_.-]*\.(?:js|css|woff2?|ttf|eot)"
)
URL_RE = re.compile(r"""url\(["']?([^"')]+)["']?\)""")
def _referenced_basenames(text: str, known: set[str]) -> set[str]:
found: set[str] = set()
for m in ASSET_NAME_RE.finditer(text):
name = m.group(0)
if name in known:
found.add(name)
for raw in URL_RE.findall(text):
name = Path(raw).name
if name in known:
found.add(name)
return found
def collect(app_dir: Path) -> set[Path]:
assets = app_dir / "assets"
index = app_dir / "index.html"
if not index.is_file():
raise SystemExit(f"missing {index}")
all_names = {p.name for p in assets.iterdir() if p.is_file()}
keep_names: set[str] = set()
for m in re.finditer(r"/app/assets/([^\s\"']+)", index.read_text(encoding="utf-8")):
name = m.group(1).split("/")[-1]
if name in all_names:
keep_names.add(name)
if not keep_names:
raise SystemExit("no entry assets found in index.html")
changed = True
while changed:
changed = False
for name in list(keep_names):
path = assets / name
if not path.is_file():
continue
text = path.read_text(encoding="utf-8", errors="ignore")
for ref in _referenced_basenames(text, all_names):
if ref not in keep_names:
keep_names.add(ref)
changed = True
return {assets / n for n in keep_names}
def main() -> int:
dry_run = "--dry-run" in sys.argv
args = [a for a in sys.argv[1:] if a != "--dry-run"]
app_dir = Path(args[0] if args else "/www/wwwroot/api.synaglobal.vip/web/app")
assets = app_dir / "assets"
if not assets.is_dir():
raise SystemExit(f"not a directory: {assets}")
keep = collect(app_dir)
all_files = {p for p in assets.iterdir() if p.is_file()}
orphan = sorted(all_files - keep, key=lambda p: p.name)
if dry_run:
print(f"dry-run kept={len(keep)} would_remove={len(orphan)} total={len(all_files)}")
return 0
for p in orphan:
p.unlink()
print(f"kept={len(keep)} removed={len(orphan)} total_before={len(all_files)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())