Release Workflow Guide — middag-io
How releases work with release-please, from commit to production. Architecture decisions: see ADR-003.
Table of Contents
- 1. How It Works
- 2. Conventional Commits
- 3. The Release Flow
- 4. Version Annotations
- 5. CHANGELOG-USER.md (Commercial Products)
- 6. Post-Release Actions
- 7. Hotfix Flow
- 8. Common Scenarios
1. How It Works
release-please monitors main branch. When it detects conventional commits, it creates (or updates) a Release PR that bumps the version and updates CHANGELOG.md. Merging the Release PR creates a tag and GitHub Release.
develop ──PR──> main ──push──> release-please
│
Creates Release PR
(version bump + CHANGELOG)
│
Merge Release PR
│
┌─────────┴──────────┐
▼ ▼
GitHub Release Git tag (X.Y.Z)
(auto-created) │
▼
Post-release workflow
(build ZIP, deploy docs,
notify privatesatis)2. Conventional Commits
Every commit to main MUST follow Conventional Commits format:
| Prefix | Semver Impact | Example |
|---|---|---|
feat: | minor (0.X.0) | feat: add Stripe multi-entity support |
fix: | patch (0.0.X) | fix: ISSNet certificate expiry check |
feat!: or BREAKING CHANGE: | major (X.0.0) | feat!: drop PHP 8.1 support |
chore: | none (no release) | chore: update CI workflow |
docs: | none | docs: update API reference |
ci: | none | ci: add deploy step |
refactor: | none | refactor: extract email service |
test: | none | test: add unit tests for quotes |
Only feat: and fix: trigger a version bump. Other prefixes are recorded in CHANGELOG but don't create a release.
Scope (optional)
Use scope for specificity: feat(payments): add Stripe Connect support
3. The Release Flow
Step 1: Merge PR to main
Normal development: feature/xyz → develop → PR to main.
Step 2: release-please creates Release PR
After merge to main, release-please:
- Analyzes commit messages since last release
- Calculates next version (patch/minor/major)
- Creates or updates a Release PR with:
- Version bump in plugin/theme file (via
x-release-please-versionannotations) - Updated
CHANGELOG.md - Updated
.release-please-manifest.json
- Version bump in plugin/theme file (via
Step 3: Review and merge Release PR
- Review the version bump and changelog
- For commercial products: update
CHANGELOG-USER.mdbefore merging - Merge the Release PR
Step 4: Automatic post-release
Merging triggers:
- GitHub Release created with tag
- Post-release workflow runs:
- Build UI assets (if applicable)
- Run Strauss vendor prefixing (if applicable)
- Create distribution ZIP
- Upload ZIP to GitHub Release
- Trigger privatesatis rebuild
- Deploy docs to Cloudflare Pages (if applicable)
4. Version Annotations
release-please uses x-release-please-version annotations to find and update version strings.
WordPress Plugin
In {plugin-name}.php:
* Version: 5.0.16 // x-release-please-version
defined('MY_PLUGIN_VERSION') || define('MY_PLUGIN_VERSION', '5.0.16'); // x-release-please-versionWordPress Theme
In style.css:
Version: 5.1.0 x-release-please-versionIn functions.php:
define('MY_THEME_VERSION', '5.1.0'); // x-release-please-versionPHP Library
In composer.json — release-please handles this natively for simple release type via version.txt.
5. CHANGELOG-USER.md (Commercial Products)
Required for: wp-plugin-my-project, and any product with end-user docs.
Format
# Changelog
## [5.0.17] - 2026-05-16
### Novo
- Suporte multi-entidade Stripe — gerencie pagamentos BR e Global separadamente
- Integração de webhooks HubSpot para sincronização automática de contatos
### Melhorado
- Geração de PDF de faturas 3x mais rápida
### Corrigido
- Entrega de e-mail para endereços Gmail em alguns casos específicosRules
- Written in the product's user language (Portuguese for BR products)
- No technical jargon ("refactor", "dependency bump", "fix N+1")
- Grouped by: Novo / Melhorado / Corrigido / Removido
- Updated BEFORE merging the Release PR
- Public docs site pulls from this file
6. Post-Release Actions
What happens automatically after merge:
| Action | Trigger | Handler |
|---|---|---|
| GitHub Release + tag | release-please merge | release-please action |
| Build dist ZIP | release_created output | wp-plugin-post-release.yml |
| Upload ZIP to Release | after build | softprops/action-gh-release |
| Trigger privatesatis | after upload | repository_dispatch |
| Deploy docs | release_created output | docs-deploy.yml |
Manual intervention needed
CHANGELOG-USER.mdupdate (before merging Release PR)- Production deploy verification (after release)
- Backport to
developif hotfix (see section 7)
7. Hotfix Flow
For critical fixes that can't wait for the normal develop → main flow:
main ──branch──> hotfix/critical-fix
│
Fix + commit
(use feat: or fix: prefix)
│
PR to main
│
Merge → release-please creates Release PR
│
Merge Release PR → tag + release
│
Also merge hotfix into develop# Create hotfix
git checkout main
git pull origin main
git checkout -b hotfix/critical-fix
# Fix and commit
git commit -m "fix: critical payment processing error"
# PR to main
gh pr create --base main --title "fix: critical payment processing error"
# After merge + release: backport to develop
git checkout develop
git merge main
git push origin develop8. Common Scenarios
"I merged to main but no Release PR appeared"
- Check if commits use Conventional Commits format
chore:,ci:,docs:don't trigger releases- Need at least one
feat:orfix:commit
"I want to release but there are only chore commits"
Add an empty commit with a release-triggering prefix:
git commit --allow-empty -m "fix: trigger release for dependency updates""I want to skip a release for a specific commit"
Commits without feat:/fix: prefix don't trigger releases. Use chore: for non-release changes.
"I want to force a major version bump"
Use ! suffix or BREAKING CHANGE: footer:
feat!: drop PHP 8.1 support
BREAKING CHANGE: minimum PHP version is now 8.2"release-please manifest is out of sync"
Update .release-please-manifest.json manually:
{
".": "5.0.17"
}Commit and push to main.
"I need to release without going through develop"
Use the hotfix flow (section 7). Never commit directly to main.