diff --git a/src/api.js b/src/api.js index 07cf485..70e94c9 100644 --- a/src/api.js +++ b/src/api.js @@ -61,7 +61,7 @@ const getBucketFiles = async (folderName) => { const contents = Array.isArray(data?.Contents) ? data.Contents : []; - // Normalize + newest-first + // normalize objects const ret = contents .filter((obj) => obj.Key !== folderKey) .map((obj) => { @@ -80,7 +80,7 @@ const getBucketFiles = async (folderName) => { second: "2-digit", }) .replace(/,/g, ""), - sort: d.getTime() || 0, + sort: Number.isFinite(d.getTime()) ? d.getTime() : 0, name: (obj.Key || "").replace(folderKey, ""), key: obj.Key || "", }; @@ -90,35 +90,54 @@ const getBucketFiles = async (folderName) => { const byKey = Object.fromEntries(ret.map((r) => [r.key, r])); const TAR = ".tar.gz"; - // Keep only tar anchors (apply nightly retention) + // helpers + const sidecars = (k) => [k + ".sha256", k + ".sig"]; + const tarBase = (name) => + name.endsWith(TAR) ? name.slice(0, -TAR.length) : name; + const parsePlatArchFromTarName = (name) => { + // name is the *tar* file's name, strip ".tar.gz" first + const base = tarBase(name); + const parts = base.split("_"); // 0=product,1=version,2=build,3=platform,4=arch + const platform = (parts[3] || "").toLowerCase(); + // arch is clean now (no extension) + let arch = (parts[4] || "").toLowerCase(); + if (arch === "arm64") arch = "aarch64"; + if (arch === "x86_64") arch = "x86-64"; + return { + base, + platform, + arch, + buildId: `${parts[0]}|${parts[1]}|${parts[2]}`, + }; + }; + + // anchors const tars = ret.filter((it) => it.name.endsWith(TAR)); + + // nightly retention (per product_platform_arch) const keepTarKeys = new Set(); const itemCount = {}; - for (const t of tars) { + const { platform, arch } = parsePlatArchFromTarName(t.name); if (folderName === "nightly") { - const parts = t.name.split("_"); // 0=product,1=version,2=build,3=platform,4=arch - const groupId = `${parts[0]}_${parts[3]}_${parts[4]}`; + const groupId = `${t.name.split("_")[0]}_${platform}_${arch}`; itemCount[groupId] = itemCount[groupId] || 0; - if (++itemCount[groupId] <= 3) { keepTarKeys.add(t.key); } else { - // mark tar + sidecars old if (!oldItems.includes(t.key)) { - oldItems.push(t.key, t.key + ".sha256", t.key + ".sig"); + oldItems.push(t.key, ...sidecars(t.key)); } - // companions old - if (parts[3] === "windows") { + if (platform === "windows") { const setupKey = t.key.slice(0, -TAR.length) + "_setup.exe"; if (byKey[setupKey] && !oldItems.includes(setupKey)) { - oldItems.push(setupKey, setupKey + ".sha256", setupKey + ".sig"); + oldItems.push(setupKey, ...sidecars(setupKey)); } } - if (parts[3] === "darwin") { + if (platform === "darwin") { const dmgKey = t.key.slice(0, -TAR.length) + ".dmg"; if (byKey[dmgKey] && !oldItems.includes(dmgKey)) { - oldItems.push(dmgKey, dmgKey + ".sha256", dmgKey + ".sig"); + oldItems.push(dmgKey, ...sidecars(dmgKey)); } } } @@ -127,70 +146,62 @@ const getBucketFiles = async (folderName) => { } } - // Build -> platform/arch -> tar + // group by build (0,1,2) -> platform/arch -> tar const builds = new Map(); // buildId -> { maxSort, map: Map("platform|arch" -> tarItem) } for (const t of tars) { if (!keepTarKeys.has(t.key)) continue; - const parts = t.name.split("_"); - const buildId = `${parts[0]}|${parts[1]}|${parts[2]}`; // parts 0..2 - const platform = (parts[3] || "").toLowerCase(); - const arch = (parts[4] || "").toLowerCase(); - const platArch = `${platform}|${arch}`; - + const { platform, arch, buildId } = parsePlatArchFromTarName(t.name); + const keyPA = `${platform}|${arch}`; if (!builds.has(buildId)) builds.set(buildId, { maxSort: t.sort, map: new Map() }); const bucket = builds.get(buildId); bucket.maxSort = Math.max(bucket.maxSort, t.sort); - bucket.map.set(platArch, t); + bucket.map.set(keyPA, t); } - // Sort builds newest-first + // order builds newest-first const orderedBuilds = Array.from(builds.entries()).sort( (a, b) => b[1].maxSort - a[1].maxSort, ); - // Explicit platform/arch order - const platformOrder = ["darwin", "windows", "linux"]; - const archOrder = ["aarch64", "arm64", "x86-64", "x86_64"]; + // explicit iteration order + const platforms = ["darwin", "windows", "linux"]; + const archs = ["aarch64", "x86-64"]; // normalized above const out = []; for (const [, bucket] of orderedBuilds) { - for (const p of platformOrder) { - for (const a of archOrder) { - const keyPA = `${p}|${a}`; - const tar = bucket.map.get(keyPA); - if (!tar) continue; + for (const p of platforms) { + for (const a of archs) { + const t = bucket.map.get(`${p}|${a}`); + if (!t) continue; - const base = tar.key.slice(0, -TAR.length); + const base = t.key.slice(0, -TAR.length); - // 1) DMG group (darwin only) + // 1) .dmg (darwin only) if (p === "darwin") { const dmgKey = base + ".dmg"; const dmg = byKey[dmgKey]; if (dmg) { out.push(dmg); - if (byKey[dmgKey + ".sha256"]) - out.push(byKey[dmgKey + ".sha256"]); - if (byKey[dmgKey + ".sig"]) out.push(byKey[dmgKey + ".sig"]); + for (const sk of sidecars(dmgKey)) + if (byKey[sk]) out.push(byKey[sk]); } } - // 2) SETUP group (windows only) + // 2) _setup.exe (windows only) if (p === "windows") { const setupKey = base + "_setup.exe"; const setup = byKey[setupKey]; if (setup) { out.push(setup); - if (byKey[setupKey + ".sha256"]) - out.push(byKey[setupKey + ".sha256"]); - if (byKey[setupKey + ".sig"]) out.push(byKey[setupKey + ".sig"]); + for (const sk of sidecars(setupKey)) + if (byKey[sk]) out.push(byKey[sk]); } } - // 3) TAR group (always) - out.push(tar); - if (byKey[tar.key + ".sha256"]) out.push(byKey[tar.key + ".sha256"]); - if (byKey[tar.key + ".sig"]) out.push(byKey[tar.key + ".sig"]); + // 3) .tar.gz + out.push(t); + for (const sk of sidecars(t.key)) if (byKey[sk]) out.push(byKey[sk]); } } }