fix: 修复前端 assets 清理脚本以支持 Rolldown 产物
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -26,7 +26,8 @@ echo "✓ Upload complete"
|
||||
# 4. Extract on server + prune orphaned legacy chunks
|
||||
echo "▶ Deploying on server..."
|
||||
ssh nexus "cd /www/wwwroot/api.synaglobal.vip/web/app && tar xzf /tmp/nexus-frontend.tar.gz && rm /tmp/nexus-frontend.tar.gz"
|
||||
echo "▶ Pruning orphaned assets..."
|
||||
echo "▶ Pruning orphaned assets (dry-run first)..."
|
||||
ssh nexus "python3 /www/wwwroot/api.synaglobal.vip/deploy/prune_frontend_assets.py --dry-run"
|
||||
ssh nexus "python3 /www/wwwroot/api.synaglobal.vip/deploy/prune_frontend_assets.py"
|
||||
echo "✓ Extracted and pruned"
|
||||
|
||||
|
||||
@@ -1,15 +1,32 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Remove orphaned files under web/app/assets/ not referenced by current Vite build."""
|
||||
"""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
|
||||
|
||||
IMPORT_RE = re.compile(
|
||||
r"""(?:from\s*["']|import\s*\(\s*["']|import\s*["'])\./([^"']+\.(?:js|css))"""
|
||||
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\(["']?([^"')]+\.(?: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]:
|
||||
@@ -17,42 +34,48 @@ def collect(app_dir: Path) -> set[Path]:
|
||||
index = app_dir / "index.html"
|
||||
if not index.is_file():
|
||||
raise SystemExit(f"missing {index}")
|
||||
keep: set[Path] = set()
|
||||
queue: list[Path] = []
|
||||
|
||||
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")):
|
||||
p = assets / m.group(1)
|
||||
if p.is_file():
|
||||
queue.append(p)
|
||||
name = m.group(1).split("/")[-1]
|
||||
if name in all_names:
|
||||
keep_names.add(name)
|
||||
|
||||
while queue:
|
||||
path = queue.pop()
|
||||
if path in keep or not path.is_file():
|
||||
continue
|
||||
keep.add(path)
|
||||
text = path.read_text(encoding="utf-8", errors="ignore")
|
||||
for rel in IMPORT_RE.findall(text):
|
||||
nxt = assets / rel
|
||||
if nxt.is_file():
|
||||
queue.append(nxt)
|
||||
if path.suffix == ".css":
|
||||
for rel in URL_RE.findall(text):
|
||||
rel = rel.lstrip("./")
|
||||
nxt = assets / rel
|
||||
if nxt.is_file():
|
||||
keep.add(nxt)
|
||||
return keep
|
||||
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:
|
||||
app_dir = Path(sys.argv[1] if len(sys.argv) > 1 else "/www/wwwroot/api.synaglobal.vip/web/app")
|
||||
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.rglob("*") if p.is_file()}
|
||||
orphan = sorted(all_files - keep)
|
||||
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)}")
|
||||
|
||||
Reference in New Issue
Block a user