d519113734
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>
88 lines
2.7 KiB
Python
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())
|