diff --git a/src/api.js b/src/api.js index aaaaed6..7a61925 100644 --- a/src/api.js +++ b/src/api.js @@ -48,27 +48,25 @@ const byNewest = (a, b) => b.sort - a.sort; const sidecarsFor = (key) => [key + ".sha256", key + ".sig"]; -// --- base/packaging helpers +// base/packaging helpers const stripSidecar = (name) => { if (name.endsWith(".sha256")) return name.slice(0, -".sha256".length); if (name.endsWith(".sig")) return name.slice(0, -".sig".length); return name; }; - const stripPackaging = (name) => { if (name.endsWith(TAR_EXT)) return name.slice(0, -TAR_EXT.length); if (name.endsWith("_setup.exe")) return name.slice(0, -"_setup.exe".length); if (name.endsWith(".dmg")) return name.slice(0, -".dmg".length); return name; }; - const baseStem = (name) => stripPackaging(stripSidecar(name)); const isTar = (name) => name.endsWith(TAR_EXT); const isDmg = (name) => name.endsWith(".dmg"); const isSetup = (name) => name.endsWith("_setup.exe"); -// normalize platform/arch and parse FROM baseStem so arch is clean +// normalize + parse FROM base stem so arch is clean const norm_platform = (p) => { const v = (p || "").toLowerCase(); if (v === "macos") return "darwin"; @@ -90,21 +88,15 @@ const parseName = (fname) => { const build = parts[2] ?? ""; const platform = norm_platform(parts[3] ?? ""); const arch = norm_arch(parts[4] ?? ""); - const groupId = `${product}_${platform}_${arch}`; // retention - const releaseId = `${product}_${build}`; // build grouping - const tripleId = `${product}_${build}_${platform}_${arch}`; // emit bucket - return { - product, - version, - build, - platform, - arch, - groupId, - releaseId, - tripleId, - }; + const groupId = `${product}_${platform}_${arch}`; // retention grouping + const releaseId = `${product}_${build}`; // build/release grouping + return { product, version, build, platform, arch, groupId, releaseId }; }; +const platformRank = (p) => + p === "darwin" ? 0 : p === "windows" ? 1 : p === "linux" ? 2 : 3; +const archRank = (a) => (a === "aarch64" ? 0 : a === "x86-64" ? 1 : 2); + /* ---------- public API ---------- */ const cleanOldItems = async () => { @@ -153,8 +145,8 @@ const getBucketFiles = async (folderName) => { // quick lookups const byKey = Object.fromEntries(listed.map((i) => [i.key, i])); - // First, group files by *base stem* to detect what each stem represents - const byBase = new Map(); // base -> { rep, tar, dmg, setup, platform, arch, releaseId } + // group by base stem (each base is one platform+arch variant of a build) + const byBase = new Map(); // base -> { rep, tar, setup, dmg, platform, arch, releaseId } for (const it of listed) { const base = baseStem(it.name); let g = byBase.get(base); @@ -163,23 +155,21 @@ const getBucketFiles = async (folderName) => { g = { rep: it, tar: null, - dmg: null, setup: null, + dmg: null, platform: meta.platform, arch: meta.arch, releaseId: meta.releaseId, - tripleId: meta.tripleId, }; byBase.set(base, g); } if (isTar(it.name)) g.tar = it; - else if (isDmg(it.name)) g.dmg = it; else if (isSetup(it.name)) g.setup = it; - // rep remains whatever we first saw; sort values come from actual artifacts anyway + else if (isDmg(it.name)) g.dmg = it; } const baseGroups = Array.from(byBase.values()).filter( - (g) => g.tar || g.dmg || g.setup, + (g) => g.tar || g.setup || g.dmg, ); // ---------- Retention: newest 3 per (product+platform+arch) in nightly ---------- @@ -187,130 +177,104 @@ const getBucketFiles = async (folderName) => { if (folder === "nightly") { const buckets = new Map(); // groupId -> [bases...] for (const g of baseGroups) { - // use the best available artifact for sort - const rep = g.tar ?? g.dmg ?? g.setup ?? g.rep; + const rep = g.tar ?? g.setup ?? g.dmg ?? g.rep; const { groupId } = parseName(rep.name); if (!buckets.has(groupId)) buckets.set(groupId, []); buckets.get(groupId).push(g); } for (const [, arr] of buckets) { arr.sort((a, b) => { - const sa = (a.tar ?? a.dmg ?? a.setup ?? a.rep)?.sort ?? 0; - const sb = (b.tar ?? b.dmg ?? b.setup ?? b.rep)?.sort ?? 0; + const sa = (a.tar ?? a.setup ?? a.dmg ?? a.rep)?.sort ?? 0; + const sb = (b.tar ?? b.setup ?? b.dmg ?? b.rep)?.sort ?? 0; return sb - sa; }); arr.forEach((g, idx) => { - const rep = g.tar ?? g.dmg ?? g.setup ?? g.rep; + const rep = g.tar ?? g.setup ?? g.dmg ?? g.rep; const base = baseStem(rep.name); if (idx < 3) { keepBaseSet.add(base); } else { - // mark artifacts in this base for cleanup if (g.tar) [g.tar.key, ...sidecarsFor(g.tar.key)].forEach((k) => oldKeys.add(k), ); - if (g.dmg) - [g.dmg.key, ...sidecarsFor(g.dmg.key)].forEach((k) => - oldKeys.add(k), - ); if (g.setup) [g.setup.key, ...sidecarsFor(g.setup.key)].forEach((k) => oldKeys.add(k), ); + if (g.dmg) + [g.dmg.key, ...sidecarsFor(g.dmg.key)].forEach((k) => + oldKeys.add(k), + ); } }); } } else { for (const g of baseGroups) { - const rep = g.tar ?? g.dmg ?? g.setup ?? g.rep; + const rep = g.tar ?? g.setup ?? g.dmg ?? g.rep; keepBaseSet.add(baseStem(rep.name)); } } - // ---------- Aggregate by (product, build, platform, arch) => "triple" ---------- - const triples = new Map(); // tripleId -> { repSort, releaseId, platform, arch, tar?, setup?, dmg? } + // ---------- Build releases: per release (build) we emit three blocks ---------- + const releases = new Map(); // releaseId -> { repSort, items:[baseGroup] } for (const g of baseGroups) { - const rep = g.tar ?? g.dmg ?? g.setup ?? g.rep; - const { tripleId, releaseId, platform, arch } = parseName(rep.name); + const rep = g.tar ?? g.setup ?? g.dmg ?? g.rep; + const base = baseStem(rep.name); + if (!keepBaseSet.has(base)) continue; - // skip bases not kept by retention - if (!keepBaseSet.has(baseStem(rep.name))) continue; - - let t = triples.get(tripleId); - if (!t) { - t = { - repSort: rep.sort, - releaseId, - platform, - arch, - tar: null, - setup: null, - dmg: null, - }; - triples.set(tripleId, t); - } - t.repSort = Math.max(t.repSort, rep.sort); - if (g.tar) t.tar = g.tar; - if (g.setup) t.setup = g.setup; - if (g.dmg) t.dmg = g.dmg; - } - - // ---------- Group triples by release (build), newest-first ---------- - const releases = new Map(); // releaseId -> { repSort, items:[triple] } - for (const t of triples.values()) { - let r = releases.get(t.releaseId); + let r = releases.get(g.releaseId); if (!r) { - r = { repSort: t.repSort, items: [] }; - releases.set(t.releaseId, r); + r = { repSort: rep.sort, items: [] }; + releases.set(g.releaseId, r); } - r.repSort = Math.max(r.repSort, t.repSort); - r.items.push(t); + r.repSort = Math.max(r.repSort, rep.sort); + r.items.push({ ...g, repSort: rep.sort }); } + + // newest releases first const releaseList = Array.from(releases.entries()).sort( (a, b) => b[1].repSort - a[1].repSort, ); - // Sort triples inside a release if you want consistent platform/arch ordering (optional) - const platformRank = (p) => - p === "darwin" ? 0 : p === "windows" ? 1 : p === "linux" ? 2 : 3; - const archRank = (a) => (a === "aarch64" ? 0 : a === "x86-64" ? 1 : 2); - - // ---------- Emit in three GROUPS per triple: (tar-group) then (setup-group) then (dmg-group) ---------- const out = []; for (const [, bucket] of releaseList) { - bucket.items.sort((a, b) => { + // Sort items consistently for all three blocks + const sorted = bucket.items.slice().sort((a, b) => { const pr = platformRank(a.platform) - platformRank(b.platform); if (pr !== 0) return pr; const ar = archRank(a.arch) - archRank(b.arch); if (ar !== 0) return ar; - return b.repSort - a.repSort; // tie-breaker newest-first + return b.repSort - a.repSort; // tie-breaker newest first }); - for (const t of bucket.items) { - // 1) tar.gz group - if (t.tar) { - out.push(t.tar); - for (const sk of sidecarsFor(t.tar.key)) { - const it = byKey[sk]; - if (it) out.push(it); - } + // Block 1: all tar.gz (+ sidecars) + for (const g of sorted) { + if (!g.tar) continue; + out.push(g.tar); + for (const sk of sidecarsFor(g.tar.key)) { + const it = byKey[sk]; + if (it) out.push(it); } - // 2) _setup.exe group - if (t.setup) { - out.push(t.setup); - for (const sk of sidecarsFor(t.setup.key)) { - const it = byKey[sk]; - if (it) out.push(it); - } + } + + // Block 2: all _setup.exe (+ sidecars) + for (const g of sorted) { + if (!g.setup) continue; + out.push(g.setup); + for (const sk of sidecarsFor(g.setup.key)) { + const it = byKey[sk]; + if (it) out.push(it); } - // 3) .dmg group - if (t.dmg) { - out.push(t.dmg); - for (const sk of sidecarsFor(t.dmg.key)) { - const it = byKey[sk]; - if (it) out.push(it); - } + } + + // Block 3: all .dmg (+ sidecars) + for (const g of sorted) { + if (!g.dmg) continue; + out.push(g.dmg); + for (const sk of sidecarsFor(g.dmg.key)) { + const it = byKey[sk]; + if (it) out.push(it); } } }