# 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?