Release Process
@franksauvag/rpg-commons uses semantic-release to automate versioning, tagging, changelog generation, and publishing to GitHub Packages.
How it works
Every push to main triggers the Release workflow. If the CI gate passes (lint, typecheck, test, build), semantic-release analyzes commits since the last release and decides whether a new version is warranted.
push to main
│
▼
┌─────────────────────────────────┐
│ CI gate │
│ lint → typecheck → test → build│
└────────────────┬────────────────┘
│ passes
▼
┌─────────────────────────────────┐
│ semantic-release │
│ analyze commits │
│ → bump version (X.Y.Z) │
│ → update CHANGELOG.md │
│ → commit + tag vX.Y.Z │
│ → GitHub Release │
│ → pnpm publish → npm.pkg.github│
└─────────────────────────────────┘If no commit since the last release is "releasable" (e.g. only chore: or docs:), semantic-release does nothing — no new version is published.
Conventional Commits
All commits must follow the Conventional Commits specification. This is what drives automatic versioning.
Format
<type>(<scope>): <description>
[optional body]
[optional footer(s)]Version bump rules
| Commit type | Example | Version bump |
|---|---|---|
fix: | fix(media): handle null apiBaseUrl | Patch 0.0.X |
perf: | perf(srd): cache bundle responses | Patch 0.0.X |
feat: | feat(srd): add buildSpellsBundle | Minor 0.X.0 |
feat!: or BREAKING CHANGE: | feat!: rename SrdClient.fetch | Major X.0.0 |
chore:, docs:, refactor:, style:, test:, build:, ci: | — | No release |
Breaking change syntax
feat: rename createSrdClient config key
BREAKING CHANGE: `baseUrl` is now `apiBaseUrl` — update all call sites.Or using the shorthand !:
feat!: rename createSrdClient config keyExamples
# Patch release (0.0.X)
fix(srd): prevent infinite retry on 401 responses
perf(components): memoize SelectionCard render
# Minor release (0.X.0)
feat(ui): add TooltipButton component
feat(srd): expose fetchDataset in SrdClient public API
# Major release (X.0.0)
feat!: drop support for React 18 — require React 19+
feat(srd): remove resolveEntryMediaSrc
BREAKING CHANGE: resolveEntryMediaSrc has been removed.
Use resolveSrdMediaUrl directly.
# No release (chore/docs/refactor)
chore: update pnpm to 10.12.1
docs(srd): add fetchDataset example to srd-data-api.md
refactor(normalizers): extract costToGp helper
test(srd): add missing edge cases for toSpellsBundleTwo publication channels
| Channel | Trigger (on main) | Artifact | Audience |
|---|---|---|---|
| npm package | release job after ci | @franksauvag/rpg-commons@X.Y.Z on GitHub Packages | Consumer apps |
| Documentation site | deploy-docs after ci + docs | HTML at libs.heritiersdudonjon.com | Humans + agents (browser) |
A docs: commit can update the site without a new npm version. A feat: triggers a minor npm release when merged to main; the doc site redeploys on every green main build regardless.
CI jobs
| Job | Runs on | Role |
|---|---|---|
ci | PR + main | lint, typecheck, test, pnpm build (library) |
release | main only | semantic-release → npm, tag, CHANGELOG |
docs | PR + main | pnpm docs:build (VitePress + TypeDoc + Storybook) |
deploy-docs | main only | FTP deploy to OVH /www-libs/ |
push to main
├─► ci ──┬─► release ──► npm publish
│ └─► docs ──► deploy-docs ──► libs.heritiersdudonjon.com
pull request
├─► ci
└─► docs (no deploy)Two-phase rule (library → consumers)
- Merge to
rpg-commonsmainand wait for npm publish + git tag. - Only then open/update consumer PRs with the new semver.
Do not merge consumer changes that depend on unpublished exports. Avoid long-lived file:../, pnpm link, or npm pack for shared CI.
Pipeline files
| File | Role |
|---|---|
.github/workflows/ci.yml | CI, release, docs, deploy-docs |
.releaserc.json | semantic-release plugins |
CHANGELOG.md | Auto-generated — do not edit manually |
typedoc.json | API reference generation |
scripts/build-docs.mjs | Full doc site build |
Release bot commits use chore(release): X.Y.Z [skip ci] — the next normal push re-runs all jobs.
See also Documentation pipeline.
First release — bootstrapping
The current package.json version (0.0.1) is the starting point. semantic-release will set the initial published version based on commits. To ensure it publishes v0.1.0 on the first run, push at least one feat: commit.
If the repo has no previous tags, semantic-release treats all commits as new and will publish v1.0.0 for a feat: or v0.1.0 if configured with "firstRelease". The default behavior uses v1.0.0 as the first release for feat:. To start at v0.1.0, add a lightweight tag v0.0.0 before the first pipeline run:
git tag v0.0.0
git push origin v0.0.0This anchors semantic-release's history so the next feat: commit produces v0.1.0.
Consuming the package
Local development
Prefer a user-level ~/.npmrc with your PAT (do not commit tokens to the consumer repo):
@franksauvag:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PATThen install as usual (pnpm add, npm install, etc.).
Optionally commit a project .npmrc that contains only the @franksauvag:registry=… line so the scope is explicit for everyone; tokens stay in ~/.npmrc or CI.
CI (GitHub Actions)
For a consumer repo's CI pipeline, add a PAT with read:packages scope as a repository secret (e.g. PACKAGES_READ_TOKEN), then configure setup-node and install:
- uses: actions/setup-node@v5
with:
node-version: 22
registry-url: https://npm.pkg.github.com
scope: "@franksauvag"
env:
NODE_AUTH_TOKEN: ${{ secrets.PACKAGES_READ_TOKEN }}
- name: Install
run: pnpm install --frozen-lockfileNote: The built-in
GITHUB_TOKENof a consumer repo can only access packages published by that same repo. A PAT is required to read packages fromrpg-commons.
Emergency manual release
If semantic-release is blocked or you need to release from a local machine:
# Ensure clean working tree on main
git checkout main && git pull
# Dry-run to preview what would be released
GITHUB_TOKEN=$(gh auth token) NODE_AUTH_TOKEN=$(gh auth token) pnpm exec semantic-release --dry-run
# Actual release
GITHUB_TOKEN=$(gh auth token) NODE_AUTH_TOKEN=$(gh auth token) pnpm exec semantic-release