From af9395d5ab604d6aa226b8fff1a53d9d575cb6e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20BIDON?= Date: Fri, 19 Jun 2026 22:25:21 +0200 Subject: [PATCH] feat(blog): add monthly newsletter automation + May 2026 report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a concise, autonomous monthly report pipeline alongside the existing quarterly one (same scope, shorter window, no strategy narrative): - .claude/skills/monthly-newsletter.md — single-window, unattended fork of the quarterly skill: previous-calendar-month window, GitHub-API data sourcing, formula-based Hugo weight (999999 - YYYYMM), and the dual description / discord_description front matter. - .github/workflows/announce-monthly.yml — announces a newly added monthly report to #monthly-news via the DISCORD_MONTHLY_WEBHOOK_URL secret, chaining off the "Update documentation" deploy (mirrors announce-quarterly.yml). - docs/doc-site/blog/monthly/2026-05.md — the first monthly report (May 2026). Co-Authored-By: Claude Opus 4.8 Signed-off-by: Frédéric BIDON --- .claude/skills/monthly-newsletter.md | 177 +++++++++++++++++++++++++ .github/workflows/announce-monthly.yml | 110 +++++++++++++++ docs/doc-site/blog/monthly/2026-05.md | 58 ++++++++ 3 files changed, 345 insertions(+) create mode 100644 .claude/skills/monthly-newsletter.md create mode 100644 .github/workflows/announce-monthly.yml create mode 100644 docs/doc-site/blog/monthly/2026-05.md diff --git a/.claude/skills/monthly-newsletter.md b/.claude/skills/monthly-newsletter.md new file mode 100644 index 0000000..158436d --- /dev/null +++ b/.claude/skills/monthly-newsletter.md @@ -0,0 +1,177 @@ +--- +name: monthly-newsletter +description: Generate a short, factual monthly activity report across all go-openapi and go-swagger repositories. Use when asked to produce the monthly newsletter/report for a given month, or when run unattended by the monthly-reports routine. The companion to quarterly-newsletter, but concise and autonomous. +--- + +# Monthly newsletter (go-openapi & go-swagger) + +A **short, fact-led** report of the previous calendar month's activity across all +go-openapi and go-swagger repositories. It is the low-effort counterpart to +`quarterly-newsletter`: same scope, shorter window, **no strategy narrative**. + +This skill is designed to run **unattended end-to-end** (e.g. from a scheduled cloud +routine). **Never ask the user a question** — if something is ambiguous, make the +conservative choice and report only what is verifiable. Omit, do not invent. + +## Window + +- The report covers the **previous calendar month**. +- Derive the bounds from today's date — do not hard-code: + - `--since` = first day of last month, `00:00:00Z` + - `--until` = first day of the current month, `00:00:00Z` +- Refer to the month in prose by name and year (e.g. "May 2026"). + +## Output file + +- Path: `docs/doc-site/blog/monthly/-.md` (zero-padded month, e.g. `2026-05.md`). +- Hugo slug → `-`; published URL → `…/blog/monthly/-/index.html`. + +## Length target + +Roughly **a third of a quarterly** — skimmable in about a minute. If in doubt, cut. +Drop the quarterly's impact-assessment essay, risk rating, and long per-repo prose. + +## Document structure + +### 0. Front matter (Hugo) — two distinct summary fields + +```yaml +--- +title: May 2026 +description: go-openapi & go-swagger activity in May 2026 # plain text — Hugo card text +weight: 797394 # 999999 - (YYYY*100 + MM); lower sorts first +discord_description: |- + <~8-12 line Discord-flavored summary — see below> +--- +``` + +- **`title`** — ` ` (e.g. `May 2026`). +- **`description`** — short **plain-text** one/two-liner. Hugo renders it as the page + meta description and the blog-card text, so keep it free of markup (`•`, `**…**` + would show literally on the cards). +- **`weight`** — `999999 - (YYYY*100 + MM)`. This sorts newest-first automatically with + no shared state to maintain (e.g. `2026-05` → `999999 - 202605 = 797394`). +- **`discord_description`** — the rich **~8-12 line** summary, posted verbatim by the + `announce-monthly.yml` workflow as the Discord embed body. Hugo ignores this custom + field. Discord embeds render **markdown, not HTML** — use `• ` bullets, `**bold**` + for repo/product names, `` `code` ``. Lead with one framing sentence, then the + headline items. **Do not** include the title or the report URL — the workflow adds + those. Keep it shorter and flatter than the quarterly's. + +### 1. Intro (one paragraph) + +A single short paragraph framing the month: the overall shape of activity and the one +or two things worth noticing. No grand strategy, no marketing language. State the +**overall effort** inline (e.g. "N commits across M repositories"). + +### 2. Themes (compact list) + +A short bulleted list of the month's cross-cutting themes — group similar changes +across repos rather than listing per-repo. Typical buckets: features, bug fixes, +dependency/CI maintenance, docs, releases. **Keep it to the few themes that actually +mattered this month**; do not pad with boilerplate categories that saw no real change. + +### 3. Repository highlights (short table) + +A compact table, one row per repo that saw **notable** change (skip repos with only +routine dependabot/CI noise unless that was the whole month): + +```markdown +| Repository | Latest release | Highlights | +|---|---|---| +| runtime | v0.30.1 | connection diagnostic; security pass | +``` + +Do **not** include per-repo commit counts. + +### 4. Quarter-overlap note (only in quarter-end months) + +If the reported month is the last month of a calendar quarter (Mar, Jun, Sep, Dec), +add a single line pointing readers to the quarterly for the strategic picture, e.g.: +"> A quarterly report covering this period in more depth will follow." Otherwise omit +this section entirely. + +### 5. Thanks to our contributors + +Close with a short, warm thank-you to the **external human contributors** for the month. + +- **Include** anyone who authored a commit in the window across all go-openapi *and* + go-swagger repos. +- **Exclude**: the maintainer(s) (`fredbi` / `Frédéric BIDON`), AI agents (Claude, + `Copilot`), and bots (`dependabot[bot]`, `bot-go-openapi[bot]`, `go-openapi-bot`). +- List each by **GitHub handle** and the repo(s) they touched. Never list email + addresses. Do **not** count individual contributions. +- If there were no external contributors this month, omit the section rather than + writing an empty thank-you. + +## Data collection (cloud environment) + +The routine starts with **only `doc-site` checked out**, so prefer the **GitHub API** +over cloning ~18 repos. **All reads must be authenticated** — unauthenticated GitHub +API is 60 req/hour (far too low); authenticated is 5,000 req/hour. The `gh` CLI uses +the `GH_TOKEN` env var automatically. + +1. **Enumerate repositories** in both orgs (skip archived repos and forks): + ```bash + gh repo list go-openapi --no-archived --source --limit 100 --json name,isArchived,isFork \ + --jq '.[] | select(.isArchived|not) | select(.isFork|not) | .name' + gh repo list go-swagger --no-archived --source --limit 100 --json name,isArchived,isFork \ + --jq '.[] | select(.isArchived|not) | select(.isFork|not) | .name' + ``` + +2. **Per repo, over the window**, pull what you need via the API (replace `/`, + ``, ``): + - Commits on the default branch: + ```bash + gh api -X GET "repos///commits" \ + -f since= -f until= --paginate \ + --jq '.[] | {sha:.sha, msg:(.commit.message|split("\n")[0]), login:.author.login, name:.commit.author.name}' + ``` + - Releases published in the window: + ```bash + gh api "repos///releases" --paginate \ + --jq '.[] | {tag:.tag_name, published:.published_at}' + ``` + - Latest release tag (for the highlights table). **Do not** use `releases/latest` — + it returns HTTP 404 for repos with no published release and `gh` leaks the error + body to stdout. Ask for the newest release (or newest tag) instead, which yields an + empty array, not a 404: + ```bash + tag=$(gh api "repos///releases?per_page=1" --jq '.[0].tag_name // empty' 2>/dev/null) + [ -z "$tag" ] && tag=$(gh api "repos///tags?per_page=1" --jq '.[0].name // empty' 2>/dev/null) + # repos with neither (e.g. codegen, doc-site) → leave the cell as "—" + ``` + - Merged PRs in the window (optional, for richer highlights): + ```bash + gh api -X GET "search/issues" \ + -f q='repo:/ is:pr is:merged merged:..' \ + --jq '.items[] | {n:.number, title:.title, user:.user.login}' + ``` + +3. **Shallow-clone only if needed** for file-level inspection of one repo: + `git clone --depth=50 --shallow-since= https://github.com//`. + +4. **Contributor handles** come straight from the commit API (`.author.login` above); + no SHA-to-login mapping needed. Skip null logins (web-flow / unmatched) rather than + guessing. + +5. **Cap and log truncation.** If any repo's history is too large to page fully within + reason, `log` what was capped rather than silently truncating — a report that hides + gaps reads as complete when it is not. + +## Style + +**DO:** factual, developer-focused language; factorize similar changes across repos; +extract latest release tags; keep it short. + +**DON'T:** per-repo commit counts; marketing language ("demonstrates maturity"); names +or emails in the themes/highlights (the thanks section is the one place to credit +external contributors, by handle); commit-by-commit logs; subjective praise; questions +to the user. + +## After writing + +Write the file to `docs/doc-site/blog/monthly/-.md`. Do **not** post to +Discord and do **not** commit from the skill — the routine handles the commit (via the +GitHub Contents API) and the PR; the Discord announcement happens on merge via +`announce-monthly.yml`. diff --git a/.github/workflows/announce-monthly.yml b/.github/workflows/announce-monthly.yml new file mode 100644 index 0000000..a48630e --- /dev/null +++ b/.github/workflows/announce-monthly.yml @@ -0,0 +1,110 @@ +name: "Announce monthly report on Discord" + +# Posts an announcement to the #monthly-news Discord channel when a new +# monthly report is published. It chains off the "Update documentation" +# workflow so we only announce once the doc site has been built and deployed +# to GitHub Pages (i.e. the report URL is actually live). +# +# Monthly only — other report kinds (quarterly, ...) are announced to their own +# channels by their own workflows. The webhook is the DISCORD_MONTHLY_WEBHOOK_URL +# repo secret (the #monthly-news channel webhook). + +permissions: + contents: read + +on: + workflow_run: + workflows: + - "Update documentation" + types: + - completed + branches: + - master + +concurrency: + group: discord-monthly-announce + cancel-in-progress: false + +defaults: + run: + shell: bash + +jobs: + announce: + name: Announce new monthly report + runs-on: ubuntu-latest + # Only when the doc build/deploy succeeded, and only for pushes to master + # (not PR builds of the upstream workflow). Note: workflow_run does not + # support a `paths` filter, so the monthly-file check is done in-job below. + if: ${{ github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' }} + steps: + - + name: Checkout the deployed commit + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + ref: ${{ github.event.workflow_run.head_sha }} + fetch-depth: 2 + - + name: Detect a newly added monthly report + id: detect + run: | + # Announce only when a NEW monthly report file is added in this + # commit (--diff-filter=A); edits/typo-fixes to existing reports do + # not re-trigger an announcement. + file=$(git diff --name-only --diff-filter=A HEAD^ HEAD \ + | grep -E '^docs/doc-site/blog/monthly/[^/]+\.md$' \ + | head -1 || true) + if [ -n "${file}" ]; then + echo "changed=true" >> "$GITHUB_OUTPUT" + echo "file=${file}" >> "$GITHUB_OUTPUT" + echo "New monthly report added: ${file}" + else + echo "changed=false" >> "$GITHUB_OUTPUT" + echo "No new monthly report in this commit; skipping announcement." + fi + - + name: Post announcement to Discord + if: ${{ steps.detect.outputs.changed == 'true' }} + env: + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_MONTHLY_WEBHOOK_URL }} + REPORT_FILE: ${{ steps.detect.outputs.file }} + run: | + if [ -z "${DISCORD_WEBHOOK_URL}" ]; then + echo "::error title=discord::DISCORD_MONTHLY_WEBHOOK_URL secret is not set"; exit 1 + fi + + # The embed body IS the report's front-matter `discord_description` — + # a short, Discord-flavored (markdown) summary authored with the + # report (see the monthly-newsletter skill). It is a dedicated field + # (not Hugo's `description`, which is plain-text card text). Title and + # URL are derived from the file, so they aren't repeated in the body. + title=$(yq --front-matter=extract '.title' "${REPORT_FILE}") + desc=$(yq --front-matter=extract '.discord_description' "${REPORT_FILE}") + if [ -z "${desc}" ] || [ "${desc}" = "null" ]; then + echo "::error title=discord::${REPORT_FILE} has no front-matter discord_description to announce"; exit 1 + fi + + slug=$(basename "${REPORT_FILE}" .md | tr '[:upper:]' '[:lower:]') + url="https://go-openapi.github.io/doc-site/blog/monthly/${slug}/index.html" + + jq -n \ + --arg title "go-openapi & go-swagger — ${title} Monthly Report" \ + --arg url "${url}" \ + --arg desc "${desc}" \ + '{ + username: "go-openapi news", + content: "🗓️ **New monthly report is out!**", + allowed_mentions: { parse: [] }, + embeds: [ { title: $title, url: $url, color: 3447003, description: $desc } ] + }' > payload.json + + code=$(curl -sS -o resp.txt -w '%{http_code}' \ + -H "Content-Type: application/json" \ + -X POST --data @payload.json \ + "${DISCORD_WEBHOOK_URL}?wait=true") + echo "Discord HTTP ${code}" + cat resp.txt || true; echo + case "${code}" in + 2*) echo "::notice title=discord::Posted monthly announcement to Discord (HTTP ${code})" ;; + *) echo "::error title=discord::Discord webhook returned HTTP ${code}"; exit 1 ;; + esac diff --git a/docs/doc-site/blog/monthly/2026-05.md b/docs/doc-site/blog/monthly/2026-05.md new file mode 100644 index 0000000..1c5ef54 --- /dev/null +++ b/docs/doc-site/blog/monthly/2026-05.md @@ -0,0 +1,58 @@ +--- +title: May 2026 +description: go-openapi & go-swagger activity in May 2026 — runtime client diagnostics, go-swagger v0.34.0, and the swag API migration. +weight: 797394 +discord_description: |- + A busy month centred on **runtime** and a wave of releases: + + • **runtime** — new `Runtime.Trace` for connection-level diagnostics (incl. a TLS diagnostic mode), a build-tagged `SkipAuth` feature, a JSON-dialects fix, and context cancellation in multipart upload. Six releases cut (**v0.29.5 → v0.32.2**), plus a large documentation pass (media-types guide, keep-alive primer, per-module READMEs). + • **go-swagger v0.34.0** — generator migrated to the new **go-openapi/swag** API, more idiomatic context-aware clients, configurable custom producers/consumers, and an enum-constant fix (#1047). + • **codegen** also moved onto the new swag API. + • Org-wide: a shared **bot-PR monitoring** CI workflow rolled out across most repos. + + 258 commits across 18 repositories. Thanks to **@Abzaek**, **@KuaaMU** and **@dashitongzhi** for their contributions. +--- + +go-openapi and go-swagger had a busy May, with **258 commits across 18 repositories**. The +month's centre of gravity was **runtime**, which gained connection-level client diagnostics +and a documentation overhaul over the course of six releases, while **go-swagger** shipped +**v0.34.0** on the back of a migration to the new `go-openapi/swag` API. + +## Themes + +- **Client diagnostics in runtime.** A new `Runtime.Trace` exposes connection-level + diagnostics, including a TLS diagnostic mode, alongside a build-tagged `SkipAuth` feature + and a fix for honoring context cancellation in the multipart upload goroutine. +- **The swag API migration.** Both **go-swagger** and **codegen** migrated their generators + onto the new `go-openapi/swag` API; go-swagger's generated clients became more idiomatic + (context-aware `SubmitContext`, internal `Params` fields moved to their own struct). +- **A wave of releases.** runtime cut six releases (**v0.29.5 → v0.32.2**), go-swagger + reached **v0.34.0**, and analysis, jsonreference, strfmt and validate all tagged patch + releases; ci-workflows iterated through **v0.3.x**. +- **Documentation.** runtime received a large docs pass — a media-types selection guide, a + keep-alive primer, a constant-time-comparison contract for auth callbacks, and per-module + READMEs — and analysis clarified its mixin precedence rules. +- **CI maintenance, org-wide.** A shared **bot-PR monitoring** workflow was rolled out across + most repositories (runtime, analysis, validate, spec, swag, strfmt, …), with follow-up + fixes to bot identity, filters and permissions. + +## Repository highlights + +| Repository | Latest release | Highlights | +|---|---|---| +| runtime | v0.32.4 | `Runtime.Trace` connection diagnostics + TLS mode; build-tagged `SkipAuth`; JSON-dialects fix; context-cancelling multipart upload; large docs pass | +| go-swagger | v0.34.1 | v0.34.0 cut; generator on the new swag API; idiomatic context-aware clients; configurable custom producers/consumers; enum-constant fix (#1047) | +| codegen | — | Generator migrated to the new go-openapi/swag API | +| examples | v0.33.2 | Regenerated for the new swag methods; new middleware-composition example | +| analysis | v0.25.2 | Mixin precedence rules clarified and limitations documented | +| codescan | v0.34.1 | v0.34.0 released; consumed by go-swagger's `swagger generate spec` | + +## Thanks to our contributors + +A warm thank-you to the external contributors who landed changes this month: + +- **[@Abzaek](https://github.com/Abzaek)** — codegen, go-swagger +- **[@KuaaMU](https://github.com/KuaaMU)** — runtime +- **[@dashitongzhi](https://github.com/dashitongzhi)** — go-swagger + +Your contributions are genuinely appreciated.