diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 66aedcec..87ee9df1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -146,10 +146,9 @@ picpeak/ ## ๐Ÿ”„ Release Process -1. Update version numbers in package.json files -2. Update CHANGELOG.md -3. Create a new release on GitHub -4. Docker images are automatically built and published +Releases are cut from the `beta` branch (rolling beta) and promoted to `main` (stable) on a 4โ€“6 week cadence. `release-please` handles version bumps, changelog generation, and Docker image publication automatically โ€” contributors don't update `package.json` or `CHANGELOG.md` by hand. + +See [RELEASING.md](RELEASING.md) for the full operational doc (promotion criteria, conflict-resolution checklist for the betaโ†’main merge, hotfix backport path, versioning rules). ## ๐Ÿ“ฎ Contact diff --git a/README.md b/README.md index b8c9b78e..efea2a32 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ Note on Docker file permissions ## ๐Ÿ”„ Release Channels -PicPeak offers two release channels for different needs: +PicPeak offers two release channels for different needs. Stable promotions are cut from a known-good beta point every 4โ€“6 weeks โ€” see [RELEASING.md](RELEASING.md) for the maintainer's promotion criteria and cadence policy. ### Stable Channel (Recommended) - Production-ready releases diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 00000000..2f1782fe --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,92 @@ +# Release Process + +This document describes how PicPeak releases are cut. It's the maintainer's reference, not user documentation โ€” for the user-facing channel choice (stable vs beta) see the [Release Channels section in README.md](README.md#-release-channels). + +## TL;DR + +- **`beta` branch** receives all merged work. Every push triggers a `release-please` PR that proposes the next `vX.Y.Z-beta.N` release. Merging that PR tags the beta and publishes Docker images on the `beta` tag. +- **`main` branch** holds the stable channel. Stable releases are cut from a known-good `beta` point via a `release/X.Y.Z-merge-from-beta` branch and a manual PR to `main`. Merging that PR triggers `release-please` to propose the stable release. +- Target cadence: **a stable release every 4โ€“6 weeks**, or sooner if a beta has been quiet and ready for promotion. + +## Cadence target + +4โ€“6 weeks between stable releases is the working target. Reasoning: + +- Long enough that each stable carries meaningful changes worth the upgrade burden. +- Short enough that beta users aren't carrying the "real" project alone for months โ€” the stable channel should actually be usable as the recommended channel for new installs. +- Aligns with how release-please surfaces beta releases (multiple beta points usually accumulate inside a 4โ€“6 week window, which gives natural promotion candidates). + +This is a target, not a hard rule. Cut sooner if a beta has been quiet and stable longer than usual. Cut later if a beta is in flux for security or migration reasons. + +## Promotion criteria + +A beta is eligible for promotion to stable when **all** of the following hold: + +1. **CI green on the candidate beta tip.** Specifically: `schema-drift` (`upgrade-from-bootstrap`), `fresh-install`, `Tests` (backend Jest + frontend Vitest), the four `Build and Push Docker Images` arch matrices, and `GitGuardian Security Checks`. +2. **No open `bug`-labelled issues against the candidate beta for at least 7 days.** Issues fixed-but-not-yet-closed count as fixed; verify their PR is in the candidate beta before closing them out. +3. **An upgrade walk has been done on real production-shaped data** โ€” apply the candidate's migration chain to a snapshot of the previous stable's DB and verify no manual intervention is required. CI proves fresh-install works; the upgrade walk is what proves the upgrade path works. +4. **Operator-time smoke** on the candidate: log in, create event, upload photos, share gallery, open as a customer, log out. Catches binary-incompatibility regressions and UI-level breaks that unit tests don't see. + +If any of the four fail, the promotion waits. File any blockers as `bug`-labelled issues and let them bake on beta before re-evaluating. + +## How a stable release is cut + +The actual mechanics, in order: + +1. **Pick the beta tip.** Confirm it satisfies the four promotion criteria above. Note the exact SHA โ€” that's what you're promoting. + +2. **Create the release branch from the beta tip.** + ```bash + git push origin :refs/heads/release/X.Y.Z-merge-from-beta + ``` + Naming convention: `release/X.Y.Z-merge-from-beta`, where `X.Y.Z` is the stable version you intend to land. release-please will write the actual `X.Y.Z` on merge โ€” the branch name is just a human label. + +3. **Open a PR to `main`.** Title: `chore(release): promote beta โ†’ main as vX.Y.Z`. Body should summarise the major themes since the previous stable, the migration count, and any operator notes (e.g. "this release adds 22 migrations; existing installs should snapshot before upgrading"). See PR #568 as a worked example. + +4. **Resolve conflicts.** Main almost always has commits beta doesn't (security backports, release-please's stable-channel release commits, README rewrites). For each conflicting file, decide deliberately: + - **`backend/package.json` / `package-lock.json` + `frontend/package.json` / `package-lock.json`** โ€” usually take beta's version (superset), but verify any security-pinned deps (`axios`, `nodemailer`, `i18next-http-backend`, `multer`, `tar`) on beta are `>=` the pinned versions on main. If main has a newer pinned version (e.g. an emergency CVE backport beta hasn't picked up), take main's pin. + - **`README.md`** โ€” keep main's version if main has had a recent rewrite that beta didn't pick up; otherwise take beta's. + - **`CHANGELOG.md`** โ€” keep main's; release-please regenerates entries on its next stable cut from the commits going forward. + - **`.release-please-manifest.json`** โ€” keep main's; release-please owns this file. + - Any other auto-merged file โ€” spot-check that the auto-merge produced something sensible, especially for security-sensitive files (`backend/src/middleware/`, `backend/src/utils/tokenUtils.js`). + +5. **Wait for CI on the PR.** All ten checks (the original eight plus `merge-backend` and `merge-frontend`) must be green. If anything fails, fix on the release branch (NOT on beta โ€” beta has already moved on). + +6. **Merge.** Standard merge commit, not squash โ€” the PR's history (the individual feature commits) carries forward into main's log. + +7. **release-please picks it up.** Within minutes, release-please will open a new `chore(main): release X.Y.Z` PR proposing the stable release. Review the auto-generated CHANGELOG.md entries for accuracy, edit if needed, and merge. That merge creates the `vX.Y.Z` git tag, publishes Docker images on the `stable` and `latest` tags, and creates the GitHub Release page. + +8. **Close the loop.** Bulk-close any `bug` issues that were fixed-but-not-closed and now appear in the released changelog. Reference the merge commit so reporters know which version contains the fix. + +## Hotfix path (backport to current stable) + +If a critical bug or security issue affects the current stable and beta has moved too far for a full promotion to be appropriate, backport just the fix: + +1. Create a `security/cve-backport-X.Y.Z` or `fix/critical-X.Y.Z` branch off `main`. +2. Cherry-pick or hand-write the minimal fix. +3. Open a PR to `main` with the smallest possible diff. +4. After merge, release-please will propose a patch-level stable release (e.g. `v3.55.1`). +5. **Forward-port the fix to beta** if it isn't already there. Otherwise the next full promotion will reintroduce the bug. + +PR #412 ("backport 18 dependency CVE patches from beta") is a worked example of this path. + +## Versioning + +PicPeak follows [Semantic Versioning](https://semver.org/) with one project-specific convention: + +- **MAJOR** bumps are reserved for breaking schema changes that require operator action on upgrade (e.g. a migration that's not safe to auto-apply, an env-var rename that can't be auto-detected). +- **MINOR** bumps for new features, additive schema changes, and any change to the public HTTP API surface. +- **PATCH** bumps for bug fixes and operator-invisible internal changes. +- **Beta suffix** (`-beta.N`) for every beta cut; the `N` counter resets on each new MINOR or MAJOR target. + +release-please derives all of this from conventional commit prefixes (`feat:`, `fix:`, `BREAKING CHANGE:`, etc.) automatically. + +## Things that don't go through this process + +- **Documentation-only changes** can land on either `main` or `beta` directly (no release cut needed); release-please will pick them up on the next regular release. +- **Test-only changes** โ€” same. +- **CI / workflow changes** โ€” same, but be aware they take effect on the branch they land on, so a CI fix targeting beta won't fix a broken stable-channel workflow until the next promotion. + +## When this doc is wrong + +If you find yourself working around something here, update the doc before doing the workaround. The point of a written process is that future-you doesn't have to remember the workaround.