release: nexus btpanel session fix and app-v2
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Download pinned frontend vendor assets into web/app/vendor/ (F2e-06).
|
||||
|
||||
Run after upgrading versions in VENDOR_MANIFEST, then commit vendor/ + HTML path updates.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import tarfile
|
||||
import tempfile
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
VENDOR_DIR = ROOT / "web" / "app" / "vendor"
|
||||
|
||||
VENDOR_MANIFEST: dict[str, dict[str, str]] = {
|
||||
"tailwindcss-browser.js": {
|
||||
"url": "https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.0.6/dist/index.global.js",
|
||||
"version": "@tailwindcss/browser@4.0.6",
|
||||
},
|
||||
"alpinejs.min.js": {
|
||||
"url": "https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js",
|
||||
"version": "alpinejs@3.14.8",
|
||||
},
|
||||
"xterm.css": {
|
||||
"url": "https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css",
|
||||
"version": "xterm@5.3.0",
|
||||
},
|
||||
"xterm.js": {
|
||||
"url": "https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js",
|
||||
"version": "xterm@5.3.0",
|
||||
},
|
||||
"xterm-addon-fit.js": {
|
||||
"url": "https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js",
|
||||
"version": "xterm-addon-fit@0.8.0",
|
||||
},
|
||||
"xterm-addon-web-links.js": {
|
||||
"url": "https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.js",
|
||||
"version": "xterm-addon-web-links@0.9.0",
|
||||
},
|
||||
"qrious.min.js": {
|
||||
"url": "https://cdn.jsdelivr.net/npm/qrious@4.0.2/dist/qrious.min.js",
|
||||
"version": "qrious@4.0.2",
|
||||
},
|
||||
}
|
||||
|
||||
MONACO_VERSION = "0.45.0"
|
||||
MONACO_VS_PREFIX = "package/min/vs/"
|
||||
MONACO_NPM_TGZ = (
|
||||
f"https://registry.npmjs.org/monaco-editor/-/monaco-editor-{MONACO_VERSION}.tgz"
|
||||
)
|
||||
MONACO_VS_PUBLIC = "/app/vendor/monaco/vs"
|
||||
|
||||
CDN_REPLACEMENTS = {
|
||||
"https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4": "/app/vendor/tailwindcss-browser.js",
|
||||
"https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js": "/app/vendor/alpinejs.min.js",
|
||||
"https://cdn.jsdelivr.net/npm/xterm@5.3.0/css/xterm.css": "/app/vendor/xterm.css",
|
||||
"https://cdn.jsdelivr.net/npm/xterm@5.3.0/lib/xterm.js": "/app/vendor/xterm.js",
|
||||
"https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.8.0/lib/xterm-addon-fit.js": "/app/vendor/xterm-addon-fit.js",
|
||||
"https://cdn.jsdelivr.net/npm/xterm-addon-web-links@0.9.0/lib/xterm-addon-web-links.js": "/app/vendor/xterm-addon-web-links.js",
|
||||
"https://cdn.jsdelivr.net/npm/qrious@4.0.2/dist/qrious.min.js": "/app/vendor/qrious.min.js",
|
||||
f"https://cdn.jsdelivr.net/npm/monaco-editor@{MONACO_VERSION}/min/vs": MONACO_VS_PUBLIC,
|
||||
f"https://cdn.jsdelivr.net/npm/monaco-editor@{MONACO_VERSION}/min/vs/loader.js": (
|
||||
f"{MONACO_VS_PUBLIC}/loader.js"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _sha384(path: Path) -> str:
|
||||
digest = hashlib.sha384(path.read_bytes()).digest()
|
||||
return "sha384-" + __import__("base64").b64encode(digest).decode()
|
||||
|
||||
|
||||
def download_monaco() -> dict:
|
||||
"""Extract monaco-editor min/vs/ into web/app/vendor/monaco/vs/."""
|
||||
vs_dest = VENDOR_DIR / "monaco" / "vs"
|
||||
vs_dest.mkdir(parents=True, exist_ok=True)
|
||||
print(f"Fetching monaco-editor@{MONACO_VERSION} npm tarball -> {vs_dest}")
|
||||
with tempfile.NamedTemporaryFile(suffix=".tgz", delete=False) as tmp:
|
||||
tmp_path = Path(tmp.name)
|
||||
try:
|
||||
with urllib.request.urlopen(MONACO_NPM_TGZ, timeout=120) as resp:
|
||||
tmp_path.write_bytes(resp.read())
|
||||
file_count = 0
|
||||
total_bytes = 0
|
||||
with tarfile.open(tmp_path, "r:gz") as archive:
|
||||
for member in archive.getmembers():
|
||||
if not member.isfile() or not member.name.startswith(MONACO_VS_PREFIX):
|
||||
continue
|
||||
rel = member.name[len(MONACO_VS_PREFIX) :]
|
||||
if not rel or rel.startswith("/") or ".." in Path(rel).parts:
|
||||
continue
|
||||
target = vs_dest / rel
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
extracted = archive.extractfile(member)
|
||||
if extracted is None:
|
||||
continue
|
||||
data = extracted.read()
|
||||
target.write_bytes(data)
|
||||
file_count += 1
|
||||
total_bytes += len(data)
|
||||
loader = vs_dest / "loader.js"
|
||||
if not loader.is_file():
|
||||
raise RuntimeError(f"monaco vs/loader.js missing after extract ({vs_dest})")
|
||||
return {
|
||||
"version": f"monaco-editor@{MONACO_VERSION}",
|
||||
"path": str(vs_dest.relative_to(ROOT)),
|
||||
"files": file_count,
|
||||
"bytes": total_bytes,
|
||||
"integrity": _sha384(loader),
|
||||
}
|
||||
finally:
|
||||
tmp_path.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def download_vendor() -> dict:
|
||||
VENDOR_DIR.mkdir(parents=True, exist_ok=True)
|
||||
manifest: dict[str, dict] = {"files": {}, "replacements": CDN_REPLACEMENTS}
|
||||
for filename, meta in VENDOR_MANIFEST.items():
|
||||
dest = VENDOR_DIR / filename
|
||||
print(f"Fetching {meta['version']} -> {dest.name}")
|
||||
with urllib.request.urlopen(meta["url"], timeout=60) as resp:
|
||||
data = resp.read()
|
||||
dest.write_bytes(data)
|
||||
manifest["files"][filename] = {
|
||||
"version": meta["version"],
|
||||
"url": meta["url"],
|
||||
"bytes": len(data),
|
||||
"integrity": _sha384(dest),
|
||||
}
|
||||
manifest["monaco"] = download_monaco()
|
||||
(VENDOR_DIR / "manifest.json").write_text(
|
||||
json.dumps(manifest, indent=2, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
return manifest
|
||||
|
||||
|
||||
def patch_app_js_files() -> int:
|
||||
app_dir = ROOT / "web" / "app"
|
||||
changed = 0
|
||||
for js in sorted(app_dir.glob("*.js")):
|
||||
text = js.read_text(encoding="utf-8")
|
||||
original = text
|
||||
for old, new in CDN_REPLACEMENTS.items():
|
||||
text = text.replace(old, new)
|
||||
if text != original:
|
||||
js.write_text(text, encoding="utf-8")
|
||||
changed += 1
|
||||
print(f"Updated {js.name}")
|
||||
return changed
|
||||
|
||||
|
||||
def patch_html_files() -> int:
|
||||
app_dir = ROOT / "web" / "app"
|
||||
changed = 0
|
||||
for html in sorted(app_dir.glob("*.html")):
|
||||
text = html.read_text(encoding="utf-8")
|
||||
original = text
|
||||
for old, new in CDN_REPLACEMENTS.items():
|
||||
text = text.replace(old, new)
|
||||
if text != original:
|
||||
html.write_text(text, encoding="utf-8")
|
||||
changed += 1
|
||||
print(f"Updated {html.name}")
|
||||
return changed
|
||||
|
||||
|
||||
def main() -> None:
|
||||
download_vendor()
|
||||
html_n = patch_html_files()
|
||||
js_n = patch_app_js_files()
|
||||
print(f"Done. {html_n} HTML file(s), {js_n} JS file(s) patched.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user