From 55e6ddd37e8c7928b0cea2507207405f00d5d92e Mon Sep 17 00:00:00 2001 From: Rotimi Best Date: Mon, 20 Jul 2026 01:21:38 +0100 Subject: [PATCH] docs(prd): add automated release process PRD --- PRD-TRACKER.md | 1 + prd/release-process/README.md | 136 ++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 prd/release-process/README.md diff --git a/PRD-TRACKER.md b/PRD-TRACKER.md index b0c3a2cb..991696b8 100644 --- a/PRD-TRACKER.md +++ b/PRD-TRACKER.md @@ -26,6 +26,7 @@ Use this file to keep a single prioritized view of all PRDs. | - | - | `prd/reset-student-course-progress` | todo | | - | 15 | `prd/webhooks` | todo | | - | - | `prd/zapier` | draft | +| - | - | `prd/release-process` | draft | ## DONE (Completed PRDs) diff --git a/prd/release-process/README.md b/prd/release-process/README.md new file mode 100644 index 00000000..c15eb84a --- /dev/null +++ b/prd/release-process/README.md @@ -0,0 +1,136 @@ +# Automated Release Process & Changelog PRD + +## Purpose + +Give ClassroomIO a real **release** mechanism — versioned, tagged, changelog'd +snapshots that self-hosters can pin to and read about — on top of the existing +continuous **deploy** to production. + +Today `main` merges deploy to prod continuously (CI builds Docker images), but +there is no reliable release: the `release` script runs the **deprecated** +`standard-version` manually and has drifted (`package.json` says `0.1.13`, the +latest git tag is `v1.0.0`, and `CHANGELOG.md` only reflects `0.1.x`). This PRD +replaces that manual step with **release-please**, which prepares a versioned +release continuously and lets a maintainer cut it on a cadence with one merge. + +## The governing distinction + +> **Deploy ≠ Release.** A deploy is "the code running on our prod server" and +> stays continuous/automatic on every `main` merge (unchanged by this PRD). A +> release is a named, tagged, changelog'd snapshot for the **community** to +> self-host and pin to. This PRD only adds the release track; it does not touch +> how prod deploys. + +## Confirmed decisions + +1. **Tool: [release-please](https://github.com/googleapis/release-please) + (v4 action).** GitHub-native, reads Conventional Commits (already enforced), + maintains a permanent "Release PR." This is the maintainer-recommended + successor to `standard-version`. +2. **Model: cadence-by-habit, not release-on-every-merge and not cron.** + release-please keeps a Release PR always up to date; a maintainer merges it on + a chosen rhythm (target: **weekly**). Merging *work* PRs never releases; + merging the *Release PR* is the one and only release trigger. This mirrors how + cal.com operates (human-triggered, machine-prepared). +3. **Versioning: SemVer, single version for the whole app.** `feat` → minor, + `fix` → patch, `feat!`/`BREAKING CHANGE` → major. One version tag `vX.Y.Z` + covers the Dockerized app (api + dashboard + jobs + packages). +4. **Side packages stay independently published.** `@classroomio/mcp` + (`publish-mcp.yml`) and `@classroomio/course-app` (`publish-course-app.yml`) + keep their own manual `workflow_dispatch` publish flows. release-please does + **not** version or publish them. (Not Changesets — that only pays off with + many independently-versioned npm packages; we ship one app.) +5. **Docker images reuse the existing tag trigger.** `docker-publish.yml` already + fires on `push: tags: v*`; a release-please tag builds the versioned image + automatically. No new build pipeline. +6. **Retire `standard-version`.** Remove the dep and the `"release"` script. + +## Current state audit + +| Capability | Status | Location | +| --- | --- | --- | +| Continuous deploy on `main` | works | `docker-publish.yml` (`push: branches: main`, path-filtered) | +| Docker build on version tag | works | `docker-publish.yml` (`push: tags: v*`) + `workflow_dispatch` | +| Conventional Commits enforced | works (PR-title lint) | `semantic-pull-requests.yml` (`amannn/action-semantic-pull-request@v5`) | +| Squash-merge → conventional commit on `main` | works | commit history (`feat(...): ... (#NNN)`) | +| Changelog generation | **deprecated + drifted** | `standard-version` dep + `"release": "standard-version"`; `CHANGELOG.md` stale at `0.1.x` | +| Version source of truth | **inconsistent** | `package.json` `0.1.13` vs latest git tag `v1.0.0` | +| GitHub Releases | none/ad hoc | — | +| Side-package publish | manual, independent | `publish-mcp.yml`, `publish-course-app.yml` (`workflow_dispatch`) | + +## Target flow + +``` +Mon–Thu merge work PRs → prod deploys continuously (unchanged) + release-please keeps the "Release PR" (version + CHANGELOG) up to date +Friday maintainer skims the Release PR changelog → merges it + → tag vX.Y.Z + GitHub Release + CHANGELOG.md committed + → docker-publish.yml builds classroomio:vX.Y.Z automatically +``` + +## Backend / infra changes + +### New files + +- **`.github/workflows/release-please.yml`** — `on: push: branches: [main]`; + `googleapis/release-please-action@v4`; permissions `contents: write`, + `pull-requests: write`. +- **`release-please-config.json`** — single root package: + `{ "packages": { ".": { "release-type": "node", "changelog-path": "CHANGELOG.md", "bump-minor-pre-major": true } } }`. +- **`release-please-manifest.json`** — seed with the agreed current version + (see Open questions): `{ ".": "1.0.0" }`. + +### Changes + +- **`package.json`** — remove `standard-version` from `devDependencies` and + remove the `"release": "standard-version"` script. Reconcile `version` to the + manifest seed so it stops drifting. +- **`docker-publish.yml`** — ensure the tag-triggered build reliably fires for + release-please tags (see Risk below); optionally add a `release: [published]` + trigger as a belt-and-suspenders path. + +## Risks / gotchas + +1. **`GITHUB_TOKEN` does not trigger downstream workflows.** A tag pushed by + release-please using the default `GITHUB_TOKEN` will **not** fire + `docker-publish.yml`'s `tags: v*` trigger. Resolve by giving release-please a + **GitHub App token / PAT**, **or** adding a `release: [published]` trigger to + `docker-publish.yml`. Must be decided before rollout or releases won't build + images. +2. **Version reconciliation.** The manifest seed must match reality or the first + release computes a wrong next version. Pick `v1.0.0` (latest tag) vs `0.1.13` + (package.json) up front. +3. **Unattended cron rejected.** Fully-automated scheduled publishing was + considered and rejected: a bad image would reach real self-hosted deployments + with unreviewed notes. Cadence stays a deliberate human merge. + +## How modern OSS projects informed this + +- **cal.com** — SemVer, ~monthly headline releases + patches; machine-generated + draft release notes, **human triggers and reviews** before publish; Docker + builds on the `v*` tag. (This PRD automates one step further: the version is + computed, not hand-typed.) +- **Polar** — Changesets, per-package SemVer, frequent alpha/beta releases — + fits their **many published SDKs**, which is why we don't adopt it. +- **Supabase** — two-track: continuous per-repo releases **plus** a curated + quarterly marketing changelog. Track 2 is out of scope here (see below). + +## Out of scope (possible follow-ups) + +- **Curated end-user changelog** (a cal.com/Supabase-style blog or docs "What's + new in vX" page) written for humans, batched at a slower cadence. Track 1 + (GitHub Releases + `CHANGELOG.md`) ships first; this is Track 2. +- Bringing `@classroomio/mcp` / `@classroomio/course-app` into release-please as + independently-versioned components (would move us toward a manifest with + multiple packages). +- Pre-release / release-candidate channel (e.g. `next` tag) before promoting to + stable. + +## Open questions + +1. **Manifest seed version** — start from `v1.0.0` (latest git tag, + recommended) or `0.1.13` (current `package.json`)? +2. **Downstream-trigger fix** — GitHub App token/PAT for release-please, or add + `release: [published]` to `docker-publish.yml`? (Recommend the App token so + the existing tag trigger keeps working unchanged.) +3. **Cadence** — weekly (recommended) or biweekly Release-PR merge?