Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .changeset/add-staging-changeset-lint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@deessejs/errors": minor
---

Add CI lint that requires a changeset on every PR to `staging`. Part of the release system plan (Phase 4).
5 changes: 5 additions & 0 deletions .changeset/docs-update-branching-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@deessejs/errors": patch
---

Update `CLAUDE.md` and `CONTRIBUTING.md` to reflect the actual branching model: devs land on `staging`, release engineer cherry-picks to `main` with a `version bump` label, hotfixes branch from `main`. The previous `main <- staging <- dev` model was documented but not practiced. Part of the release system plan (Phase 5).
5 changes: 5 additions & 0 deletions .changeset/release-workflow-rewrite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@deessejs/errors": patch
---

Rewrite the release workflow to detect pending changesets explicitly and gate all publish steps on detection. Tag is now pushed at the version bump commit (not the merge commit), fixing the `@deessejs/errors@1.1.1` tag drift. Adds `dry_run` and `packages` inputs to `workflow_dispatch`. Part of the release system plan (Phase 3).
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: ci

on:
pull_request:
branches: [staging]

jobs:
changeset-check:
name: Changeset required
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Fetch staging
run: git fetch origin staging
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Require changeset
run: |
changed=$(git diff --name-only origin/staging...HEAD)
if ! echo "$changed" | grep -q '^\.changeset/.*\.md$'; then
echo "::error::This PR must include a changeset file (.changeset/<slug>.md)."
exit 1
fi
- name: Validate changeset format
run: pnpm changeset status --since=origin/staging
43 changes: 38 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ name: Release

on:
workflow_dispatch:
inputs:
dry_run:
description: 'Skip publish and tag push'
type: boolean
default: false
packages:
description: 'Restrict to a subset of packages (comma-separated). Empty = all.'
type: string
default: ''

pull_request:
types:
Expand Down Expand Up @@ -41,33 +50,57 @@ jobs:
cache: 'pnpm'

- name: Install dependencies
run: pnpm install
run: pnpm install --frozen-lockfile

- name: Detect pending changesets
id: detect
run: |
if git diff --name-only HEAD~1 HEAD | grep -q '^\.changeset/.*\.md$'; then
echo "has_changesets=true" >> "$GITHUB_OUTPUT"
else
echo "has_changesets=false" >> "$GITHUB_OUTPUT"
fi

- name: Create versions from changesets
if: steps.detect.outputs.has_changesets == 'true'
run: pnpm changeset version

- name: Commit version changes and push
if: steps.detect.outputs.has_changesets == 'true' && (github.event_name == 'workflow_dispatch' || inputs.dry_run != true)
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "chore: apply changeset version bumps" || true
git push origin HEAD && git push --tags
git diff --quiet || git commit -m "chore(release): version packages"
git push origin HEAD
git push --tags

- name: Build
if: steps.detect.outputs.has_changesets == 'true' && (github.event_name == 'workflow_dispatch' || inputs.dry_run != true)
run: pnpm build

- name: Test
if: steps.detect.outputs.has_changesets == 'true' && (github.event_name == 'workflow_dispatch' || inputs.dry_run != true)
run: pnpm test

- name: Publish packages
run: pnpm changeset publish
if: steps.detect.outputs.has_changesets == 'true' && (github.event_name == 'workflow_dispatch' || inputs.dry_run != true)
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
if [ -n "${{ inputs.packages }}" ]; then
pnpm changeset publish --packages=$(echo "${{ inputs.packages }}" | tr ',' ' ')
else
pnpm changeset publish
fi

- name: Get latest tag
id: tag
run: echo "version=$(git describe --tags --abbrev=0)" >> $GITHUB_OUTPUT
if: steps.detect.outputs.has_changesets == 'true' && (github.event_name == 'workflow_dispatch' || inputs.dry_run != true)
run: echo "version=$(git describe --tags --abbrev=0)" >> "$GITHUB_OUTPUT"

- name: Create GitHub Release
if: steps.detect.outputs.has_changesets == 'true' && (github.event_name == 'workflow_dispatch' || inputs.dry_run != true)
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.version }}
Expand Down
16 changes: 11 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@ This is **`@deessejs/errors`**, a TypeScript library that reimagines error handl

## Branching Strategy

This project follows the branching model: `main` <- `staging` <- `dev`
This project uses a **staging-first** branching model. The convention is:

- **dev**: Latest work-in-progress changes. Developers work here.
- **staging**: Contains work that has been reviewed and is ready for release testing.
- **main**: Production-ready code. Contains the official release history.
- **`staging`** is the integration branch. Developers open their feature/fix/chore PRs targeting `staging`. Every PR to `staging` must include a `.changeset/*.md` file (enforced by the CI lint in `.github/workflows/ci.yml`).
- **`main`** is the release branch. The release engineer cherry-picks curated batches of commits from `staging` into a `release/*` branch, opens a release PR targeting `main`, and applies the `version bump` label. Merging a `version bump` PR triggers the release workflow: `pnpm changeset version`, then `pnpm changeset publish`, then push the `@deessejs/errors@X.Y.Z` tag.
- **`dev`** is **deprecated** and will be archived. It is not part of the current flow.

All developers push directly to `main`. The release engineer is responsible for managing the flow from `main` to `staging` and from `staging` to `main` (releases).
### Hotfix path

For urgent fixes that must skip the staging queue: branch from `main` as `release/hotfix-<slug>`, open a PR directly to `main` with a Changeset and the `[hotfix]` label, and merge. The release workflow fires on merge as for any other merge to `main`.

### Release cadence

Each merge of a `version bump` PR to `main` publishes one release per package that has pending changesets. Multiple changesets in a single merge become one version bump per affected package (Changesets default behavior).

## Web Search

Expand Down
22 changes: 18 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,25 @@ Thank you for your interest in contributing to this project!

## Branching Strategy

This project follows `main` <- `staging` <- `dev` branching:
This project uses a **staging-first** branching model:

- `main`: Production-ready code (all developers push here)
- `staging`: Release candidate testing
- `dev`: Work-in-progress development
- **`staging`** is the integration branch. Developers open their feature/fix/chore PRs targeting `staging`. Every PR to `staging` must include a `.changeset/*.md` file (enforced by the CI lint).
- **`main`** is the release branch. The release engineer cherry-picks curated batches from `staging` into a `release/*` branch, opens a release PR targeting `main`, and applies the `version bump` label. Merging a `version bump` PR triggers the release workflow.
- **`dev`** is deprecated and archived.

### Pull Requests

- **Target `staging`** for any feature, fix, refactor, or chore that should ship in a future release.
- **Include a Changeset** in your PR: `pnpm changeset` and commit the generated `.changeset/*.md` file. The CI lint blocks PRs that don't include one.
- **Allowed exemptions** to the changeset requirement: `docs:` only changes, `chore:` only changes, CI/workflow changes under `.github/`, and PRs labeled `no-changeset-required` by a maintainer.

### Hotfixes

For urgent fixes that must skip the staging queue: branch from `main` as `release/hotfix-<slug>`, open a PR directly to `main` with a Changeset and the `[hotfix]` label, and merge. The release workflow runs on merge as for any other merge to `main`.

### Release Engineer

Releases are managed by a single release engineer. The release engineer is the only person who cherry-picks commits from `staging` to `main` and applies the `version bump` label. There is no rotation. If the release engineer is unavailable, the team waits; bypassing the workflow is not a recommended escape hatch.

## Commit Messages

Expand Down
Loading