From b5b1a481530bc631d8f81526d65067d6fe102f85 Mon Sep 17 00:00:00 2001 From: Justin Oros Date: Mon, 28 Sep 2026 14:48:14 -0700 Subject: [PATCH] Add copy PowerShell install button and version stamp site assets --- build.py | 19 ++++++++++++++++++- site/app.js | 35 +++++++++++++++++++++++++++++++++++ site/style.css | 16 ++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/build.py b/build.py index 280f1f6..17943e8 100644 --- a/build.py +++ b/build.py @@ -69,6 +69,16 @@ def total_downloads(full_name): return total +def install_command(repo): + branch = repo.get("default_branch") or "main" + url = f"https://raw.githubusercontent.com/{repo['full_name']}/{branch}/Install.ps1" + try: + request(url, accept="text/plain") + except (urllib.error.URLError, OSError): + return None + return f"irm {url} | iex" + + def zip_asset(release): for asset in release.get("assets", []): if asset["name"].lower().endswith(".zip"): @@ -156,7 +166,8 @@ def build_mod(repo): shutil.rmtree(OUT.joinpath(*base.parts), ignore_errors=True) return None downloads = total_downloads(repo["full_name"]) - print(f"mod {mod_id} {version} ({len(files)} files, {downloads} downloads)") + command = install_command(repo) + print(f"mod {mod_id} {version} ({len(files)} files, {downloads} downloads, installer {'yes' if command else 'no'})") return { "id": mod_id, "name": meta.get("name") or mod_id, @@ -171,6 +182,7 @@ def build_mod(repo): "downloadUrl": asset["browser_download_url"], "published": release.get("published_at"), "downloads": downloads, + "installCommand": command, "base": f"{base}/", "dll": dll, "manifestPath": manifest_path, @@ -254,6 +266,11 @@ def main(): if OUT.exists(): shutil.rmtree(OUT) shutil.copytree(SITE_SRC, OUT) + stamp = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S") + index = OUT / "index.html" + html = index.read_text(encoding="utf-8") + html = html.replace('src="app.js"', f'src="app.js?v={stamp}"').replace('href="style.css"', f'href="style.css?v={stamp}"') + index.write_text(html, encoding="utf-8") mods = [] for repo in sorted(list_repos(), key=lambda r: r["name"].lower()): topics = repo.get("topics") or [] diff --git a/site/app.js b/site/app.js index 0cf88f5..bf5b61c 100644 --- a/site/app.js +++ b/site/app.js @@ -78,6 +78,40 @@ function renderRedloader() { ); } +async function copyText(text) { + try { + await navigator.clipboard.writeText(text); + return true; + } catch { + const area = el("textarea", { readonly: true, style: "position:fixed;opacity:0" }); + area.value = text; + document.body.append(area); + area.select(); + let ok = false; + try { + ok = document.execCommand("copy"); + } catch {} + area.remove(); + return ok; + } +} + +const COPY_ICON = ''; + +function copyButton(command) { + const label = el("span", {}, "Copy PowerShell install"); + const button = el("button", { type: "button", class: "copy", title: command }, label); + button.insertAdjacentHTML("afterbegin", COPY_ICON); + button.addEventListener("click", async () => { + const ok = await copyText(command); + label.textContent = ok ? "Copied" : "Copy failed"; + setTimeout(() => { + label.textContent = "Copy PowerShell install"; + }, 2000); + }); + return button; +} + function modRow(mod) { return el("li", { class: "row" }, el("div", {}, @@ -87,6 +121,7 @@ function modRow(mod) { el("span", {}, `Latest ${mod.version}`), typeof mod.downloads === "number" ? el("span", {}, `${mod.downloads.toLocaleString()} downloads`) : null, link(mod.releaseUrl, "Release notes"), + mod.installCommand ? copyButton(mod.installCommand) : null, ), ), ); diff --git a/site/style.css b/site/style.css index 007319c..b388b69 100644 --- a/site/style.css +++ b/site/style.css @@ -167,6 +167,22 @@ a.button.secondary:hover { line-height: 1.1; } +button.copy { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0; + border: 0; + background: none; + color: var(--flare); + font: inherit; + cursor: pointer; +} + +button.copy:hover { + color: var(--fog); +} + .block-note { margin: 1rem 0 0; color: var(--lichen);