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
mainmerge (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
- Tool: 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. - 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).
- Versioning: SemVer, single version for the whole app.
feat→ minor,fix→ patch,feat!/BREAKING CHANGE→ major. One version tagvX.Y.Zcovers the Dockerized app (api + dashboard + jobs + packages). - Side packages stay independently published.
@classroomio/mcp(publish-mcp.yml) and@classroomio/course-app(publish-course-app.yml) keep their own manualworkflow_dispatchpublish 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.) - Docker images reuse the existing tag trigger.
docker-publish.ymlalready fires onpush: tags: v*; a release-please tag builds the versioned image automatically. No new build pipeline. - 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; permissionscontents: 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— removestandard-versionfromdevDependenciesand remove the"release": "standard-version"script. Reconcileversionto 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 arelease: [published]trigger as a belt-and-suspenders path.
Risks / gotchas
GITHUB_TOKENdoes not trigger downstream workflows. A tag pushed by release-please using the defaultGITHUB_TOKENwill not firedocker-publish.yml'stags: v*trigger. Resolve by giving release-please a GitHub App token / PAT, or adding arelease: [published]trigger todocker-publish.yml. Must be decided before rollout or releases won't build images.- Version reconciliation. The manifest seed must match reality or the first
release computes a wrong next version. Pick
v1.0.0(latest tag) vs0.1.13(package.json) up front. - 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-appinto release-please as independently-versioned components (would move us toward a manifest with multiple packages). - Pre-release / release-candidate channel (e.g.
nexttag) before promoting to stable.
Open questions
- Manifest seed version — start from
v1.0.0(latest git tag, recommended) or0.1.13(currentpackage.json)? - Downstream-trigger fix — GitHub App token/PAT for release-please, or add
release: [published]todocker-publish.yml? (Recommend the App token so the existing tag trigger keeps working unchanged.) - Cadence — weekly (recommended) or biweekly Release-PR merge?