UNPKG

@cashu/cashu-ts

Version:

cashu library for communicating with a cashu mint

206 lines (173 loc) 5.73 kB
import fs from 'fs'; import fetch from 'node-fetch'; // ----------------------------- // Config per dependency // ----------------------------- const MAKEFILE = 'Makefile'; type Dep = { name: string; githubRepo: string; dockerTagPrefix: string; stableVar: string; rcVar: string; }; const DEPENDENCIES: Dep[] = [ { name: 'cashubtc/mintd', githubRepo: 'cashubtc/cdk', dockerTagPrefix: 'v', stableVar: 'CDK_IMAGE', rcVar: 'CDK_IMAGE_RC', }, { name: 'cashubtc/nutshell', githubRepo: 'cashubtc/nutshell', dockerTagPrefix: '', stableVar: 'NUT_IMAGE', rcVar: 'NUT_IMAGE_RC', }, ]; // for github output interface Update { name: string; version: string; } const updates: Update[] = []; // ----------------------------- // Helpers // ----------------------------- function setGithubOutput(name: string, value: string) { const outputPath = process.env.GITHUB_OUTPUT; if (!outputPath) return; fs.appendFileSync(outputPath, `${name}<<EOF\n${value}\nEOF\n`); } // Write PR outputs function writePrOutputs(updates: Update[]): void { if (updates.length === 0) { setGithubOutput('changed', 'false'); return; } setGithubOutput('changed', 'true'); const summary = updates.map((u) => `- ${u.name} -> ${u.version}`).join('\n'); setGithubOutput('summary', summary); const title = updates.length === 1 ? `chore(docker): update ${updates[0].name} to ${updates[0].version}` : 'chore(docker): update mint images'; setGithubOutput('title', title); setGithubOutput('branch', 'automation/update-mint-images'); } type GithubRelease = { tag_name: string; prerelease: boolean; draft: boolean; }; async function fetchGithubReleases(repo: string): Promise<GithubRelease[]> { const url = `https://api.github.com/repos/${repo}/releases?per_page=100`; const headers: Record<string, string> = { Accept: 'application/vnd.github+json' }; const token = process.env.GITHUB_TOKEN; if (token) headers['Authorization'] = `Bearer ${token}`; const resp = await fetch(url, { headers }); if (!resp.ok) { throw new Error(`Failed to fetch releases for ${repo}: ${resp.statusText}`); } return (await resp.json()) as GithubRelease[]; } function stripV(tag: string): string { return tag.replace(/^v/, ''); } function semverKey(tag: string): number[] { const base = tag.split('-')[0]; return base.split('.').map((x) => parseInt(x, 10)); } function isRc(version: string): boolean { return /^\d+\.\d+\.\d+-rc\.\d+$/.test(version); } function getLatestStable(releases: GithubRelease[]): string | null { const stable = releases .filter((r) => !r.draft && !r.prerelease) .map((r) => stripV(r.tag_name)) .filter((v) => /^\d+\.\d+\.\d+$/.test(v)); if (stable.length === 0) return null; return stable.sort(compareSemver).pop()!; } function getLatestRc(releases: GithubRelease[]): string | null { const rcs = releases .filter((r) => !r.draft && r.prerelease) .map((r) => stripV(r.tag_name)) .filter(isRc); if (rcs.length === 0) return null; return rcs.sort(compareSemver).pop()!; } function compareSemver(a: string, b: string): number { const parse = (v: string) => { const [base, suffix] = v.split('-'); const parts = base.split('.').map((x) => parseInt(x, 10)); const rcMatch = suffix?.match(/^rc\.(\d+)$/); const rc = rcMatch ? parseInt(rcMatch[1], 10) : null; return { parts, rc }; }; const av = parse(a); const bv = parse(b); for (let i = 0; i < 3; i++) { if (av.parts[i] !== bv.parts[i]) return av.parts[i] - bv.parts[i]; } if (av.rc !== null && bv.rc !== null) return av.rc - bv.rc; return 0; } async function dockerTagExists(repo: string, tag: string): Promise<boolean> { const resp = await fetch(`https://hub.docker.com/v2/repositories/${repo}/tags/${tag}`); return resp.ok; } function updateMakefile(varName: string, newTag: string) { const content = fs.readFileSync(MAKEFILE, 'utf-8'); const regex = new RegExp(`^${varName}\\s*\\?=.*$`, 'm'); const updated = content.replace(regex, `${varName} ?= ${newTag}`); if (updated !== content) { fs.writeFileSync(MAKEFILE, updated); console.log(`Updated ${varName} to ${newTag}`); // Only writes in CI updates.push({ name: varName, version: newTag }); return true; } else { console.log(`No update needed for ${varName}`); return false; } } // ----------------------------- // Main // ----------------------------- async function main() { for (const dep of DEPENDENCIES) { const releases = await fetchGithubReleases(dep.githubRepo); const stable = getLatestStable(releases); const rc = getLatestRc(releases); if (stable) { const dockerTag = `${dep.dockerTagPrefix}${stable}`; if (await dockerTagExists(dep.name, dockerTag)) { updateMakefile(dep.stableVar, `${dep.name}:${dockerTag}`); } else { console.log(`Docker image ${dep.name}:${dockerTag} not yet available, skipping`); } } if (rc) { const stableVer = stable ? semverKey(stable) : [0, 0, 0]; const rcVer = semverKey(rc.split('-')[0]); if (rcVer.some((v, i) => v > (stableVer[i] || 0))) { const dockerTag = `${dep.dockerTagPrefix}${rc}`; if (await dockerTagExists(dep.name, dockerTag)) { updateMakefile(dep.rcVar, `${dep.name}:${dockerTag}`); } else { console.log(`Docker image ${dep.name}:${dockerTag} not yet available, skipping`); } } else { console.log(`RC ${rc} is not newer than stable ${stable}, skipping RC update`); } } } writePrOutputs(updates); } main().catch((err) => { console.error(err); process.exit(1); });