Skip to content

ci: adopt a staging branch as integration gate for all PRs with auto-release on main merge #44

Description

@martyy-code

Problem

Today, every PR — whether Dependabot or a developer contribution — is merged directly onto main. This creates friction:

  • Every merge triggers a CI run on main, even for WIP or draft contributions
  • Release cadence is decoupled from development — no consolidated view of what is about to ship
  • No integration step to catch cross-package conflicts before shipping
  • Developers must coordinate merges to avoid stepping on each other on main

Solution

Adopt a staging branch model for all contributions (flat):

  feat/xyz PR  ─┐
  fix/abc PR   ─┼──►  staging  ──►  main  (release)
  dep PRs      ─┘

All PRs (dev + Dependabot) target staging directly. main is never the target of a PR.

  • Staging is an accumulation and integration branch — never a working branch
  • Each dev opens one PR per feature/fix, targeting staging
  • Staging is merged into main when the team decides the train is ready

Changes required

.github/dependabot.yml:

target-branch: staging

.github/workflows/release-on-merge.yml — new file:

  • Trigger: push on main
  • Steps: checkout (full history), pnpm install, changesets/version + changesets/publish
  • OIDC/Trusted Publishing: permissions: { id-token: write } + NPM_CONFIG_PROVENANCE: true
  • Skip the merge commit itself (detect via github.event.head_commit.message)

Branch setup (manual, not in repo):

  • Create staging from main
  • Add branch protection on staging: PRs required, CI required, no force-push
  • Update branch protection on main: direct pushes disabled, only staging PR can merge
  • All existing contributors notified: PRs now target staging, not main

What stays the same

  • Major-version Dependabot PRs still require manual review before merge onto staging
  • group-by: dependency-name (from ci: deduplicate Dependabot PRs in monorepo via group-by #43) still applies
  • apps/web with private: true is never published
  • changesets/action v1.9.0 is still the production-safe version

Merge strategy on staging (Decision needed)

Option Behavior
Squash and merge One commit per PR lands on staging
Merge commit Full history preserved on staging

Recommendation: Squash and merge — keeps staging history clean and linear.

Hotfix path

If a hotfix must bypass the staging gate:

  1. Branch from main, fix, open PR to staging with label "hotfix"
  2. Fast-track CODEOWNERS review
  3. Merge staging -> main immediately
    Normal features always go through staging.

Acceptance Criteria

  • staging branch created from main
  • Branch protection on staging: PR required, CI required, no force-push
  • Branch protection on main updated: direct pushes disabled, PR from staging required
  • .github/dependabot.yml updated: target-branch: staging + group-by: dependency-name
  • .github/workflows/release-on-merge.yml created and functional
  • One full train cycle tested: PR -> staging -> main -> npm publish
  • CONTRIBUTING.md updated to reflect new branching model
  • OIDC / Trusted Publishing configured (drop NPM_TOKEN)
  • changesets/action pinned to v1.9.0 in the workflow
  • Merge strategy on staging decided (squash or merge commit)

Related

Risks

  • Medium: Process change for all contributors — requires communication and CONTRIBUTING.md update.
  • Low: Staging can drift from main if left unmerged too long. Mitigate with a scheduled sync or reminder.
  • Low: CI reliability on staging — if CI is flaky, the train stalls.

Metadata

Metadata

Assignees

Labels

area:ciWorkflows, dependabot, toolingdependenciesDependency updates (used by Dependabot)status:blockedBlocked by an external dependency (TS, upstream)

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions