Watch upstream #11
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: Watch upstream | |
| # Daily digest of what landed in block/buzz, plus alerts when a change actually | |
| # needs our attention. Keeps main in sync with upstream but never rewrites | |
| # duval-deploy, so the running server's clone is never invalidated. | |
| on: | |
| schedule: | |
| - cron: "30 13 * * *" # 09:30 America/New_York while EDT (08:30 during EST) | |
| workflow_dispatch: | |
| inputs: | |
| lookback: | |
| description: "Report on the last N upstream commits regardless of sync state (leave blank for the normal daily diff)" | |
| required: false | |
| default: "" | |
| permissions: | |
| contents: write | |
| issues: write | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| OVERLAY_BRANCH: duval-deploy | |
| UPSTREAM: block/buzz | |
| DIGEST_ISSUE_TITLE: "Upstream digest — block/buzz" | |
| jobs: | |
| watch: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Sync main from upstream | |
| id: sync | |
| run: | | |
| set -euo pipefail | |
| before="$(git rev-parse origin/main)" | |
| gh api -X POST "repos/${GITHUB_REPOSITORY}/merge-upstream" -f branch=main >/dev/null 2>&1 || true | |
| git fetch -q origin main | |
| after="$(git rev-parse origin/main)" | |
| { | |
| echo "before=$before" | |
| echo "after=$after" | |
| } >> "$GITHUB_OUTPUT" | |
| if [ "$before" = "$after" ]; then | |
| echo "changed=false" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "changed=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Build the digest | |
| id: digest | |
| run: | | |
| set -euo pipefail | |
| before="${{ steps.sync.outputs.before }}" | |
| after="${{ steps.sync.outputs.after }}" | |
| changed="${{ steps.sync.outputs.changed }}" | |
| # Manual runs may ask for a fixed lookback window instead. | |
| lookback="${{ github.event.inputs.lookback }}" | |
| if [ -n "$lookback" ]; then | |
| before="$(git rev-parse "${after}~${lookback}")" | |
| changed=true | |
| echo "Manual lookback of ${lookback} commit(s): ${before}..${after}" | |
| fi | |
| if [ "$changed" != "true" ]; then | |
| { | |
| echo "## Upstream digest — $(date -u +%Y-%m-%d)" | |
| echo | |
| echo "No new commits in \`${UPSTREAM}\` since the last check." | |
| } > digest.md | |
| echo "has_changes=false" >> "$GITHUB_OUTPUT" | |
| cat digest.md >> "$GITHUB_STEP_SUMMARY" | |
| exit 0 | |
| fi | |
| echo "has_changes=true" >> "$GITHUB_OUTPUT" | |
| git fetch -q origin "$OVERLAY_BRANCH" | |
| n_commits="$(git rev-list --count "$before".."$after")" | |
| n_authors="$(git log --format='%an' "$before".."$after" | sort -u | wc -l | tr -d ' ')" | |
| files_changed="$(git diff --name-only "$before".."$after" | wc -l | tr -d ' ')" | |
| # Which of the changed files does our deploy overlay also patch? | |
| base="$(git merge-base origin/main "origin/${OVERLAY_BRANCH}")" | |
| git diff --name-only "$base".."origin/${OVERLAY_BRANCH}" | sort -u > ours.txt | |
| git diff --name-only "$before".."$after" | sort -u > theirs.txt | |
| comm -12 ours.txt theirs.txt > overlap.txt | |
| # Group commit subjects by conventional-commit type. | |
| git log --no-merges --format='%s' "$before".."$after" > subjects.txt | |
| TYPES='feat|fix|perf|refactor|style|docs|chore|ci|build|test' | |
| render () { # $1 = heading, stdin = matching subjects | |
| local heading="$1" matches total | |
| matches="$(cat)" | |
| if [ -z "$matches" ]; then | |
| return 0 | |
| fi | |
| echo "### ${heading}" | |
| printf '%s\n' "$matches" | head -25 | while IFS= read -r line; do | |
| pr="$(printf '%s' "$line" | grep -oE '\(#[0-9]+\)$' | tr -d '(#)' || true)" | |
| text="$(printf '%s' "$line" | sed -E 's/ *\(#[0-9]+\)$//')" | |
| if [ -n "$pr" ]; then | |
| echo "- ${text} ([#${pr}](https://github.com/${UPSTREAM}/pull/${pr}))" | |
| else | |
| echo "- ${text}" | |
| fi | |
| done | |
| total="$(printf '%s\n' "$matches" | wc -l | tr -d ' ')" | |
| if [ "$total" -gt 25 ]; then | |
| echo "- _…and $((total - 25)) more_" | |
| fi | |
| echo | |
| } | |
| section () { # $1 = type regex, $2 = heading | |
| { grep -iE "^($1)(\(.+\))?!?:" subjects.txt || true; } | render "$2" | |
| } | |
| { | |
| echo "## Upstream digest — $(date -u +%Y-%m-%d)" | |
| echo | |
| echo "**${n_commits} commit(s)** from **${n_authors} author(s)**, touching **${files_changed} file(s)**." | |
| # Compare against our own repo: after a sync, main carries commits | |
| # (like this workflow) that do not exist upstream, so an upstream | |
| # compare URL would 404. | |
| echo "[Full compare](https://github.com/${GITHUB_REPOSITORY}/compare/${before}...${after})" | |
| echo | |
| if [ -s overlap.txt ]; then | |
| echo "> [!WARNING]" | |
| echo "> Upstream changed files our \`${OVERLAY_BRANCH}\` overlay also patches." | |
| echo "> Rebase and re-test the stack before deploying:" | |
| sed 's/^/> - `/; s/$/`/' overlap.txt | |
| echo | |
| fi | |
| section 'feat' 'Features' | |
| section 'fix' 'Fixes' | |
| section 'perf' 'Performance' | |
| section 'refactor|style' 'Refactoring' | |
| section 'docs|chore|ci|build|test' 'Docs, chores & CI' | |
| # Anything not using a conventional-commit prefix. | |
| { grep -ivE "^(${TYPES})(\(.+\))?!?:" subjects.txt || true; } | render 'Other' | |
| } > digest.md | |
| cat digest.md >> "$GITHUB_STEP_SUMMARY" | |
| - name: Post the digest to the rolling issue | |
| if: steps.digest.outputs.has_changes == 'true' | |
| run: | | |
| set -euo pipefail | |
| num="$(gh issue list --state open --search "\"${DIGEST_ISSUE_TITLE}\" in:title" \ | |
| --json number,title -q \ | |
| "[.[] | select(.title == \"${DIGEST_ISSUE_TITLE}\")][0].number" || true)" | |
| if [ -z "$num" ] || [ "$num" = "null" ]; then | |
| num="$(gh issue create --title "$DIGEST_ISSUE_TITLE" \ | |
| --body "Daily summary of what lands in \`${UPSTREAM}\`. Each run appends a comment; nothing is posted on quiet days." \ | |
| | grep -oE '[0-9]+$')" | |
| echo "Created rolling digest issue #${num}" | |
| fi | |
| # A bot opened this issue, so nobody is auto-subscribed. An @mention | |
| # notifies regardless of watch state. Change with: | |
| # gh variable set DIGEST_MENTION --repo <repo> --body "<github-handle>" | |
| # Written to a copy so digest.md stays clean for the Buzz post. | |
| cp digest.md digest-issue.md | |
| mention="${{ vars.DIGEST_MENTION }}" | |
| if [ -n "$mention" ]; then | |
| { | |
| echo | |
| echo "cc @${mention}" | |
| } >> digest-issue.md | |
| fi | |
| gh issue comment "$num" --body-file digest-issue.md | |
| echo "Posted digest to issue #${num}" | |
| - name: Post the digest into Buzz as the upstream bot | |
| if: steps.digest.outputs.has_changes == 'true' | |
| env: | |
| BUZZ_BOT_NSEC: ${{ secrets.BUZZ_BOT_NSEC }} | |
| BUZZ_RELAY_HTTP: https://chat.duvalsoftware.com | |
| CHANNEL: ${{ vars.BUZZ_CHANNEL_ID }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${BUZZ_BOT_NSEC}" ] || [ -z "${CHANNEL}" ]; then | |
| echo "Bot key or channel not configured; skipping the Buzz post." | |
| exit 0 | |
| fi | |
| python3 .github/scripts/buzz_bot.py post --channel "$CHANNEL" --file digest.md | |
| - name: Flag a stale pinned version on the landing page | |
| run: | | |
| set -euo pipefail | |
| git fetch -q origin "$OVERLAY_BRANCH" | |
| latest="$(gh release view --repo "$UPSTREAM" --json tagName -q .tagName 2>/dev/null || true)" | |
| latest_ver="$(printf '%s' "$latest" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true)" | |
| pinned_ver="$(git show "origin/${OVERLAY_BRANCH}:deploy/landing/index.html" 2>/dev/null \ | |
| | grep -oE 'Buzz_[0-9]+\.[0-9]+\.[0-9]+' | head -1 \ | |
| | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || true)" | |
| if [ -z "$latest_ver" ] || [ -z "$pinned_ver" ] || [ "$latest_ver" = "$pinned_ver" ]; then | |
| echo "Landing page links are current (pinned=${pinned_ver:-?} latest=${latest_ver:-?})." | |
| exit 0 | |
| fi | |
| title="Buzz ${latest_ver} released — landing page still links ${pinned_ver}" | |
| if gh issue list --state open --search "\"${title}\" in:title" \ | |
| --json title -q '.[].title' | grep -qxF "$title"; then | |
| echo "Issue already open; not duplicating." | |
| exit 0 | |
| fi | |
| { | |
| echo "Upstream released **${latest_ver}**, but the download buttons on" | |
| echo "https://chat.duvalsoftware.com still point at **${pinned_ver}**." | |
| echo | |
| echo "Update the asset links in \`deploy/landing/index.html\` on" | |
| echo "\`${OVERLAY_BRANCH}\`, then copy the file to \`/opt/buzz-landing/index.html\`" | |
| echo "on buzz-relay-01. No restart needed — Caddy serves it from disk." | |
| echo | |
| echo "Release: https://github.com/${UPSTREAM}/releases/tag/${latest}" | |
| } > release-body.md | |
| gh issue create --title "$title" --body-file release-body.md |