const REPO = "praktimarc/kst4contest"; const API = `https://api.github.com/repos/${REPO}`; const WORKFLOW_FILE = "nightly-artifacts.yml"; const WORKFLOW_BASENAME = "nightly-artifacts"; const BRANCH = "main"; const WORKFLOW_RUNS_URL = `https://github.com/${REPO}/actions/workflows/${WORKFLOW_FILE}`; /** * nightly.link (https://nightly.link) mirrors the most recent successful * GitHub Actions artifact for a given workflow/branch/artifact-name as a * plain public download, with no GitHub account required. It always follows * the latest successful run on its own, so these URLs stay correct without * this site having to resolve a run ID at build time. */ const NIGHTLY_LINK_BASE = `https://nightly.link/${REPO}/workflows/${WORKFLOW_BASENAME}/${BRANCH}`; /** * Static metadata for the artifacts produced by * .github/workflows/nightly-artifacts.yml, keyed by the upload-artifact name * used in that workflow. Anything not listed here (e.g. the raw * flatpak-ostree-repo folder) is skipped on the nightly page. */ const ARTIFACT_INFO = { "windows-zip": { os: "Windows", format: "ZIP x64", icon: "🪟", note: "Extract the archive, then start praktiKST.exe. No separate Java installation is required.", order: 1 }, "linux-appimage": { os: "Linux", format: "AppImage x86_64", icon: "🐧", note: "Portable build. Make the downloaded file executable before the first launch.", order: 2 }, flatpakref: { os: "Linux", format: "Flatpak (.flatpakref, nightly branch)", icon: "🐧", note: "Adds the nightly branch of the KST4Contest Flatpak repo. See the installation guide for the flatpak CLI alternative.", order: 3 }, "linux-debian": { os: "Debian / Ubuntu", format: "DEB amd64", icon: "📦", note: "Native package for Debian, Ubuntu and distributions based on them.", order: 4 }, "linux-fedora": { os: "Fedora", format: "RPM x86_64", icon: "📦", note: "Native package built for Fedora and compatible RPM-based systems.", order: 5 }, "linux-arch": { os: "Arch Linux", format: "pkg.tar.zst", icon: "📦", note: "Package for direct installation with pacman.", order: 6 }, "macos-dmg-macos-latest": { os: "macOS Apple Silicon", format: "DMG arm64", icon: "🍎", note: "Best-effort build for Apple Silicon Macs. Not notarized by Apple.", order: 7 }, "macos-dmg-macos-15-intel": { os: "macOS Intel", format: "DMG x86_64", icon: "🍎", note: "Best-effort build for Intel Macs. Not notarized by Apple.", order: 8 } }; function buildItems() { return Object.entries(ARTIFACT_INFO) .map(([name, info]) => ({ ...info, name, url: `${NIGHTLY_LINK_BASE}/${name}.zip` })) .sort((a, b) => a.order - b.order); } function authHeaders() { const headers = { Accept: "application/vnd.github+json" }; if (process.env.GITHUB_TOKEN) { headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`; } return headers; } function formatSize(bytes) { if (bytes < 1024 * 1024) { return `${(bytes / 1024).toFixed(0)} KB`; } return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } /** * Best-effort lookup of the run behind the current nightly.link downloads, * purely to show a commit/date on the page. The download links themselves * (buildItems) do not depend on this succeeding. */ async function fetchLatestRunMeta(headers) { const runsRes = await fetch( `${API}/actions/workflows/${WORKFLOW_FILE}/runs?branch=${BRANCH}&status=success&per_page=1`, { headers } ); if (!runsRes.ok) { throw new Error(`workflow runs request failed: ${runsRes.status}`); } const runsData = await runsRes.json(); const run = runsData.workflow_runs && runsData.workflow_runs[0]; if (!run) { throw new Error("no successful nightly run found"); } const meta = { runUrl: run.html_url, commitUrl: `https://github.com/${REPO}/commit/${run.head_sha}`, shortSha: run.head_sha.slice(0, 7), createdAt: run.created_at }; try { const artifactsRes = await fetch( `${API}/actions/runs/${run.id}/artifacts?per_page=100`, { headers } ); if (artifactsRes.ok) { const artifactsData = await artifactsRes.json(); const sizeByName = {}; for (const artifact of artifactsData.artifacts || []) { if (!artifact.expired) { sizeByName[artifact.name] = formatSize(artifact.size_in_bytes); } } meta.sizeByName = sizeByName; } } catch { // Sizes are a nice-to-have; the run metadata above is still useful without them. } return meta; } module.exports = async function () { const items = buildItems(); let meta = null; try { meta = await fetchLatestRunMeta(authHeaders()); } catch (err) { console.warn( `[nightly] Could not load latest nightly run metadata (commit/date will be omitted). ` + `Download links are unaffected since they are served by nightly.link: ${err.message}` ); } if (meta && meta.sizeByName) { for (const item of items) { if (meta.sizeByName[item.name]) { item.size = meta.sizeByName[item.name]; } } } return { runsUrl: WORKFLOW_RUNS_URL, meta, items }; };