Weekly compare link to release notes #28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Weekly compare link to release notes | |
| on: | |
| release: | |
| types: [published] # run automatically when a (pre-)release is published | |
| workflow_dispatch: # allow manual runs too | |
| inputs: | |
| current_tag: | |
| description: "Weekly tag (e.g., weekly-2026.01.07). Leave empty to auto-detect latest weekly pre-release." | |
| required: false | |
| dry_run: | |
| description: "Only compute; do not update the release body." | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: write # required to PATCH the release body | |
| jobs: | |
| update-notes: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Inject compare link into weekly release notes | |
| uses: actions/github-script@v7 | |
| env: | |
| # Pass manual inputs via env for convenience | |
| CURRENT_TAG: ${{ github.event.inputs.current_tag }} | |
| DRY_RUN: ${{ github.event.inputs.dry_run }} | |
| PLACEHOLDER: "<!--DIFF_LINK-->" | |
| with: | |
| script: | | |
| // Updates the current weekly release notes with a compare link to the previous weekly. | |
| // Works for both release-published events and manual (workflow_dispatch) runs. | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const rx = /^weekly-(\d{4})\.(\d{2})\.(\d{2})$/; | |
| // Determine currentTag: | |
| // 1) Manual input via workflow_dispatch (env.CURRENT_TAG) | |
| // 2) Tag from release event payload | |
| // 3) Fallback: newest weekly pre-release | |
| let currentTag = process.env.CURRENT_TAG || (context.payload?.release?.tag_name) || null; | |
| async function detectLatestWeeklyTag() { | |
| const releases = await github.paginate(github.rest.repos.listReleases, { owner, repo, per_page: 100 }); | |
| const cand = releases.find(r => r.prerelease && typeof r.tag_name === 'string' && rx.test(r.tag_name)); | |
| return cand?.tag_name || null; | |
| } | |
| if (!currentTag) { | |
| currentTag = await detectLatestWeeklyTag(); | |
| } | |
| if (!currentTag || !rx.test(currentTag)) { | |
| core.info(`No weekly tag detected or tag format mismatch ('${currentTag}'). Skipping.`); | |
| return; | |
| } | |
| // Resolve the current release object | |
| let curRel; | |
| try { | |
| const { data } = await github.rest.repos.getReleaseByTag({ owner, repo, tag: currentTag }); | |
| curRel = data; | |
| } catch (e) { | |
| core.setFailed(`No release for tag ${currentTag}: ${e.message}`); | |
| return; | |
| } | |
| // If event is a normal release (not pre-release), skip automatically-run case; | |
| // but allow manual override (manual runs can patch any weekly tag). | |
| const isManual = context.eventName === 'workflow_dispatch'; | |
| if (!isManual && !curRel.prerelease) { | |
| core.info('Current release is not a pre-release; skipping (auto run).'); | |
| return; | |
| } | |
| // Helpers | |
| const toPrevWeeklyTag = (tag) => { | |
| const [, y, m, d] = tag.match(rx); | |
| const dt = new Date(Date.UTC(+y, +m - 1, +d)); | |
| const prev = new Date(dt.getTime() - 7 * 24 * 3600 * 1000); // minus 7 days | |
| const iso = prev.toISOString().slice(0, 10); // YYYY-MM-DD | |
| return `weekly-${iso.replace(/-/g, '.')}`; // weekly-YYYY.MM.DD | |
| }; | |
| const ymdKey = (t) => t.replace(rx, '$1$2$3'); // YYYYMMDD | |
| async function tagExists(tag) { | |
| try { | |
| await github.rest.git.getRef({ owner, repo, ref: `tags/${tag}` }); | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| // Compute previous weekly deterministically, then fall back if needed | |
| let prevTag = toPrevWeeklyTag(currentTag); | |
| if (!(await tagExists(prevTag))) { | |
| core.info(`Computed previous tag ${prevTag} not found; scanning older weeklies...`); | |
| const releases = await github.paginate(github.rest.repos.listReleases, { owner, repo, per_page: 100 }); | |
| const curKey = ymdKey(currentTag); | |
| const older = releases | |
| .filter(r => r.prerelease && typeof r.tag_name === 'string' && rx.test(r.tag_name)) | |
| .map(r => ({ tag: r.tag_name, key: ymdKey(r.tag_name) })) | |
| .filter(x => x.key < curKey) | |
| .sort((a, b) => b.key.localeCompare(a.key)); // newest older first | |
| if (!older.length) { | |
| core.info('No older weekly found; nothing to do.'); | |
| return; | |
| } | |
| prevTag = older[0].tag; | |
| } | |
| const compareUrl = `https://github.com/${owner}/${repo}/compare/${prevTag}...${currentTag}`; | |
| const head = `**Changes since last weekly (${prevTag} → ${currentTag}):**\n${compareUrl}\n`; | |
| if (process.env.DRY_RUN === 'true') { | |
| core.info(`[DRY RUN] Would update release ${currentTag} with: ${compareUrl}`); | |
| return; | |
| } | |
| // Idempotent body update | |
| let body = curRel.body || ''; | |
| if (body.includes(compareUrl)) { | |
| core.info('Compare URL already present; done.'); | |
| return; | |
| } | |
| const placeholder = process.env.PLACEHOLDER || '<!--DIFF_LINK-->'; | |
| if (body.includes(placeholder)) { | |
| body = body.replace(placeholder, compareUrl); | |
| } else if (/\*\*Changes since last weekly:/i.test(body)) { | |
| body = body.replace(/\*\*Changes since last weekly:[^\n]*\n?/i, head + '\n'); | |
| } else { | |
| body += (body.endsWith('\n') ? '\n' : '\n\n') + head; | |
| } | |
| await github.rest.repos.updateRelease({ owner, repo, release_id: curRel.id, body }); | |
| core.info(`Release notes updated with compare link: ${compareUrl}`); |