Files
stamp/README.md
2026-03-13 00:59:27 +01:00

549 lines
16 KiB
Markdown

# stamp
A language-agnostic, changesets-style versioning and changelog tool. Works with any project type and supports monorepos.
## Overview
`stamp` is inspired by the [Changesets](https://github.com/changesets/changesets) workflow, but without the Node.js dependency. It works by having contributors add small **stamp files** (`.stamp/*.md`) alongside their code changes. When it's time to release, `stamp` consumes those files, bumps versions, updates changelogs, creates git tags, and publishes releases to GitHub or Gitea.
There are two supported release workflows:
- **Direct release** — run `stamp version` and `stamp publish` locally or in CI on the base branch.
- **Release PR** — run `stamp release-pr` in CI to open a pull request that contains the version bumps. Merging the PR triggers the actual publish step.
## Installation
Via [Mise](https://mise.jdx.dev/):
```toml
# mise.toml
[tools]
"go:git.thokra.dev/thokra/stamp/cmd/stamp" = "latest"
```
Or with `go install`:
```sh
go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
```
Or build from source:
```sh
git clone https://git.thokra.dev/thokra/stamp
cd stamp
go build -o bin/stamp ./cmd/stamp
```
## Quick Start
### 1. Create `.stamp/stamp.toml`
```toml
[[projects]]
name = "my-app"
path = "."
version = "0.1.0"
[projects.publish]
tags = true
releases = true
artifacts = ["dist/my-app-*"]
```
Because `stamp.toml` lives inside `.stamp/`, git will track the directory without needing a `.gitignore` file in it.
See [`examples/stamp.toml`](examples/stamp.toml) for a fully annotated example.
### 2. Add a stamp file when making a change
```sh
# Interactive
stamp add
# Non-interactive (great for scripts and AI agents)
stamp add --project=my-app --bump=minor --message="Added new feature"
```
This creates a `.stamp/<random-slug>.md` file. Commit it alongside your code.
### 3. Check pending stamps
```sh
stamp status
```
```
📦 Pending stamps: 2
PROJECT CURRENT NEXT BUMP STAMPS
------- ------- ---- ---- ------
my-app 1.2.3 1.3.0 minor 2
```
### 4. Release
```sh
# Bump versions and update changelogs
stamp version
# Create git tags, GitHub/Gitea releases, and upload artifacts
STAMP_REPO=owner/repo stamp publish
```
## Commands
### `stamp add`
Creates a new stamp file describing a change.
| Flag | Description |
|------|-------------|
| `--project` | Project name to include (repeatable) |
| `--bump` | Bump type: `major`, `minor`, `patch`, `premajor`, `preminor`, `prepatch`, `prerelease` |
| `--message` | Description of the change |
| `--no-interactive` | Disable interactive prompts (requires `--project`, `--bump`, `--message`) |
| `--slug` | Custom filename slug (default: auto-generated, e.g. `brave-river`) |
### `stamp status`
Shows all pending stamp files and the projected next version for each project.
| Flag | Description |
|------|-------------|
| `--json` | Output as JSON |
### `stamp version`
Consumes all pending stamp files, bumps versions in `.stamp/stamp.toml`, and prepends a new entry to each project's `CHANGELOG.md`. Then stages and commits the changes via git.
| Flag | Description |
|------|-------------|
| `--snapshot <id>` | Create a pre-release with the given identifier (e.g. `alpha`, `rc`) |
| `--no-commit` | Apply changes without creating a git commit |
### `stamp publish`
Creates git tags and publishes releases (with optional artifact uploads) to GitHub or Gitea.
| Flag | Description |
|------|-------------|
| `--dry-run` / `-n` | Print what would happen without executing |
| `--project` | Only publish a specific project |
Requires `STAMP_REPO=owner/repo` to be set. Detects GitHub vs Gitea automatically via the `GITEA_BASE_URL` environment variable.
Tag format: `<name>@v<version>` (e.g. `my-app@v1.3.0`).
### `stamp release-pr`
Creates or updates a **release pull request** — a PR that contains all the version bumps and changelog updates that would be applied by `stamp version`. Merging it acts as the explicit release trigger.
The command:
1. Reads all pending stamp files. If there are none, it exits cleanly with no side effects.
2. Fetches `origin` and resets a well-known branch (default: `stamp/release`) from the tip of the base branch, so the branch is always a clean, fast-forwardable head.
3. Applies all version bumps and changelog updates to that branch (identical logic to `stamp version --no-commit`), then commits and force-pushes it.
4. Opens a new PR — or updates the existing one — targeting the base branch. The PR is identified across runs by a hidden marker in the body, so only one stamp release PR exists at a time.
Once the PR is merged, run `stamp publish` to tag and release.
| Flag | Description |
|------|-------------|
| `--branch` | Release branch name (default: `stamp/release`) |
| `--base` | Base branch the PR targets (default: from `stamp.toml`, fallback: `main`) |
| `--repo` | Repository slug `owner/repo` (defaults to `STAMP_REPO` or `GITHUB_REPOSITORY`) |
| `--snapshot <id>` | Pre-release identifier (e.g. `alpha`, `rc`) |
| `--dry-run` / `-n` | Print what would be done without pushing or opening a PR |
### `stamp comment`
Posts or updates a PR comment summarising pending stamps. Useful in CI to remind contributors to add stamp files or to show reviewers what versions will change.
- **No stamps found** → warns the author with instructions on how to add one.
- **Stamps found** → shows a table of affected projects and their next versions.
| Flag | Description |
|------|-------------|
| `--pr` | Pull request number (required) |
| `--repo` | Repository slug `owner/repo` (defaults to `STAMP_REPO` or `GITHUB_REPOSITORY`) |
### `stamp preview`
Manages **pre-release mode** for a project. While a project is in pre-release mode, every `stamp version` (and `stamp release-pr`) run produces pre-release versions (e.g. `1.3.0-beta.0`, `1.3.0-beta.1`) instead of normal ones. Exiting preview mode causes the next run to produce a regular release.
```sh
# Enter pre-release mode — all future version bumps will be pre-releases
stamp preview enter my-app beta
# Leave pre-release mode — the next version bump will be a normal release
stamp preview exit my-app
```
## Stamp File Format
Stamp files live in `.stamp/` alongside `stamp.toml`, and use Markdown with YAML or TOML frontmatter.
### YAML (default)
```markdown
---
bumps:
my-app: minor
my-lib: patch
---
Short description used as the changelog entry.
- Optional bullet points for more detail.
```
### TOML
```markdown
+++
[bumps]
my-app = "minor"
+++
Short description.
```
### Bump types
| Type | Description |
|------|-------------|
| `major` | Breaking change — `1.2.3``2.0.0` |
| `minor` | New feature — `1.2.3``1.3.0` |
| `patch` | Bug fix — `1.2.3``1.2.4` |
| `premajor` | Pre-release major — `1.2.3``2.0.0-alpha.0` |
| `preminor` | Pre-release minor — `1.2.3``1.3.0-alpha.0` |
| `prepatch` | Pre-release patch — `1.2.3``1.2.4-alpha.0` |
| `prerelease` | Increment pre-release — `1.2.4-rc.0``1.2.4-rc.1` |
## Configuration Reference (`.stamp/stamp.toml`)
```toml
# Global settings (all optional)
[config]
base_branch = "main" # Base branch for PR change detection (default: main)
[[projects]]
name = "my-app"
path = "apps/my-app" # Path relative to repo root
version = "1.2.3" # Current version — updated by `stamp version`
changelog = "CHANGELOG.md" # Relative to path (default: CHANGELOG.md)
pre_tag = "" # Set by `stamp preview enter`; leave blank for normal releases
[projects.publish]
tags = true
releases = true
artifacts = [
"apps/my-app/dist/my-app-linux-amd64",
"apps/my-app/dist/my-app-darwin-arm64",
]
```
## Environment Variables
| Variable | Purpose |
|----------|---------|
| `STAMP_REPO` | Repository slug `owner/repo` — required for `publish`, `comment`, and `release-pr` |
| `GITHUB_TOKEN` | GitHub token for releases and PR comments — automatically provided by the GitHub Actions runner; no manual setup needed |
| `GITEA_TOKEN` | Gitea access token — **must be created manually** (Gitea Actions does not inject one automatically); create a token in your Gitea account settings and store it as a repository secret |
| `GITEA_BASE_URL` | Gitea instance URL (e.g. `https://gitea.example.com`) — also enables Gitea mode |
## CI Integration
### Workflow: PR check (`stamp comment`)
Posts a comment on every pull request showing which projects will be bumped and to what version. Warns if no stamp file was added.
**GitHub Actions**
```yaml
# .github/workflows/stamp-check.yml
name: stamp check
on:
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install stamp
run: go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
- name: Comment on PR
run: stamp comment --pr=${{ github.event.pull_request.number }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
STAMP_REPO: ${{ github.repository }}
```
**Gitea Actions**
```yaml
# .gitea/workflows/stamp-check.yml
name: stamp check
on:
pull_request:
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install stamp
run: go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
- name: Comment on PR
run: stamp comment --pr=${{ gitea.event.pull_request.number }}
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_BASE_URL: ${{ gitea.server_url }}
STAMP_REPO: ${{ gitea.repository }}
```
---
### Workflow: Release PR (`stamp release-pr` + `stamp publish`)
This is the recommended automation workflow. Whenever a PR with stamp files is merged to the base branch, CI opens (or updates) a release PR. Merging the release PR triggers the actual publish.
```
feature branch ──► merge to main ──► stamp release-pr ──► release PR
merge to main
stamp publish ──► tags + releases
```
**Step 1 — Open or update the release PR on every push to `main`**
*GitHub Actions*
```yaml
# .github/workflows/stamp-release-pr.yml
name: release PR
on:
push:
branches: [main]
jobs:
release-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# A full clone is needed so stamp can fetch and reset the release branch.
fetch-depth: 0
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Install stamp
run: go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
- name: Create or update release PR
run: stamp release-pr --base=main
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
STAMP_REPO: ${{ github.repository }}
```
*Gitea Actions*
```yaml
# .gitea/workflows/stamp-release-pr.yml
name: release PR
on:
push:
branches: [main]
jobs:
release-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure git
run: |
git config user.name "stamp-bot"
git config user.email "stamp-bot@users.noreply.gitea.com"
- name: Install stamp
run: go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
- name: Create or update release PR
run: stamp release-pr --base=main
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_BASE_URL: ${{ gitea.server_url }}
STAMP_REPO: ${{ gitea.repository }}
```
**Step 2 — Publish after the release PR is merged**
*GitHub Actions*
```yaml
# .github/workflows/stamp-publish.yml
name: publish
on:
push:
branches: [main]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Install stamp
run: go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
- name: Publish releases
run: stamp publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
STAMP_REPO: ${{ github.repository }}
```
*Gitea Actions*
```yaml
# .gitea/workflows/stamp-publish.yml
name: publish
on:
push:
branches: [main]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure git
run: |
git config user.name "stamp-bot"
git config user.email "stamp-bot@users.noreply.gitea.com"
- name: Install stamp
run: go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
- name: Publish releases
run: stamp publish
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
GITEA_BASE_URL: ${{ gitea.server_url }}
STAMP_REPO: ${{ gitea.repository }}
```
> **Tip:** Both jobs run on every push to `main`. `stamp release-pr` is a no-op once there are no more pending stamp files (i.e. after the release PR has been merged and `stamp publish` has consumed them). `stamp publish` is a no-op if there are no new tags to create. The two jobs are safe to run in parallel or in sequence.
---
### Workflow: Direct release (no PR)
If you prefer to version and publish in a single step without a release PR, run both commands directly on the base branch:
```yaml
# .github/workflows/stamp-release.yml
name: release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Install stamp
run: go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
- name: Version and publish
run: |
stamp version
git push
stamp publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
STAMP_REPO: ${{ github.repository }}
```
## Development
This project uses [Mise](https://mise.jdx.dev/) to manage the Go toolchain and common tasks.
```sh
# Build
mise run build
# Run tests
mise run test
# Lint
mise run lint
# Install locally
mise run install
```
## Project Structure
```
stamp/
├── cmd/stamp/ # CLI entry point and command definitions
├── internal/
│ ├── changeset/ # Stamp file parsing, writing, and slug generation
│ ├── changelog/ # CHANGELOG.md generation and appending
│ ├── config/ # stamp.toml loading, validation, and saving
│ ├── git/ # Git operations (tag, commit, push, branch management)
│ ├── gitea/ # Gitea API client (releases, PR comments, release PRs)
│ ├── github/ # GitHub API client (releases, PR comments, release PRs)
│ ├── publish/ # Orchestrates tagging and release publishing
│ ├── releasepr/ # Orchestrates release PR creation and updates
│ └── semver/ # Semantic version bumping logic
├── examples/
│ └── stamp.toml # Annotated configuration example
└── docs/
└── workflow.md # Detailed workflow guide
```
## License
MIT