Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
name: Release

# Releases are triggered on a fixed schedule, not on every merge.
# This separates "code merged to main" from "version published" — merging
# is still cheap and frequent, releasing is a deliberate, reviewable event.
#
# Triggers:
# - schedule: nightly at 04:00 UTC. semantic-release inspects commits
# since the last tag and either publishes a release or skips silently.
# - workflow_dispatch: manual trigger from the Actions tab when a
# release should happen ahead of schedule.
#
# To skip a scheduled release on a given day, push commits with types that
# don't bump the version (chore, docs, refactor, test, ci, style, build).
on:
push:
branches: [main]
schedule:
- cron: '0 4 * * *' # daily at 04:00 UTC
workflow_dispatch:
inputs:
dry-run:
description: "Dry run — analyse commits but don't publish"
required: false
default: false
type: boolean
Comment on lines 21 to +30

permissions:
contents: write # create tags + GitHub Releases
Expand Down Expand Up @@ -38,6 +56,7 @@ jobs:
id: semantic
uses: cycjimmy/semantic-release-action@v6
with:
dry_run: ${{ inputs.dry-run || false }}
extra_plugins: |
@semantic-release/changelog@6
@semantic-release/git@10
Expand Down
14 changes: 11 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,18 @@ The CI workflow runs the same. PRs that fail any check won't be reviewed until g

## Releases

Releases are **fully automated** via semantic-release on push to `main`:
- Conventional commit messages → version bump + changelog → tag → GitHub Release → Docker images on GHCR + Docker Hub.
Releases are **scheduled, not on every merge.** Merging to `main` does NOT publish a release — it only bundles changes for the next scheduled release window.

Maintainers do not manually tag releases.
| Trigger | When |
|---|---|
| `cron: '0 4 * * *'` | Nightly at 04:00 UTC. semantic-release analyses commits since the last tag and publishes if there's anything releasable, otherwise skips silently |
| `workflow_dispatch` | Manual button in the Actions tab. Optional `dry-run` input lets a maintainer preview what would be released without publishing |

Behind the scenes, semantic-release reads the Conventional Commit messages on `main` since the previous tag, decides the next semver version, generates the `CHANGELOG.md` entry, creates the Git tag and GitHub Release, and triggers the Docker image push to GHCR and Docker Hub.

**To skip a scheduled release on a given day**, push only commits with types that don't bump the version: `chore`, `docs`, `refactor`, `test`, `ci`, `style`, `build`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The instruction suggesting that pushing a non-bumping commit will 'skip' a release is technically inaccurate. semantic-release evaluates the entire history since the last tag. If a version-bumping commit (like feat or fix) is already on main, pushing a chore commit afterwards will not prevent the scheduled release. A release is only skipped if no bumping commits exist in the history since the previous release.

Suggested change
**To skip a scheduled release on a given day**, push only commits with types that don't bump the version: `chore`, `docs`, `refactor`, `test`, `ci`, `style`, `build`.
A release is only published if there are commits with types that trigger a version bump (feat, fix, perf) since the last tag. If only non-bumping types (chore, docs, refactor, test, ci, style, build) are merged, the scheduled run will skip publishing.


Maintainers do not manually tag releases. If a hotfix needs to ship before the next scheduled window, use the **Run workflow** button on the Release workflow in the Actions tab.

## Trademarks

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,19 @@ We adopt **[Conventional Commits 1.0.0](https://www.conventionalcommits.org/)**

Allowed scopes: `printer-models`, `queue`, `status`, `api`, `ui`, `webhook`, `docker`, `ci`, `examples`, `docs`, `integration`, `pwa`, `security`, `release`. Validated by `commitlint` on PR titles.

**Release trigger:** push to `main`. Maintainers do not manually tag releases.
**Release trigger:** **scheduled (nightly 04:00 UTC) + manual via `workflow_dispatch`**. Merges to `main` do NOT publish releases — they only accumulate changes for the next release window. This separation means merging stays cheap and frequent, while releasing remains a deliberate event the maintainer can review or postpone.

Maintainers do not manually tag releases. To trigger an unscheduled release (e.g. for a security hotfix), use the **Run workflow** button on the Release workflow.

**Release pipeline:**
1. Push to `main`
2. `release.yml` workflow runs `semantic-release`
3. semantic-release inspects commits since last tag → decides version bump
4. Updates `CHANGELOG.md`, creates Git tag, creates GitHub Release
1. Schedule fires at 04:00 UTC (or maintainer hits "Run workflow")
2. `release.yml` workflow runs `semantic-release` against `main` HEAD
3. semantic-release inspects commits since last tag → decides version bump (or skips if nothing releasable)
4. If a release is due: updates `CHANGELOG.md`, creates Git tag, creates GitHub Release
5. Release-published event triggers `docker-publish.yml` → builds and pushes both backend and frontend images per ADR 0007

To skip a release on a given day, push only commit types that don't bump the version (`chore`, `docs`, `refactor`, `test`, `ci`, `style`, `build`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This sentence implies that a release can be suppressed by pushing a non-bumping commit. In reality, semantic-release will trigger a release if any feat, fix, or perf commits are present since the last tag. It is more accurate to state that a release only occurs when releasable changes are present in the history.

Suggested change
To skip a release on a given day, push only commit types that don't bump the version (`chore`, `docs`, `refactor`, `test`, `ci`, `style`, `build`).
A release is only published if there are commits with types that trigger a version bump (feat, fix, perf) since the last tag. If only non-bumping types (chore, docs, refactor, test, ci, style, build) are merged, the scheduled run skips publishing.


## Options considered

### Option A — Conventional Commits + semantic-release (chosen)
Expand Down
Loading