diff --git a/src/api.js b/src/api.js index 6581e33..1d71242 100644 --- a/src/api.js +++ b/src/api.js @@ -62,159 +62,131 @@ const getBucketFiles = async (folderName) => { Prefix: folderKey, }), ); - const ret = data.Contents.filter((item) => item.Key !== folderKey) + + const ret = (data.Contents || []) + .filter((item) => item.Key !== folderKey) .map((item) => { + const d = + item.LastModified instanceof Date + ? item.LastModified + : new Date(item.LastModified); return { - date: item.LastModified.toLocaleDateString("en-US", { - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }).replace(/,/g, ""), - sort: item.LastModified.getTime(), + date: d + .toLocaleDateString("en-US", { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + }) + .replace(/,/g, ""), + sort: d.getTime(), name: item.Key.replace(folderKey, ""), key: item.Key, }; }) - .sort((a, b) => { - return a.sort > b.sort ? -1 : a.sort < b.sort ? 1 : 0; - }); + .sort((a, b) => (a.sort > b.sort ? -1 : a.sort < b.sort ? 1 : 0)); + const itemCount = {}; const ext = ".tar.gz"; - const filteredItems = ret - .filter((item) => item.name.endsWith(ext)) - .filter((item) => { - if (folderName === "nightly") { - const parts = item.name.split("_"); - const groupId = `${parts[0]}_${parts[3]}_${parts[4]}`; - itemCount[groupId] = itemCount[groupId] || 0; - if (++itemCount[groupId] <= 3) { - return true; + // ----- choose which tars to keep (apply nightly retention) ----- + const baseTars = ret.filter((item) => item.name.endsWith(ext)); + const keepTarKeys = new Set(); + + for (const t of baseTars) { + if (folderName === "nightly") { + const parts = t.name.split("_"); + // parts[0]=product, parts[3]=platform, parts[4]=arch + const groupId = `${parts[0]}_${parts[3]}_${parts[4]}`; + itemCount[groupId] = itemCount[groupId] || 0; + + if (++itemCount[groupId] <= 3) { + keepTarKeys.add(t.key); + } else { + // mark old: tar + sidecars + if (!oldItems.includes(t.key)) { + oldItems.push(t.key, t.key + ".sha256", t.key + ".sig"); } - if (!oldItems.find((key) => key === item.key)) { - // mark tar + sidecars old - oldItems.push(item.key); - oldItems.push(item.key + ".sha256"); - oldItems.push(item.key + ".sig"); - - // windows companion (_setup.exe) + sidecars - if (parts[3] === "windows") { - const setup_key = - item.key.substring(0, item.key.length - ext.length) + - "_setup.exe"; - const setup_item = ret.find((i) => i.key == setup_key); - if (setup_item) { - oldItems.push(setup_key); - oldItems.push(setup_key + ".sha256"); - oldItems.push(setup_key + ".sig"); - } - } - - // darwin companion (.dmg) + sidecars <-- NEW - if (parts[3] === "darwin") { - const dmg_key = - item.key.substring(0, item.key.length - ext.length) + ".dmg"; - const dmg_item = ret.find((i) => i.key == dmg_key); - if (dmg_item) { - oldItems.push(dmg_key); - oldItems.push(dmg_key + ".sha256"); - oldItems.push(dmg_key + ".sig"); - } + // windows companion + if (parts[3] === "windows") { + const setup_key = + t.key.substring(0, t.key.length - ext.length) + "_setup.exe"; + const setup_item = ret.find((x) => x.key === setup_key); + if (setup_item && !oldItems.includes(setup_key)) { + oldItems.push( + setup_key, + setup_key + ".sha256", + setup_key + ".sig", + ); + } + } + // darwin companion (.dmg) + if (parts[3] === "darwin") { + const dmg_key = + t.key.substring(0, t.key.length - ext.length) + ".dmg"; + const dmg_item = ret.find((x) => x.key === dmg_key); + if (dmg_item && !oldItems.includes(dmg_key)) { + oldItems.push(dmg_key, dmg_key + ".sha256", dmg_key + ".sig"); } } - return false; } - return true; - }); + } else { + keepTarKeys.add(t.key); + } + } - const totalCount = filteredItems.length * 3; - const setup_items = []; - const dmg_items = []; // <-- NEW - let setup_offset = 0; - let dmg_offset = 0; + // ----- build final output without splicing while iterating ----- + const byKey = Object.fromEntries(ret.map((r) => [r.key, r])); + const out = []; - for (let i = 0; i < totalCount && i < filteredItems.length; i += 3) { - const parts = filteredItems[i].name.split("_"); + for (const t of baseTars) { + if (!keepTarKeys.has(t.key)) continue; - // add tar sidecars - let item = ret.filter( - (it) => it.name === filteredItems[i].name + ".sha256", - ); - filteredItems.splice(i + 1, 0, ...item); - item = ret.filter((it) => it.name === filteredItems[i].name + ".sig"); - filteredItems.splice(i + 2, 0, ...item); + const parts = t.name.split("_"); - // windows companion block (setup.exe + sidecars) + // 1) tar + sidecars + out.push(t); + const tarSha = byKey[t.key + ".sha256"]; + if (tarSha) out.push(tarSha); + const tarSig = byKey[t.key + ".sig"]; + if (tarSig) out.push(tarSig); + + // 2) windows companion: _setup.exe + sidecars if (parts[3] === "windows") { const setup_key = - filteredItems[i].key.substring( - 0, - filteredItems[i].key.length - ext.length, - ) + "_setup.exe"; - - const setup_item = ret.find((it) => it.key == setup_key); - if (setup_item) { - const setup_item2 = ret.find((it) => it.key == setup_key + ".sha256"); - const setup_item3 = ret.find((it) => it.key == setup_key + ".sig"); - setup_items.push([ - { idx: i + 3 + setup_offset }, - setup_item, - setup_item2, - setup_item3, - ]); - setup_offset += 3; + t.key.substring(0, t.key.length - ext.length) + "_setup.exe"; + const setup = byKey[setup_key]; + if (setup) { + out.push(setup); + const setupSha = byKey[setup_key + ".sha256"]; + if (setupSha) out.push(setupSha); + const setupSig = byKey[setup_key + ".sig"]; + if (setupSig) out.push(setupSig); } } - // darwin companion block (.dmg + sidecars) <-- NEW (mirrors setup flow) + // 3) darwin companion: .dmg + sidecars if (parts[3] === "darwin") { - const dmg_key = - filteredItems[i].key.substring( - 0, - filteredItems[i].key.length - ext.length, - ) + ".dmg"; - - const dmg_item = ret.find((it) => it.key == dmg_key); - if (dmg_item) { - const dmg_item2 = ret.find((it) => it.key == dmg_key + ".sha256"); - const dmg_item3 = ret.find((it) => it.key == dmg_key + ".sig"); - dmg_items.push([ - { idx: i + 3 + dmg_offset }, - dmg_item, - dmg_item2, - dmg_item3, - ]); - dmg_offset += 3; + const dmg_key = t.key.substring(0, t.key.length - ext.length) + ".dmg"; + const dmg = byKey[dmg_key]; + if (dmg) { + out.push(dmg); + const dmgSha = byKey[dmg_key + ".sha256"]; + if (dmgSha) out.push(dmgSha); + const dmgSig = byKey[dmg_key + ".sig"]; + if (dmgSig) out.push(dmgSig); } } } - // Insert darwin DMG blocks first, then Windows setup blocks (order doesn't matter across platforms) - if (dmg_items.length > 0) { - dmg_items.forEach((items) => { - filteredItems.splice(items[0].idx, 0, items[1]); - filteredItems.splice(items[0].idx + 1, 0, items[2]); - filteredItems.splice(items[0].idx + 2, 0, items[3]); - }); - } - - if (setup_items.length > 0) { - setup_items.forEach((items) => { - filteredItems.splice(items[0].idx, 0, items[1]); - filteredItems.splice(items[0].idx + 1, 0, items[2]); - filteredItems.splice(items[0].idx + 2, 0, items[3]); - }); - } - - return filteredItems; + return out; } catch (err) { console.error(err); + // make sure we always return an array for callers + return []; } - - return []; }; export { cleanOldItems, createDownloadLink, getBucketFiles };