Files
Nexus/deploy/prune_frontend_assets.py
T
Your Name d519113734 fix: Gate3 health plain text, refresh empty body, prune underscore chunks
test_api: assert /health returns plain text ok instead of JSON parse.

auth: RefreshRequest.refresh_token optional so SPA POST {} is not 422.

prune: allow leading underscore in asset names to avoid deleting _plugin-vue_export-helper.

Add run_test_api_on_server.sh and deployment follow-up changelog.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-01 13:41:28 +08:00

88 lines
2.7 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
# Leading `_` required for Vite chunks like `_plugin-vue_export-helper-*.js`
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())