const getBucketFiles = async (folderName) => { try { folderName = (folderName || "").toLowerCase(); const folderKey = encodeURIComponent(folderName) + "/"; const data = await s3.send( new ListObjectsCommand({ Bucket: BUCKET, Prefix: folderKey, }), ); const contents = Array.isArray(data?.Contents) ? data.Contents : []; const ret = contents .filter((obj) => obj.Key !== folderKey) .map((obj) => { const d = obj.LastModified instanceof Date ? obj.LastModified : new Date(obj.LastModified); return { 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() || 0, name: (obj.Key || "").replace(folderKey, ""), key: obj.Key || "", }; }) .sort((a, b) => (a.sort > b.sort ? -1 : a.sort < b.sort ? 1 : 0)); const itemCount = {}; const ext = ".tar.gz"; // 1) choose which .tar.gz to keep (nightly retention unchanged) const tars = ret.filter((it) => it.name.endsWith(ext)); const keepTarKeys = new Set(); for (const t of tars) { 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]}`; 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"); } // windows companion if (parts[3] === "windows") { const setupKey = t.key.substring(0, t.key.length - ext.length) + "_setup.exe"; const hasSetup = ret.find((x) => x.key === setupKey); if (hasSetup && !oldItems.includes(setupKey)) { oldItems.push(setupKey, setupKey + ".sha256", setupKey + ".sig"); } } // darwin companion if (parts[3] === "darwin") { const dmgKey = t.key.substring(0, t.key.length - ext.length) + ".dmg"; const hasDmg = ret.find((x) => x.key === dmgKey); if (hasDmg && !oldItems.includes(dmgKey)) { oldItems.push(dmgKey, dmgKey + ".sha256", dmgKey + ".sig"); } } } } else { keepTarKeys.add(t.key); } } // 2) fast lookup by key const byKey = Object.fromEntries(ret.map((r) => [r.key, r])); // 3) build final list strictly in the order you want for each group: // DMG(+sidecars) -> SETUP(+sidecars) -> TAR(+sidecars) const out = []; for (const t of tars) { if (!keepTarKeys.has(t.key)) continue; const base = t.key.substring(0, t.key.length - ext.length); const dmgKey = base + ".dmg"; const setupKey = base + "_setup.exe"; // group order: 1) DMG group (if present) const dmg = byKey[dmgKey]; if (dmg) { out.push(dmg); const dmgSha = byKey[dmgKey + ".sha256"]; if (dmgSha) out.push(dmgSha); const dmgSig = byKey[dmgKey + ".sig"]; if (dmgSig) out.push(dmgSig); } // 2) SETUP group (if present) const setup = byKey[setupKey]; if (setup) { out.push(setup); const setupSha = byKey[setupKey + ".sha256"]; if (setupSha) out.push(setupSha); const setupSig = byKey[setupKey + ".sig"]; if (setupSig) out.push(setupSig); } // 3) TAR group (always present for this loop) 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); } return out; } catch (err) { console.error(err); return []; } };