function createReleaseReceipt({ storage, getLogin, fetchJson, launcher = null, dialog = null, statusNode = null, checksNode = null, releaseNode = null }) { const prefix = 'stackchain.release-receipt.v1:'; let receipt = null; const login = () => String(getLogin?.() || '').trim().toLowerCase(); const key = () => prefix + login(); const path = value => 'api/v1/repos/' + String(value.repository || '').split('/') .map(encodeURIComponent).join('/') + '/release-receipt/' + encodeURIComponent(value.commit_sha); function restore() { const account = login(); if (!account) return null; try { const parsed = JSON.parse(storage?.getItem(prefix + account) || 'null'); receipt = parsed && parsed.account === account && parsed.repository && parsed.commit_sha ? parsed : null; } catch (_error) { receipt = null; } render(receipt?.status || null); return receipt; } function capture(item, mergeResult) { const account = login(); const commitSha = String(mergeResult?.merge_commit_sha || '').trim(); if (!account || !item?.repository || !commitSha) throw new Error('The exact merge commit is unavailable.'); receipt = { account, repository: item.repository, number: Number(item.number), key: item.key || item.repository + '#' + item.number, commit_sha: commitSha, status: null, }; storage?.setItem(key(), JSON.stringify(receipt)); render(null); return receipt; } function summarize(payload) { const checks = Array.isArray(payload?.checks) ? payload.checks : []; const failing = checks.filter(check => ['failure', 'error'].includes(check.state)).map(check => check.name).filter(Boolean); const pending = checks.filter(check => check.state === 'pending').map(check => check.name).filter(Boolean); if (payload?.release) return { ...payload, label: 'Released · ' + payload.release.tag, checks: [] }; if (failing.length) return { ...payload, label: 'Checks failed', checks: failing }; if (payload?.ci_state === 'success') return { ...payload, label: 'Checks passed · waiting for release', checks: [] }; return { ...payload, label: 'Checks running', checks: pending }; } function render(status) { if (launcher) { launcher.hidden = !receipt; launcher.textContent = status?.label || (receipt ? 'Merged · tracking release' : ''); } if (statusNode) statusNode.textContent = status?.label || 'Checking the exact merge commit…'; if (checksNode) checksNode.textContent = (status?.checks || []).join(', '); if (releaseNode) { releaseNode.hidden = !status?.release?.url; if (status?.release?.url) { releaseNode.href = status.release.url; releaseNode.textContent = 'Open release ' + status.release.tag; } } } async function refresh() { if (!receipt) restore(); if (!receipt) return null; const payload = await fetchJson(path(receipt), { headers: { Accept: 'application/json' } }); if (payload?.commit_sha !== receipt.commit_sha) throw new Error('Release evidence did not match the merged commit.'); const status = summarize(payload); receipt.status = status; storage?.setItem(key(), JSON.stringify(receipt)); render(status); return status; } function dismiss() { const accountKey = key(); storage?.removeItem(accountKey); receipt = null; render(null); if (dialog?.open) dialog.close(); } function bind() { launcher?.addEventListener('click', async () => { dialog?.showModal?.(); try { await refresh(); } catch (error) { if (statusNode) statusNode.textContent = error.message + ' Retry when connected.'; } }); } return { capture, restore, refresh, dismiss, bind, fetchJson }; } if (typeof module !== 'undefined' && module.exports) module.exports = createReleaseReceipt;