diff --git a/.github/workflows/weekly-build-notes.md b/.github/workflows/weekly-build-notes.md index 35175110fd..1ff153a15f 100644 --- a/.github/workflows/weekly-build-notes.md +++ b/.github/workflows/weekly-build-notes.md @@ -1,6 +1,7 @@ > [!IMPORTANT] > Bleeding edge FreeCAD development builds for testing bugfixes, regressions, and recently implemented features. Do not use in a production environment. +**Changes since last weekly:** ### How-to use @@ -8,7 +9,7 @@ 2. Unpack the bundle to any folder on your system 3. Launch the application - **Windows** - Run `\bin\FreeCAD.exe` in the extracted directory + Run `\FreeCAD.exe` in the extracted directory - **macOS** Launch `/FreeCAD.app` in the extracted directory - **Linux** diff --git a/.github/workflows/weekly-compare-link.yml b/.github/workflows/weekly-compare-link.yml new file mode 100644 index 0000000000..8c2c606d8b --- /dev/null +++ b/.github/workflows/weekly-compare-link.yml @@ -0,0 +1,140 @@ +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: "" + 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 || ''; + 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}`);