Release
This commit is contained in:
245
README.md
245
README.md
@@ -6,6 +6,11 @@ A language-agnostic, changesets-style versioning and changelog tool. Works with
|
|||||||
|
|
||||||
`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.
|
`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
|
## Installation
|
||||||
|
|
||||||
Via [Mise](https://mise.jdx.dev/):
|
Via [Mise](https://mise.jdx.dev/):
|
||||||
@@ -13,19 +18,19 @@ Via [Mise](https://mise.jdx.dev/):
|
|||||||
```toml
|
```toml
|
||||||
# mise.toml
|
# mise.toml
|
||||||
[tools]
|
[tools]
|
||||||
"go:github.com/thokra/stamp/cmd/stamp" = "latest"
|
"go:git.thokra.dev/thokra/stamp/cmd/stamp" = "latest"
|
||||||
```
|
```
|
||||||
|
|
||||||
Or with `go install`:
|
Or with `go install`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go install github.com/thokra/stamp/cmd/stamp@latest
|
go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Or build from source:
|
Or build from source:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/thokra/stamp
|
git clone https://git.thokra.dev/thokra/stamp
|
||||||
cd stamp
|
cd stamp
|
||||||
go build -o bin/stamp ./cmd/stamp
|
go build -o bin/stamp ./cmd/stamp
|
||||||
```
|
```
|
||||||
@@ -130,6 +135,27 @@ Requires `STAMP_REPO=owner/repo` to be set. Detects GitHub vs Gitea automaticall
|
|||||||
|
|
||||||
Tag format: `<name>@v<version>` (e.g. `my-app@v1.3.0`).
|
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`
|
### `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.
|
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.
|
||||||
@@ -142,6 +168,18 @@ Posts or updates a PR comment summarising pending stamps. Useful in CI to remind
|
|||||||
| `--pr` | Pull request number (required) |
|
| `--pr` | Pull request number (required) |
|
||||||
| `--repo` | Repository slug `owner/repo` (defaults to `STAMP_REPO` or `GITHUB_REPOSITORY`) |
|
| `--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 File Format
|
||||||
|
|
||||||
Stamp files live in `.stamp/` alongside `stamp.toml`, and use Markdown with YAML or TOML frontmatter.
|
Stamp files live in `.stamp/` alongside `stamp.toml`, and use Markdown with YAML or TOML frontmatter.
|
||||||
@@ -195,6 +233,7 @@ name = "my-app"
|
|||||||
path = "apps/my-app" # Path relative to repo root
|
path = "apps/my-app" # Path relative to repo root
|
||||||
version = "1.2.3" # Current version — updated by `stamp version`
|
version = "1.2.3" # Current version — updated by `stamp version`
|
||||||
changelog = "CHANGELOG.md" # Relative to path (default: CHANGELOG.md)
|
changelog = "CHANGELOG.md" # Relative to path (default: CHANGELOG.md)
|
||||||
|
pre_tag = "" # Set by `stamp preview enter`; leave blank for normal releases
|
||||||
|
|
||||||
[projects.publish]
|
[projects.publish]
|
||||||
tags = true
|
tags = true
|
||||||
@@ -209,14 +248,18 @@ changelog = "CHANGELOG.md" # Relative to path (default: CHANGELOG.md)
|
|||||||
|
|
||||||
| Variable | Purpose |
|
| Variable | Purpose |
|
||||||
|----------|---------|
|
|----------|---------|
|
||||||
| `STAMP_REPO` | Repository slug `owner/repo` — required for `publish` and `comment` |
|
| `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 |
|
| `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_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 |
|
| `GITEA_BASE_URL` | Gitea instance URL (e.g. `https://gitea.example.com`) — also enables Gitea mode |
|
||||||
|
|
||||||
## CI Integration
|
## CI Integration
|
||||||
|
|
||||||
### GitHub Actions — PR check
|
### 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
|
```yaml
|
||||||
# .github/workflows/stamp-check.yml
|
# .github/workflows/stamp-check.yml
|
||||||
@@ -232,7 +275,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install stamp
|
- name: Install stamp
|
||||||
run: go install github.com/thokra/stamp/cmd/stamp@latest
|
run: go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
|
||||||
|
|
||||||
- name: Comment on PR
|
- name: Comment on PR
|
||||||
run: stamp comment --pr=${{ github.event.pull_request.number }}
|
run: stamp comment --pr=${{ github.event.pull_request.number }}
|
||||||
@@ -241,7 +284,7 @@ jobs:
|
|||||||
STAMP_REPO: ${{ github.repository }}
|
STAMP_REPO: ${{ github.repository }}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Gitea Actions — PR check
|
**Gitea Actions**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# .gitea/workflows/stamp-check.yml
|
# .gitea/workflows/stamp-check.yml
|
||||||
@@ -257,7 +300,7 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install stamp
|
- name: Install stamp
|
||||||
run: go install github.com/thokra/stamp/cmd/stamp@latest
|
run: go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
|
||||||
|
|
||||||
- name: Comment on PR
|
- name: Comment on PR
|
||||||
run: stamp comment --pr=${{ gitea.event.pull_request.number }}
|
run: stamp comment --pr=${{ gitea.event.pull_request.number }}
|
||||||
@@ -267,6 +310,181 @@ jobs:
|
|||||||
STAMP_REPO: ${{ gitea.repository }}
|
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: 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: 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: 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: 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
|
## Development
|
||||||
|
|
||||||
This project uses [Mise](https://mise.jdx.dev/) to manage the Go toolchain and common tasks.
|
This project uses [Mise](https://mise.jdx.dev/) to manage the Go toolchain and common tasks.
|
||||||
@@ -294,17 +512,18 @@ stamp/
|
|||||||
│ ├── changeset/ # Stamp file parsing, writing, and slug generation
|
│ ├── changeset/ # Stamp file parsing, writing, and slug generation
|
||||||
│ ├── changelog/ # CHANGELOG.md generation and appending
|
│ ├── changelog/ # CHANGELOG.md generation and appending
|
||||||
│ ├── config/ # stamp.toml loading, validation, and saving
|
│ ├── config/ # stamp.toml loading, validation, and saving
|
||||||
│ ├── git/ # Git operations (tag, commit, push)
|
│ ├── git/ # Git operations (tag, commit, push, branch management)
|
||||||
│ ├── gitea/ # Gitea API client (releases, PR comments)
|
│ ├── gitea/ # Gitea API client (releases, PR comments, release PRs)
|
||||||
│ ├── github/ # GitHub API client (releases, PR comments)
|
│ ├── github/ # GitHub API client (releases, PR comments, release PRs)
|
||||||
│ ├── publish/ # Orchestrates tagging and release publishing
|
│ ├── publish/ # Orchestrates tagging and release publishing
|
||||||
|
│ ├── releasepr/ # Orchestrates release PR creation and updates
|
||||||
│ └── semver/ # Semantic version bumping logic
|
│ └── semver/ # Semantic version bumping logic
|
||||||
├── examples/
|
├── examples/
|
||||||
│ └── stamp.toml # Annotated configuration example
|
│ └── stamp.toml # Annotated configuration example
|
||||||
└── docs/
|
└── docs/
|
||||||
└── workflow.md # Detailed workflow guide
|
└── workflow.md # Detailed workflow guide
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
79
cmd/stamp/cmd_release_pr.go
Normal file
79
cmd/stamp/cmd_release_pr.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
|
||||||
|
"git.thokra.dev/thokra/stamp/internal/config"
|
||||||
|
"git.thokra.dev/thokra/stamp/internal/releasepr"
|
||||||
|
)
|
||||||
|
|
||||||
|
func releasePRCmd() *cli.Command {
|
||||||
|
return &cli.Command{
|
||||||
|
Name: "release-pr",
|
||||||
|
Usage: "create or update a release pull request with pending version bumps",
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "branch",
|
||||||
|
Usage: "release branch name (default: stamp/release)",
|
||||||
|
Value: releasepr.DefaultReleaseBranch,
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "base",
|
||||||
|
Usage: "base branch the PR targets (default: from stamp.toml, fallback: main)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "repo",
|
||||||
|
Usage: "repository slug owner/repo (defaults to STAMP_REPO env var)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "snapshot",
|
||||||
|
Usage: "pre-release identifier (e.g. alpha, beta, rc) for snapshot releases",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "dry-run",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Usage: "print what would be done without pushing or opening a PR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: func(ctx context.Context, cmd *cli.Command) error {
|
||||||
|
repoRoot, err := findRepoRoot(".")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := config.Load(repoRoot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repoSlug := cmd.String("repo")
|
||||||
|
if repoSlug == "" {
|
||||||
|
repoSlug = os.Getenv("STAMP_REPO")
|
||||||
|
}
|
||||||
|
if repoSlug == "" {
|
||||||
|
repoSlug = os.Getenv("GITHUB_REPOSITORY")
|
||||||
|
}
|
||||||
|
if repoSlug == "" {
|
||||||
|
return fmt.Errorf("--repo or STAMP_REPO / GITHUB_REPOSITORY env var must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseBranch := cmd.String("base")
|
||||||
|
if baseBranch == "" {
|
||||||
|
baseBranch = cfg.BaseBranch()
|
||||||
|
}
|
||||||
|
|
||||||
|
return releasepr.Run(ctx, releasepr.Options{
|
||||||
|
RepoRoot: repoRoot,
|
||||||
|
RepoSlug: repoSlug,
|
||||||
|
BaseBranch: baseBranch,
|
||||||
|
ReleaseBranch: cmd.String("branch"),
|
||||||
|
SnapshotID: cmd.String("snapshot"),
|
||||||
|
DryRun: cmd.Bool("dry-run"),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,22 +75,7 @@ func versionCmd() *cli.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if preID != "" {
|
if preID != "" {
|
||||||
// If the project is already on a pre-release version, just
|
highest = semver.PreReleaseBump(project.Version, highest)
|
||||||
// increment it. Otherwise promote the highest regular bump
|
|
||||||
// to its pre-release equivalent so we don't skip a version.
|
|
||||||
v, _ := semver.CurrentVersion(project.Version)
|
|
||||||
if v != nil && v.Prerelease() != "" {
|
|
||||||
highest = changeset.BumpPreRelease
|
|
||||||
} else {
|
|
||||||
switch highest {
|
|
||||||
case changeset.BumpMajor:
|
|
||||||
highest = changeset.BumpPreMajor
|
|
||||||
case changeset.BumpMinor:
|
|
||||||
highest = changeset.BumpPreMinor
|
|
||||||
default: // patch or anything lower
|
|
||||||
highest = changeset.BumpPrePatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nextVer, err := semver.Bump(project.Version, highest, preID)
|
nextVer, err := semver.Bump(project.Version, highest, preID)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ func main() {
|
|||||||
publishCmd(),
|
publishCmd(),
|
||||||
commentCmd(),
|
commentCmd(),
|
||||||
previewCmd(),
|
previewCmd(),
|
||||||
|
releasePRCmd(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
412
docs/workflow.md
412
docs/workflow.md
@@ -3,87 +3,87 @@
|
|||||||
`stamp` is a language-agnostic, changesets-style versioning and changelog tool.
|
`stamp` is a language-agnostic, changesets-style versioning and changelog tool.
|
||||||
It works with any project type and supports monorepos.
|
It works with any project type and supports monorepos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Concepts
|
## Concepts
|
||||||
|
|
||||||
| Term | Description |
|
| Term | Meaning |
|
||||||
|------|-------------|
|
|------|---------|
|
||||||
| **Stamp file** | A `.stamp/*.md` file describing a change and which projects it affects |
|
| **Stamp file** | A `.stamp/*.md` file describing a change and which projects it affects. Committed alongside the code change that caused it. |
|
||||||
| **Bump type** | How to increment a version: `major`, `minor`, `patch`, or pre-release variants |
|
| **Bump type** | How to increment a version: `major`, `minor`, `patch`, or a pre-release variant. |
|
||||||
| `stamp version` | Consumes stamp files → bumps versions → updates changelogs |
|
| **Pending stamps** | Stamp files that have been committed but not yet consumed by a version run. |
|
||||||
| `stamp publish` | Creates git tags, releases, and uploads artifacts |
|
| `stamp version` | Consumes pending stamp files → bumps versions in `stamp.toml` → prepends entries to `CHANGELOG.md` files → commits the result. |
|
||||||
|
| `stamp release-pr` | Same versioning logic as `stamp version`, but commits the changes onto a dedicated branch and opens (or updates) a pull request. The actual release happens when that PR is merged. |
|
||||||
|
| `stamp publish` | Creates git tags and publishes releases (with optional artifact uploads) to GitHub or Gitea. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Setup
|
## Two Release Workflows
|
||||||
|
|
||||||
### 1. Install stamp
|
### Direct release
|
||||||
|
|
||||||
Via [Mise](https://mise.jdx.dev/):
|
Run `stamp version` and `stamp publish` directly on the base branch. Good for small projects, CLIs, or repos where a human is doing releases manually.
|
||||||
|
|
||||||
```toml
|
```
|
||||||
# mise.toml
|
feature branch ──► merge to main ──► stamp version ──► git push ──► stamp publish
|
||||||
[tools]
|
|
||||||
"go:git.thokra.dev/thokra/stamp/cmd/stamp" = "latest"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Or build from source:
|
### Release PR (recommended for automation)
|
||||||
|
|
||||||
```sh
|
`stamp release-pr` runs in CI whenever a change is merged to the base branch. It opens a pull request that contains all the version bumps and changelog updates. Merging that PR is the explicit, reviewable release trigger. A second CI job runs `stamp publish` after the merge.
|
||||||
go install git.thokra.dev/thokra/stamp/cmd/stamp@latest
|
|
||||||
|
```
|
||||||
|
feature branch ──► merge to main ──► stamp release-pr ──► release PR
|
||||||
|
│
|
||||||
|
merge to main
|
||||||
|
│
|
||||||
|
stamp publish ──► tags + releases
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Create .stamp/stamp.toml
|
The two CI jobs (create PR and publish) both run on every push to `main`. They are each no-ops when there is nothing for them to do — `stamp release-pr` exits silently if there are no pending stamp files, and `stamp publish` skips any tag that already exists.
|
||||||
|
|
||||||
Inside the `.stamp/` directory at the root of your repository:
|
|
||||||
|
|
||||||
```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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Day-to-day Workflow
|
## Day-to-day: Adding a Stamp File
|
||||||
|
|
||||||
### Adding a stamp to your PR
|
When making a code change that should result in a release, add a stamp file alongside it:
|
||||||
|
|
||||||
When making a change that should be released, add a stamp file:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Interactive
|
# Interactive wizard
|
||||||
stamp add
|
stamp add
|
||||||
|
|
||||||
# Non-interactive (useful in scripts or with AI agents)
|
# Non-interactive — good for scripts and AI agents
|
||||||
stamp add --project=my-app --bump=minor --message="Added X feature" --no-interactive
|
stamp add --project=my-app --bump=minor --message="Add X feature" --no-interactive
|
||||||
```
|
```
|
||||||
|
|
||||||
This creates a `.stamp/<random-slug>.md` file, e.g.:
|
This creates a file like `.stamp/brave-river.md`:
|
||||||
|
|
||||||
```markdown
|
```
|
||||||
---
|
---
|
||||||
bumps:
|
bumps:
|
||||||
my-app: minor
|
my-app: minor
|
||||||
---
|
---
|
||||||
|
|
||||||
Added X feature
|
Add X feature
|
||||||
```
|
```
|
||||||
|
|
||||||
> Stamp files live alongside `stamp.toml` in the `.stamp/` directory.
|
Commit and push the stamp file together with your code. Your CI PR-check job (see below) will update its comment automatically.
|
||||||
|
|
||||||
Commit and push the stamp file alongside your code changes.
|
### Bump type reference
|
||||||
|
|
||||||
### Checking pending stamps
|
| Type | What it does | Example |
|
||||||
|
|------|-------------|---------|
|
||||||
|
| `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-beta.0` |
|
||||||
|
| `preminor` | Pre-release minor | `1.2.3` → `1.3.0-beta.0` |
|
||||||
|
| `prepatch` | Pre-release patch | `1.2.3` → `1.2.4-beta.0` |
|
||||||
|
| `prerelease` | Increment pre-release counter | `1.2.4-rc.0` → `1.2.4-rc.1` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checking Pending Stamps
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
stamp status
|
stamp status
|
||||||
@@ -95,36 +95,210 @@ stamp status
|
|||||||
PROJECT CURRENT NEXT BUMP STAMPS
|
PROJECT CURRENT NEXT BUMP STAMPS
|
||||||
------- ------- ---- ---- ------
|
------- ------- ---- ---- ------
|
||||||
my-app 1.2.3 1.3.0 minor 2
|
my-app 1.2.3 1.3.0 minor 2
|
||||||
my-lib 0.1.0 0.1.0 — 0
|
my-lib 0.4.1 0.4.1 — 0
|
||||||
```
|
|
||||||
|
|
||||||
### Releasing
|
|
||||||
|
|
||||||
On your release branch (e.g. after merging PRs):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# 1. Bump versions and update changelogs
|
|
||||||
stamp version
|
|
||||||
|
|
||||||
# 2. Publish tags, releases, and artifacts
|
|
||||||
STAMP_REPO=owner/repo stamp publish
|
|
||||||
```
|
|
||||||
|
|
||||||
For a pre-release snapshot:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
stamp version --snapshot=alpha
|
|
||||||
stamp publish
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## PR Commenting (CI)
|
## Workflow A: Direct Release
|
||||||
|
|
||||||
Use `stamp comment` in your CI pipeline to automatically comment on PRs:
|
Use this when you release manually or from CI without a release PR.
|
||||||
|
|
||||||
- ⚠️ **No stamp file?** → warns the author and explains how to add one
|
```sh
|
||||||
- ✅ **Stamps found?** → shows a table of affected projects and next versions
|
# 1. Bump versions, update changelogs, and commit
|
||||||
|
stamp version
|
||||||
|
|
||||||
|
# 2. Push the version commit and tags
|
||||||
|
git push
|
||||||
|
|
||||||
|
# 3. Create GitHub / Gitea releases and upload artifacts
|
||||||
|
STAMP_REPO=owner/repo stamp publish
|
||||||
|
```
|
||||||
|
|
||||||
|
For a pre-release snapshot (does not require `stamp preview`):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
stamp version --snapshot=rc
|
||||||
|
git push
|
||||||
|
stamp publish
|
||||||
|
```
|
||||||
|
|
||||||
|
`--snapshot` overrides the bump type for every affected project so that it produces a pre-release version with the given identifier. It is independent of `stamp preview` mode.
|
||||||
|
|
||||||
|
### Skipping the git commit
|
||||||
|
|
||||||
|
If you want to inspect or further modify the files before committing:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
stamp version --no-commit
|
||||||
|
# review the diff, make additional edits…
|
||||||
|
git add -A && git commit -m "chore: version packages"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Workflow B: Release PR
|
||||||
|
|
||||||
|
Use this for fully automated, reviewable releases in CI.
|
||||||
|
|
||||||
|
### How it works — step by step
|
||||||
|
|
||||||
|
1. **Check for pending stamps.** If none exist, `stamp release-pr` exits immediately with no side effects. This makes it safe to run on every push.
|
||||||
|
|
||||||
|
2. **Fetch origin** and reset the release branch (default: `stamp/release`) from the tip of `origin/<base>`. The branch is always recreated cleanly, so it is always fast-forwardable and never diverges from the base branch.
|
||||||
|
|
||||||
|
3. **Apply version changes** — bump versions in `stamp.toml`, prepend changelog entries, delete consumed stamp files. This is identical to what `stamp version --no-commit` does.
|
||||||
|
|
||||||
|
4. **Commit and force-push** the release branch to `origin`.
|
||||||
|
|
||||||
|
5. **Upsert the pull request.** A hidden marker (`<!-- stamp-release-pr -->`) is embedded in the PR body. On the first run a new PR is opened; on subsequent runs the existing PR's title and body are updated to reflect any newly merged stamp files. Only one stamp release PR is ever open at a time.
|
||||||
|
|
||||||
|
The PR body contains a version table:
|
||||||
|
|
||||||
|
```
|
||||||
|
## 📦 Pending releases
|
||||||
|
|
||||||
|
| Project | Current | Next | Bump |
|
||||||
|
|---------|---------|-------|-------|
|
||||||
|
| my-app | 1.2.3 | 1.3.0 | minor |
|
||||||
|
| my-lib | 0.4.1 | 0.4.2 | patch |
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Merge the PR.** Merging triggers the publish job (see below) which creates git tags and GitHub/Gitea releases.
|
||||||
|
|
||||||
|
### CI setup — GitHub Actions
|
||||||
|
|
||||||
|
**Job 1: open / update the release PR** — runs on every push to `main`
|
||||||
|
|
||||||
|
```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:
|
||||||
|
# Full clone so stamp can fetch and reset the release branch.
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- 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 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Job 2: publish after the release PR is merged** — also runs on every push to `main`
|
||||||
|
|
||||||
|
```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: 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 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
> Both jobs run on every push to `main`. They are each idempotent and safe to run concurrently: `stamp release-pr` is a no-op when there are no pending stamps; `stamp publish` skips tags that already exist.
|
||||||
|
|
||||||
|
### CI setup — Gitea Actions
|
||||||
|
|
||||||
|
**Job 1: open / update the release PR**
|
||||||
|
|
||||||
|
```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: 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 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Job 2: publish after the release PR is merged**
|
||||||
|
|
||||||
|
```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: 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 }}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note on `GITEA_TOKEN`:** Gitea Actions does not automatically inject a token the way GitHub Actions does. You must create a personal access token (or a bot account token) in your Gitea account settings with repository read/write permissions, then store it as a repository secret named `GITEA_TOKEN`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PR Commenting (stamp comment)
|
||||||
|
|
||||||
|
Use `stamp comment` in CI to post a summary comment on every pull request. It keeps reviewers and contributors informed about what will be released.
|
||||||
|
|
||||||
|
- **No stamp file** → the comment warns the author and shows how to add one.
|
||||||
|
- **Stamps found** → the comment shows a table of affected projects and their projected next versions.
|
||||||
|
|
||||||
|
The comment is created on first run and updated in place on subsequent runs (identified by a hidden marker in the body).
|
||||||
|
|
||||||
### GitHub Actions
|
### GitHub Actions
|
||||||
|
|
||||||
@@ -179,20 +353,58 @@ jobs:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Stamp File Format
|
## Pre-release Mode (stamp preview)
|
||||||
|
|
||||||
Stamp files live in `.stamp/` alongside `stamp.toml`, and use Markdown with either YAML or TOML frontmatter.
|
`stamp preview` lets you put an individual project into **pre-release mode**. While in this mode, every `stamp version` or `stamp release-pr` run produces pre-release versions instead of normal ones, regardless of the bump types in the stamp files.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Enter pre-release mode — all future versions for my-app will be pre-releases
|
||||||
|
stamp preview enter my-app beta
|
||||||
|
# → next stamp version will produce e.g. 1.3.0-beta.0
|
||||||
|
|
||||||
|
# If a pre-release version already exists, subsequent runs increment the counter
|
||||||
|
# → 1.3.0-beta.0 → 1.3.0-beta.1 → 1.3.0-beta.2 …
|
||||||
|
|
||||||
|
# Exit pre-release mode — the next stamp version will produce a normal release
|
||||||
|
stamp preview exit my-app
|
||||||
|
# → next stamp version will produce e.g. 1.3.0
|
||||||
|
```
|
||||||
|
|
||||||
|
`stamp preview enter` sets the `pre_tag` field on the project in `stamp.toml` and commits nothing — it is a local config change you commit yourself. `stamp preview exit` clears the field.
|
||||||
|
|
||||||
|
### Typical pre-release workflow
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 1. Enter beta mode
|
||||||
|
stamp preview enter my-app beta
|
||||||
|
git add .stamp/stamp.toml && git commit -m "chore: enter beta mode for my-app"
|
||||||
|
|
||||||
|
# 2. Normal development — add stamp files as usual, merge PRs
|
||||||
|
# stamp release-pr will produce beta versions automatically
|
||||||
|
|
||||||
|
# 3. When ready to release, exit beta mode
|
||||||
|
stamp preview exit my-app
|
||||||
|
git add .stamp/stamp.toml && git commit -m "chore: exit beta mode for my-app"
|
||||||
|
|
||||||
|
# 4. The next release-pr or stamp version will produce the full release (e.g. 1.3.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stamp File Format Reference
|
||||||
|
|
||||||
|
Stamp files live in `.stamp/` alongside `stamp.toml`, and use Markdown with YAML or TOML frontmatter.
|
||||||
|
|
||||||
### YAML frontmatter (default)
|
### YAML frontmatter (default)
|
||||||
|
|
||||||
```markdown
|
```
|
||||||
---
|
---
|
||||||
bumps:
|
bumps:
|
||||||
my-app: minor
|
my-app: minor
|
||||||
my-lib: patch
|
my-lib: patch
|
||||||
---
|
---
|
||||||
|
|
||||||
Short description of the change used as the changelog entry.
|
Short description used as the changelog entry.
|
||||||
|
|
||||||
- Optional bullet points
|
- Optional bullet points
|
||||||
- for more detail
|
- for more detail
|
||||||
@@ -200,7 +412,7 @@ Short description of the change used as the changelog entry.
|
|||||||
|
|
||||||
### TOML frontmatter
|
### TOML frontmatter
|
||||||
|
|
||||||
```markdown
|
```
|
||||||
+++
|
+++
|
||||||
[bumps]
|
[bumps]
|
||||||
my-app = "minor"
|
my-app = "minor"
|
||||||
@@ -209,17 +421,35 @@ my-app = "minor"
|
|||||||
Short description.
|
Short description.
|
||||||
```
|
```
|
||||||
|
|
||||||
### Valid bump types
|
A stamp file can affect multiple projects in a single file (as shown above). Each project gets its own changelog entry with the description from the file.
|
||||||
|
|
||||||
| Type | Description |
|
---
|
||||||
|------|-------------|
|
|
||||||
| `major` | Breaking change (1.x.x → 2.0.0) |
|
## Configuration Reference
|
||||||
| `minor` | New feature (1.2.x → 1.3.0) |
|
|
||||||
| `patch` | Bug fix (1.2.3 → 1.2.4) |
|
```toml
|
||||||
| `premajor` | Pre-release major (→ 2.0.0-alpha.0) |
|
# .stamp/stamp.toml
|
||||||
| `preminor` | Pre-release minor (→ 1.3.0-beta.0) |
|
|
||||||
| `prepatch` | Pre-release patch (→ 1.2.4-rc.0) |
|
[config]
|
||||||
| `prerelease` | Increment pre-release (1.2.4-rc.0 → 1.2.4-rc.1) |
|
base_branch = "main" # Base branch targeted by release PRs (default: main)
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "my-app"
|
||||||
|
path = "apps/my-app" # Relative to repository root
|
||||||
|
version = "1.2.3" # Current version — managed by stamp, do not edit manually
|
||||||
|
changelog = "CHANGELOG.md" # Relative to `path` (default: CHANGELOG.md)
|
||||||
|
pre_tag = "" # Set by `stamp preview enter`; blank means normal releases
|
||||||
|
|
||||||
|
[projects.publish]
|
||||||
|
tags = true # Create an annotated git tag on publish
|
||||||
|
releases = true # Create a GitHub / Gitea release on publish
|
||||||
|
artifacts = [ # Glob patterns for files to upload to the release
|
||||||
|
"apps/my-app/dist/my-app-linux-amd64",
|
||||||
|
"apps/my-app/dist/my-app-darwin-arm64",
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Tag format: `<name>@v<version>` for monorepos (e.g. `my-app@v1.3.0`), or `v<version>` for single-project repositories where `name` is empty.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -227,7 +457,7 @@ Short description.
|
|||||||
|
|
||||||
| Variable | Purpose |
|
| Variable | Purpose |
|
||||||
|----------|---------|
|
|----------|---------|
|
||||||
| `STAMP_REPO` | Repository slug `owner/repo` used by publish and comment |
|
| `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 |
|
| `GITHUB_TOKEN` | GitHub token — automatically injected by GitHub Actions; no setup needed |
|
||||||
| `GITEA_TOKEN` | Gitea access token — **must be created manually**; create a token in your Gitea account settings and store it as a repository secret |
|
| `GITEA_TOKEN` | Gitea access token — must be created manually and stored as a secret |
|
||||||
| `GITEA_BASE_URL` | Gitea instance URL (e.g. `https://gitea.example.com`) — also used to detect Gitea mode |
|
| `GITEA_BASE_URL` | Gitea instance URL (e.g. `https://gitea.example.com`) — presence of this variable also activates Gitea mode |
|
||||||
@@ -82,3 +82,36 @@ func TagExists(dir, tag string) (bool, error) {
|
|||||||
}
|
}
|
||||||
return strings.TrimSpace(out) == tag, nil
|
return strings.TrimSpace(out) == tag, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BranchExists returns true if the given local branch exists.
|
||||||
|
func BranchExists(dir, branch string) (bool, error) {
|
||||||
|
out, err := Run(dir, "branch", "--list", branch)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(out) != "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckoutBranch checks out an existing branch.
|
||||||
|
func CheckoutBranch(dir, branch string) error {
|
||||||
|
_, err := Run(dir, "checkout", branch)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckoutNewBranch creates and checks out a new branch from the given base.
|
||||||
|
func CheckoutNewBranch(dir, branch, base string) error {
|
||||||
|
_, err := Run(dir, "checkout", "-B", branch, base)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushBranch force-pushes a branch to origin.
|
||||||
|
func PushBranch(dir, branch string) error {
|
||||||
|
_, err := Run(dir, "push", "--force-with-lease", "origin", branch)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// FetchOrigin fetches from origin.
|
||||||
|
func FetchOrigin(dir string) error {
|
||||||
|
_, err := Run(dir, "fetch", "origin")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
giteaSDK "code.gitea.io/sdk/gitea"
|
giteaSDK "code.gitea.io/sdk/gitea"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const prMarker = "<!-- stamp-release-pr -->"
|
||||||
|
|
||||||
// Client wraps the Gitea API.
|
// Client wraps the Gitea API.
|
||||||
type Client struct {
|
type Client struct {
|
||||||
client *giteaSDK.Client
|
client *giteaSDK.Client
|
||||||
@@ -74,6 +76,51 @@ func (c *Client) UploadAsset(releaseID int64, filePath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpsertPR creates or updates a stamp release PR on the given base branch.
|
||||||
|
// It identifies the existing PR by scanning open PRs for the hidden marker in the body.
|
||||||
|
func (c *Client) UpsertPR(head, base, title, body string) (int64, string, error) {
|
||||||
|
markedBody := prMarker + "\n" + body
|
||||||
|
|
||||||
|
// Search open PRs for an existing stamp release PR (Gitea SDK does not support
|
||||||
|
// filtering by head branch, so we filter client-side).
|
||||||
|
prs, _, err := c.client.ListRepoPullRequests(c.owner, c.repo, giteaSDK.ListPullRequestsOptions{
|
||||||
|
State: giteaSDK.StateOpen,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", fmt.Errorf("listing pull requests: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pr := range prs {
|
||||||
|
if pr.Head != nil && pr.Head.Name != head {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.Contains(pr.Body, prMarker) {
|
||||||
|
// Update existing PR.
|
||||||
|
updated, _, err := c.client.EditPullRequest(c.owner, c.repo, pr.Index,
|
||||||
|
giteaSDK.EditPullRequestOption{
|
||||||
|
Title: title,
|
||||||
|
Body: &markedBody,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", fmt.Errorf("updating pull request #%d: %w", pr.Index, err)
|
||||||
|
}
|
||||||
|
return updated.Index, updated.HTMLURL, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new PR.
|
||||||
|
pr, _, err := c.client.CreatePullRequest(c.owner, c.repo, giteaSDK.CreatePullRequestOption{
|
||||||
|
Head: head,
|
||||||
|
Base: base,
|
||||||
|
Title: title,
|
||||||
|
Body: markedBody,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", fmt.Errorf("creating pull request: %w", err)
|
||||||
|
}
|
||||||
|
return pr.Index, pr.HTMLURL, nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpsertPRComment posts or updates a stamped PR comment.
|
// UpsertPRComment posts or updates a stamped PR comment.
|
||||||
func (c *Client) UpsertPRComment(prNumber int, body string) error {
|
func (c *Client) UpsertPRComment(prNumber int, body string) error {
|
||||||
markedBody := commentMarker + "\n" + body
|
markedBody := commentMarker + "\n" + body
|
||||||
|
|||||||
@@ -94,3 +94,49 @@ func (c *Client) UpsertPRComment(ctx context.Context, prNumber int, body string)
|
|||||||
&gh.IssueComment{Body: gh.Ptr(markedBody)})
|
&gh.IssueComment{Body: gh.Ptr(markedBody)})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const prMarker = "<!-- stamp-release-pr -->"
|
||||||
|
|
||||||
|
// UpsertPR creates or updates a release PR from head into base.
|
||||||
|
// It identifies a previously-created PR by the hidden marker in the body.
|
||||||
|
// Returns the PR number and the URL.
|
||||||
|
func (c *Client) UpsertPR(ctx context.Context, head, base, title, body string) (int, string, error) {
|
||||||
|
markedBody := prMarker + "\n" + body
|
||||||
|
|
||||||
|
// Search open PRs from this head branch.
|
||||||
|
prs, _, err := c.client.PullRequests.List(ctx, c.owner, c.repo, &gh.PullRequestListOptions{
|
||||||
|
State: "open",
|
||||||
|
Head: c.owner + ":" + head,
|
||||||
|
Base: base,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", fmt.Errorf("listing pull requests: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pr := range prs {
|
||||||
|
if strings.Contains(pr.GetBody(), prMarker) {
|
||||||
|
// Update existing PR.
|
||||||
|
updated, _, err := c.client.PullRequests.Edit(ctx, c.owner, c.repo, pr.GetNumber(),
|
||||||
|
&gh.PullRequest{
|
||||||
|
Title: gh.Ptr(title),
|
||||||
|
Body: gh.Ptr(markedBody),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", fmt.Errorf("updating pull request: %w", err)
|
||||||
|
}
|
||||||
|
return updated.GetNumber(), updated.GetHTMLURL(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new PR.
|
||||||
|
pr, _, err := c.client.PullRequests.Create(ctx, c.owner, c.repo, &gh.NewPullRequest{
|
||||||
|
Title: gh.Ptr(title),
|
||||||
|
Head: gh.Ptr(head),
|
||||||
|
Base: gh.Ptr(base),
|
||||||
|
Body: gh.Ptr(markedBody),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", fmt.Errorf("creating pull request: %w", err)
|
||||||
|
}
|
||||||
|
return pr.GetNumber(), pr.GetHTMLURL(), nil
|
||||||
|
}
|
||||||
|
|||||||
278
internal/releasepr/releasepr.go
Normal file
278
internal/releasepr/releasepr.go
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
package releasepr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.thokra.dev/thokra/stamp/internal/changelog"
|
||||||
|
"git.thokra.dev/thokra/stamp/internal/changeset"
|
||||||
|
"git.thokra.dev/thokra/stamp/internal/config"
|
||||||
|
"git.thokra.dev/thokra/stamp/internal/git"
|
||||||
|
giteaClient "git.thokra.dev/thokra/stamp/internal/gitea"
|
||||||
|
ghClient "git.thokra.dev/thokra/stamp/internal/github"
|
||||||
|
"git.thokra.dev/thokra/stamp/internal/semver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultReleaseBranch = "stamp/release"
|
||||||
|
|
||||||
|
// Options controls release-pr behaviour.
|
||||||
|
type Options struct {
|
||||||
|
// RepoRoot is the absolute path to the repository root.
|
||||||
|
RepoRoot string
|
||||||
|
// RepoSlug is "owner/repo", required for API calls.
|
||||||
|
RepoSlug string
|
||||||
|
// BaseBranch is the branch the PR targets (e.g. "main").
|
||||||
|
BaseBranch string
|
||||||
|
// ReleaseBranch is the branch stamp commits to (default: "stamp/release").
|
||||||
|
ReleaseBranch string
|
||||||
|
// SnapshotID is an optional pre-release identifier forwarded to stamp version logic.
|
||||||
|
SnapshotID string
|
||||||
|
// DryRun prints what would happen without pushing or opening a PR.
|
||||||
|
DryRun bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// BumpSummary holds the computed version change for one project.
|
||||||
|
type BumpSummary struct {
|
||||||
|
Name string
|
||||||
|
Current string
|
||||||
|
Next string
|
||||||
|
Bump string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run is the main entry point for `stamp release-pr`.
|
||||||
|
// It versions all pending stamps, commits to the release branch, pushes, and
|
||||||
|
// opens or updates a pull request against the base branch.
|
||||||
|
func Run(ctx context.Context, opts Options) error {
|
||||||
|
cfg, err := config.Load(opts.RepoRoot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
stampDir := filepath.Join(opts.RepoRoot, cfg.ChangesetDir())
|
||||||
|
|
||||||
|
changesets, err := changeset.ReadAll(stampDir)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("reading stamp files: %w", err)
|
||||||
|
}
|
||||||
|
if len(changesets) == 0 {
|
||||||
|
fmt.Println("No pending stamps found — nothing to do.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute version bumps for the PR body before mutating anything.
|
||||||
|
summaries, err := computeSummaries(cfg, changesets, opts.SnapshotID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.DryRun {
|
||||||
|
fmt.Println("[dry-run] Would create/update release PR with:")
|
||||||
|
for _, s := range summaries {
|
||||||
|
fmt.Printf(" %s: %s → %s (%s)\n", s.Name, s.Current, s.Next, s.Bump)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch so we have an up-to-date origin/<base>.
|
||||||
|
fmt.Println("Fetching from origin...")
|
||||||
|
if err := git.FetchOrigin(opts.RepoRoot); err != nil {
|
||||||
|
return fmt.Errorf("fetching origin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create or reset the release branch from origin/<base> so it is always a
|
||||||
|
// clean, fast-forwardable branch on top of the latest base.
|
||||||
|
base := "origin/" + opts.BaseBranch
|
||||||
|
fmt.Printf("Resetting %s from %s...\n", opts.ReleaseBranch, base)
|
||||||
|
if err := git.CheckoutNewBranch(opts.RepoRoot, opts.ReleaseBranch, base); err != nil {
|
||||||
|
return fmt.Errorf("checking out release branch: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply version changes (same logic as `stamp version --no-commit`).
|
||||||
|
changedFiles, err := applyVersions(opts.RepoRoot, cfg, changesets, opts.SnapshotID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stage and commit.
|
||||||
|
if err := git.Add(opts.RepoRoot, changedFiles...); err != nil {
|
||||||
|
return fmt.Errorf("staging files: %w", err)
|
||||||
|
}
|
||||||
|
commitMsg := buildCommitMessage(summaries)
|
||||||
|
if err := git.Commit(opts.RepoRoot, commitMsg); err != nil {
|
||||||
|
return fmt.Errorf("committing version bump: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push the release branch.
|
||||||
|
fmt.Printf("Pushing %s...\n", opts.ReleaseBranch)
|
||||||
|
if err := git.PushBranch(opts.RepoRoot, opts.ReleaseBranch); err != nil {
|
||||||
|
return fmt.Errorf("pushing release branch: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open or update the PR.
|
||||||
|
title := buildPRTitle(summaries)
|
||||||
|
body := buildPRBody(summaries)
|
||||||
|
|
||||||
|
prNumber, prURL, err := upsertPR(ctx, opts, title, body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("✓ Release PR #%d ready: %s\n", prNumber, prURL)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeSummaries calculates the next version for every project that has pending stamps.
|
||||||
|
func computeSummaries(cfg *config.Config, changesets []*changeset.Changeset, snapshotID string) ([]BumpSummary, error) {
|
||||||
|
projectBumps := semver.ProjectBumps(changesets)
|
||||||
|
var summaries []BumpSummary
|
||||||
|
|
||||||
|
for _, project := range cfg.Projects {
|
||||||
|
bumps := projectBumps[project.Name]
|
||||||
|
if len(bumps) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
highest := semver.HighestBump(bumps)
|
||||||
|
preID := snapshotID
|
||||||
|
if preID == "" {
|
||||||
|
preID = project.PreTag
|
||||||
|
}
|
||||||
|
if preID != "" {
|
||||||
|
highest = semver.PreReleaseBump(project.Version, highest)
|
||||||
|
}
|
||||||
|
|
||||||
|
next, err := semver.Bump(project.Version, highest, preID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("computing next version for %s: %w", project.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
summaries = append(summaries, BumpSummary{
|
||||||
|
Name: project.Name,
|
||||||
|
Current: project.Version,
|
||||||
|
Next: next,
|
||||||
|
Bump: string(highest),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return summaries, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyVersions runs the version-bump logic and returns the list of files that were changed.
|
||||||
|
// It mutates cfg.Projects in place (same as cmd_version.go).
|
||||||
|
func applyVersions(repoRoot string, cfg *config.Config, changesets []*changeset.Changeset, snapshotID string) ([]string, error) {
|
||||||
|
projectBumps := semver.ProjectBumps(changesets)
|
||||||
|
now := time.Now()
|
||||||
|
var changedFiles []string
|
||||||
|
|
||||||
|
for i := range cfg.Projects {
|
||||||
|
project := &cfg.Projects[i]
|
||||||
|
bumps := projectBumps[project.Name]
|
||||||
|
if len(bumps) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
highest := semver.HighestBump(bumps)
|
||||||
|
preID := snapshotID
|
||||||
|
if preID == "" {
|
||||||
|
preID = project.PreTag
|
||||||
|
}
|
||||||
|
if preID != "" {
|
||||||
|
highest = semver.PreReleaseBump(project.Version, highest)
|
||||||
|
}
|
||||||
|
|
||||||
|
nextVer, err := semver.Bump(project.Version, highest, preID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("bumping %s: %w", project.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body string
|
||||||
|
for _, cs := range changesets {
|
||||||
|
if _, ok := cs.Bumps[project.Name]; ok {
|
||||||
|
if cs.Description != "" {
|
||||||
|
body += "- " + cs.Description + "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clPath := project.ChangelogPath(repoRoot)
|
||||||
|
if err := changelog.Append(clPath, changelog.Entry{
|
||||||
|
Version: nextVer,
|
||||||
|
Date: now,
|
||||||
|
Description: body,
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("updating changelog for %s: %w", project.Name, err)
|
||||||
|
}
|
||||||
|
changedFiles = append(changedFiles, clPath)
|
||||||
|
|
||||||
|
fmt.Printf(" %s: %s → %s\n", project.Name, project.Version, nextVer)
|
||||||
|
project.Version = nextVer
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist updated versions to stamp.toml.
|
||||||
|
if err := config.Save(repoRoot, cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("saving stamp.toml: %w", err)
|
||||||
|
}
|
||||||
|
changedFiles = append(changedFiles, config.ConfigPath(repoRoot))
|
||||||
|
|
||||||
|
// Delete consumed stamp files.
|
||||||
|
stampDir := filepath.Join(repoRoot, cfg.ChangesetDir())
|
||||||
|
if err := changeset.DeleteAll(stampDir); err != nil {
|
||||||
|
return nil, fmt.Errorf("deleting stamp files: %w", err)
|
||||||
|
}
|
||||||
|
changedFiles = append(changedFiles, stampDir)
|
||||||
|
|
||||||
|
return changedFiles, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCommitMessage(summaries []BumpSummary) string {
|
||||||
|
msg := "chore: version packages\n\n"
|
||||||
|
for _, s := range summaries {
|
||||||
|
msg += fmt.Sprintf("- %s: %s → %s\n", s.Name, s.Current, s.Next)
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPRTitle(summaries []BumpSummary) string {
|
||||||
|
if len(summaries) == 1 {
|
||||||
|
s := summaries[0]
|
||||||
|
return fmt.Sprintf("chore: release %s v%s", s.Name, s.Next)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("chore: release %d packages", len(summaries))
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildPRBody(summaries []BumpSummary) string {
|
||||||
|
body := "## 📦 Pending releases\n\n"
|
||||||
|
body += "This PR was created by `stamp release-pr`. Merge it to apply the version bumps and trigger publishing.\n\n"
|
||||||
|
body += "| Project | Current | Next | Bump |\n"
|
||||||
|
body += "|---------|---------|------|------|\n"
|
||||||
|
for _, s := range summaries {
|
||||||
|
body += fmt.Sprintf("| %s | %s | %s | `%s` |\n", s.Name, s.Current, s.Next, s.Bump)
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
func upsertPR(ctx context.Context, opts Options, title, body string) (int, string, error) {
|
||||||
|
if isGitea() {
|
||||||
|
client, err := giteaClient.NewClient(opts.RepoSlug)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
num, url, err := client.UpsertPR(opts.ReleaseBranch, opts.BaseBranch, title, body)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
return int(num), url, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := ghClient.NewClient(opts.RepoSlug)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
return client.UpsertPR(ctx, opts.ReleaseBranch, opts.BaseBranch, title, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isGitea() bool {
|
||||||
|
return os.Getenv("GITEA_BASE_URL") != ""
|
||||||
|
}
|
||||||
@@ -9,6 +9,25 @@ import (
|
|||||||
"git.thokra.dev/thokra/stamp/internal/changeset"
|
"git.thokra.dev/thokra/stamp/internal/changeset"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PreReleaseBump converts a regular bump type to its pre-release equivalent,
|
||||||
|
// taking into account whether the current version is already a pre-release.
|
||||||
|
// If it is, the bump is always BumpPreRelease (just increment the pre number).
|
||||||
|
// Otherwise the highest regular bump is promoted to its pre-release variant.
|
||||||
|
func PreReleaseBump(currentVersion string, highest changeset.BumpType) changeset.BumpType {
|
||||||
|
v, err := goSemver.NewVersion(currentVersion)
|
||||||
|
if err == nil && v.Prerelease() != "" {
|
||||||
|
return changeset.BumpPreRelease
|
||||||
|
}
|
||||||
|
switch highest {
|
||||||
|
case changeset.BumpMajor:
|
||||||
|
return changeset.BumpPreMajor
|
||||||
|
case changeset.BumpMinor:
|
||||||
|
return changeset.BumpPreMinor
|
||||||
|
default:
|
||||||
|
return changeset.BumpPrePatch
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// CurrentVersion parses a version string and returns the semver object.
|
// CurrentVersion parses a version string and returns the semver object.
|
||||||
// It is exported so callers can inspect pre-release state without re-importing the semver library.
|
// It is exported so callers can inspect pre-release state without re-importing the semver library.
|
||||||
func CurrentVersion(version string) (*goSemver.Version, error) {
|
func CurrentVersion(version string) (*goSemver.Version, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user