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
59 changes: 33 additions & 26 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,39 @@ Standardized release workflow for @stackbox-dev/fp-plugins.

## Workflow

1. **Pre-flight checks**
- Ensure working tree is clean (`git status`)
- Ensure on `main` branch
- Run `pnpm test` to verify all tests pass
- Run `pnpm run build` to verify build succeeds

2. **Version bump**
- Ask user for bump type: patch, minor, or major
- Update `version` in `package.json`
- Create a commit with message: `X.Y.Z` (version number only, matching existing convention)

3. **Verify**
- Show the user the version diff and commit
- Remind the user that publishing is triggered by **creating a GitHub Release**,
not by pushing. `npm-publish-github-packages.yml` runs `on: release: [created]`;
a push to `main` alone publishes nothing.
1. **Pre-flight**
- Working tree clean, on `main`, up to date
- `pnpm test` and `pnpm run build` pass

2. **Cut the version on a branch** — `main` requires a PR, so this cannot be pushed
directly:

```bash
git checkout -b release/X.Y.Z
pnpm version <patch|minor|major> # commit "X.Y.Z" + local tag vX.Y.Z
git push -u origin release/X.Y.Z # branch only, not the tag
```

Ask the user for the bump type. `pnpm version` writes a commit whose message is
just the version, matching existing history.

3. **Open the PR**, titled with the version (see `release/2.14.0`, PR #4).

4. **Merging is the release.** `.github/workflows/release.yml` runs on every push to
`main`: if `package.json`'s version has no matching `vX.Y.Z` tag, it runs the full
gate (lint, build, unit tests, MinIO integration), then tags, creates the GitHub
Release and publishes to GitHub Packages. Nothing else is required.

## Notes

- The version bump belongs on `main` and nowhere else. Never put one in a feature or
fix PR — those PRs describe changes, releases decide versions. A bump riding along
in a feature branch also makes the PR harder to review and forces a rebuild if the
release is deferred.
- Do NOT push automatically — let the user decide when to push
- Do NOT create git tags by hand. Creating the GitHub Release creates the tag.
- Follow existing commit message convention (see `git log` — version bumps use just the version number like `2.12.0`)
- `main` is protected by a ruleset requiring one approving review, code-owner review,
and `require_last_push_approval` — so any push after an approval dismisses it. Get
the branch final before asking for review.
- **Do not push the tag by hand.** The workflow creates it. `pnpm version` makes one
locally; delete it (`git tag -d vX.Y.Z`) or just leave it unpushed.
- **Do not create the GitHub Release by hand.** The workflow does that too, with
generated notes.
- A version bump belongs on `main` and nowhere else — never in a feature or fix PR.
- Pushes to `main` whose version is already tagged are a no-op, so ordinary merges do
not publish.
- A failed run can be retried with `workflow_dispatch` without bumping the version
again; every step is idempotent.
- `main` requires one approving review and sets `require_last_push_approval`, so any
push after an approval dismisses it. Get the branch final before requesting review.
66 changes: 0 additions & 66 deletions .github/workflows/npm-publish-github-packages.yml

This file was deleted.

122 changes: 122 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Merging a version bump to main is the whole release. This workflow tags, creates the
# GitHub Release and publishes to GitHub Packages.
#
# Why one workflow instead of "create a release, let release:published publish it":
# a release created with GITHUB_TOKEN does not trigger further workflow runs — GitHub
# blocks that to avoid recursion — so the publish would silently never happen.
#
# Every step is idempotent, so a failed run can be retried with workflow_dispatch
# without bumping the version again.

name: Release

on:
push:
branches: [main]
workflow_dispatch:

concurrency:
group: release
cancel-in-progress: false

env:
NODE_VERSION: 24

jobs:
check:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.check.outputs.version }}
release: ${{ steps.check.outputs.release }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- id: check
run: |
VERSION="$(node -p 'require("./package.json").version')"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "release=true" >> "$GITHUB_OUTPUT"
echo "manual run: releasing v$VERSION"
elif git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null; then
echo "release=false" >> "$GITHUB_OUTPUT"
echo "v$VERSION is already tagged — nothing to release"
else
echo "release=true" >> "$GITHUB_OUTPUT"
echo "v$VERSION is new — releasing"
fi

release:
needs: check
if: needs.check.outputs.release == 'true'
runs-on: ubuntu-latest
permissions:
contents: write # create the tag and the release
packages: write # publish
env:
VERSION: ${{ needs.check.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
registry-url: https://npm.pkg.github.com/
- run: pnpm install --frozen-lockfile

# Publishing is irreversible, so the full gate runs first.
- run: pnpm run lint
- run: pnpm run build
- run: pnpm test

- name: Start MinIO
run: |
docker run -d --name release-minio -p 9000:9000 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
minio/minio:latest server /data
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:9000/minio/health/live; then
echo "minio ready"; exit 0
fi
sleep 2
done
echo "minio did not become healthy" >&2
exit 1
- run: pnpm run test:integration
env:
MINIO_TEST_ENDPOINT: http://127.0.0.1:9000
- name: Stop MinIO
if: always()
run: docker rm -f release-minio || true

- name: Tag
run: |
if git rev-parse -q --verify "refs/tags/v$VERSION" >/dev/null; then
echo "tag v$VERSION already exists"
else
git tag "v$VERSION"
git push origin "v$VERSION"
fi

- name: GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if gh release view "v$VERSION" >/dev/null 2>&1; then
echo "release v$VERSION already exists"
else
gh release create "v$VERSION" --title "v$VERSION" --generate-notes
fi

# npm publish, not pnpm publish: pnpm adds git-state checks this flow does not
# need. prepublishOnly rebuilds. No --provenance: GitHub Packages rejects
# attestations. A duplicate version 409s, which is the desired failure.
- name: Publish
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npm publish
7 changes: 4 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ the issue thread or the PR that resolves it.
- **`tsconfig.build.json` sets `skipLibCheck: true`** — still required. Turning it off
fails on `thread-stream@4.2.0` (a pino dependency), whose `.d.ts` references
`TransferListItem`, a name its `worker_threads` types do not export.
- **Releasing**: version bumps happen on `main`, in their own commit named just the
version (`2.16.0`). Do not put a version bump in a feature PR and do not create
tags by hand — publishing is triggered by creating a GitHub Release. See
- **Releasing**: bump the version with `pnpm version` on a `release/X.Y.Z` branch and
merge it. `.github/workflows/release.yml` does the rest — tag, GitHub Release,
publish — on any push to `main` whose version has no matching tag. Do not push tags
or create releases by hand, and never put a version bump in a feature PR. See
`.claude/skills/release/SKILL.md`.
21 changes: 13 additions & 8 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,19 @@ updated lockfile.

## Releases

**Do not put a version bump in a feature or fix PR**, and do not create tags by hand.
Cut the version on a branch — `main` requires a PR:

Releasing is a separate act on `main`:
```bash
git checkout -b release/X.Y.Z
pnpm version <patch|minor|major>
git push -u origin release/X.Y.Z # branch only; do not push the tag
```

Open a PR titled with the version. **Merging it is the release**: the `Release`
workflow tags, creates the GitHub Release and publishes to GitHub Packages, after
running lint, build, unit tests and the MinIO integration suite.

1. Bump `version` in `package.json` in its own commit, whose message is just the
version number (`2.16.0`), matching existing history.
2. Create a **GitHub Release**. That is what creates the tag and triggers
`npm-publish-github-packages.yml` to publish. Pushing to `main` alone publishes
nothing.
Do not push tags or create releases by hand. Merges whose version is already tagged
are a no-op, so normal PRs do not publish.

See `.claude/skills/release/SKILL.md` for the full checklist.
See `.claude/skills/release/SKILL.md`.
Loading