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 .bumpy/_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,10 @@
"publish": {
"provenance": true,
"npmStaged": true
},
// prerelease channel — dogfooding our own feature: pushes to `next` publish
// `-rc.N` versions to the @next dist-tag (prerelease versions are never committed)
"channels": {
"next": { "branch": "next", "preid": "rc", "tag": "next" }
}
}
5 changes: 5 additions & 0 deletions .bumpy/prerelease-channels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@varlock/bumpy': minor
---

Add prerelease channels — branch-based prerelease lines (e.g. `next` → `@next` dist-tag) where prerelease versions are never committed to git. Targets derive from bump files, counters from the registry; shipped bump files are tracked by moving them into `.bumpy/<channel>/`. Includes channel-aware `version` / `publish` / `status` / `ci release` flows, exact-pinned lockstep cycle publishes, and promotion-by-merge to stable.
36 changes: 32 additions & 4 deletions .github/workflows/bumpy-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
# ⚠️ NOTE - DO NOT COPY THIS FILE
# instead look at the recommended workflow in the docs
# ➡️ https://bumpy.varlock.dev/blob/main/docs/github-actions.md ⬅️
#
# This repo splits the check into two mutually-exclusive jobs so it can dogfood its
# OWN unreleased CLI on internal PRs while staying safe for fork PRs. A normal project
# only needs the single `bunx @varlock/bumpy@latest ci check` job (the fork-safe one).

name: Bumpy Check

Expand All @@ -14,17 +18,41 @@ permissions:
contents: read

jobs:
bumpy-check:
# Fork PRs (untrusted): run the PUBLISHED bumpy and never execute the PR's code.
# pull_request_target carries a write token + secrets, so building/running fork
# code here would be a privilege-escalation hole. `ci check` reads json/yaml only.
check-published:
if: github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
steps:
# Check out the PR head so bumpy can read the PR's bump files, config, and package.json
# Check out the PR head so bumpy can read the PR's bump files, config, and package.json.
# We never execute this code!
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: oven-sh/setup-bun@v2

# reads json/yaml files only, so it's safe to run on fork PRs
- run: bunx @varlock/bumpy@latest ci check
env:
GH_TOKEN: ${{ github.token }}

# Internal (non-fork) PRs: build and run THIS repo's local bumpy so we dogfood the
# unreleased CLI (e.g. channel-aware comments before they're published to @latest).
# ⚠️ DO NOT COPY — only safe because the PR head lives in this same repo, so no
# untrusted code runs with the privileged token. Forks fall through to check-published.
check-local:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0 # need history to diff bump files against the PR base branch
- uses: oven-sh/setup-bun@v2
- run: bun install
# Build first since we run the local built version of bumpy instead of the published one
- run: bun run --filter @varlock/bumpy build
# run bun install again to make the now-built CLI available
- run: bun install
- run: bunx @varlock/bumpy ci check
env:
GH_TOKEN: ${{ github.token }}
5 changes: 3 additions & 2 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
name: Release
on:
push:
branches: [main]
branches: [main, next] # `next` = prerelease channel (dogfooding bumpy's own feature)

concurrency:
group: bumpy-release
# per-branch so a `next` prerelease run doesn't queue behind a `main` stable run
group: bumpy-release-${{ github.ref }}
cancel-in-progress: false

jobs:
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Fixed locale fallback logic in utils.
- **Flexible package management** - include/exclude any package individually via per-package config, glob patterns, or `privatePackages` setting
- **Non-interactive CLI** - `bumpy add` works fully non-interactively for CI/CD and AI-assisted development
- **Aggregated GitHub releases** - optionally create a single consolidated release instead of one per package
- **Prerelease channels** - branch-based `@next` / `@beta` release lines where prerelease versions are derived at publish time, never committed to git (see [prerelease channels docs](https://github.com/dmno-dev/bumpy/blob/main/docs/prereleases.md))
- **Auto-generate from commits** - `bumpy generate` creates bump files from branch commits - works with any commit style, with enhanced detection for conventional commits
- **Pluggable changelog formatters** - built-in `"default"` and `"github"` formatters, or write your own
- **Zero runtime dependencies** - dependencies are minimal and bundled at release time
Expand Down Expand Up @@ -119,6 +120,7 @@ The skill teaches the AI to examine git changes, identify affected packages, cho
- [CLI reference](https://github.com/dmno-dev/bumpy/blob/main/docs/cli.md) - every command with flags and examples
- [GitHub Actions setup](https://github.com/dmno-dev/bumpy/blob/main/docs/github-actions.md) - CI workflows, token setup, trusted publishing
- [Version propagation](https://github.com/dmno-dev/bumpy/blob/main/docs/version-propagation.md) - how dependency bumps cascade through your graph
- [Prerelease channels](https://github.com/dmno-dev/bumpy/blob/main/docs/prereleases.md) - branch-based `@next` / `@beta` release lines

## Why files instead of conventional commits?

Expand All @@ -133,6 +135,7 @@ Bumpy is built as a successor to [🦋changesets](https://github.com/changesets/
- **Custom publish commands** - changesets is hardcoded to `npm publish`. Bumpy supports per-package custom publish for VSCode extensions, Docker images, JSR, etc.
- **Flexible package management** - changesets treats all private packages the same. Bumpy lets you include/exclude any package individually.
- **CI without a separate action or bot** - changesets requires installing a [GitHub App](https://github.com/apps/changeset-bot) _and_ using a [separate GitHub Action](https://github.com/changesets/action). Bumpy replaces both with two CLI commands (`bumpy ci check` + `bumpy ci release`) that run directly in your workflows - no extra repos to trust, no app installation requiring org admin approval.
- **Prerelease channels that don't corrupt state** - changesets' prerelease mode is described in [their own docs](https://github.com/changesets/changesets/blob/main/docs/prereleases.md) as "very complicated" with states "very hard to fix." Bumpy uses [branch-based channels](https://github.com/dmno-dev/bumpy/blob/main/docs/prereleases.md) where prerelease versions are never committed - no global mode file to poison unrelated releases.
- **Automatic migration** - `bumpy init` detects `.changeset/`, renames it to `.bumpy/`, migrates config, keeps pending files, and offers to uninstall `@changesets/cli`.

## Development
Expand All @@ -146,7 +149,6 @@ bunx bumpy --help # invoke built cli

## Roadmap

- Prerelease mode (for now, use [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) for branch preview packages)
- Standalone binary for use outside of JS projects
- Better support for versioning non-JS packages and usage without package.json files
- Plugin system for different publish targets, and support multiple targets per package
Expand Down
44 changes: 29 additions & 15 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ bumpy status --verbose
| `--bump <types>` | Filter by bump type, e.g. `"major"` or `"minor,patch"` |
| `--filter <names>` | Filter by package name or glob |
| `--verbose` | Show bump file details and summaries |
| `--channel <name>` | Channel override (default: inferred from the current branch) |

Exits with code `0` if releases are pending, `1` if none.

On a [prerelease channel](prereleases.md) branch, status shows the cycle instead: shipped vs pending bump files and the derived `-<preid>.N` versions (counters come from the registry; offline they render as `.?`).

## `bumpy version`

Consume all pending bump files and apply the release plan:
Expand All @@ -72,9 +75,12 @@ bumpy version
bumpy version --commit
```

| Flag | Description |
| ---------- | --------------------------------- |
| `--commit` | Commit the version changes to git |
| Flag | Description |
| ------------------ | ------------------------------------------------------------ |
| `--commit` | Commit the version changes to git |
| `--channel <name>` | Channel override (default: inferred from the current branch) |

On a [prerelease channel](prereleases.md) branch, `bumpy version` does something much smaller: it **moves pending bump files into `.bumpy/<channel>/`** and writes no versions and no changelogs — prerelease versions are derived at publish time and never committed.

## `bumpy publish`

Expand All @@ -87,12 +93,15 @@ bumpy publish --tag beta
bumpy publish --filter "@myorg/*"
```

| Flag | Description |
| ------------------ | --------------------------------------------------------- |
| `--dry-run` | Preview what would be published without actually doing it |
| `--tag <tag>` | npm dist-tag (e.g., `next`, `beta`) |
| `--no-push` | Skip pushing git tags to the remote |
| `--filter <names>` | Only publish matching packages (supports globs) |
| Flag | Description |
| ------------------ | ------------------------------------------------------------ |
| `--dry-run` | Preview what would be published without actually doing it |
| `--tag <tag>` | npm dist-tag (e.g., `next`, `beta`) |
| `--no-push` | Skip pushing git tags to the remote |
| `--filter <names>` | Only publish matching packages (supports globs) |
| `--channel <name>` | Channel override (default: inferred from the current branch) |

On a [prerelease channel](prereleases.md) branch, publish derives prerelease versions (targets from the cycle's bump files, counters from the registry), transiently writes them into the working tree so pack/build see them, publishes the whole cycle to the channel's dist-tag with exact-pinned inter-cycle deps, then restores the files. Nothing version-shaped is ever committed.

**How bumpy detects unpublished packages:**

Expand All @@ -114,12 +123,15 @@ bumpy check --hook pre-commit
bumpy check --hook pre-push
```

| Flag | Description |
| ------------------- | ---------------------------------------------------------- |
| `--strict` | Fail if any changed package is not covered by a bump file |
| `--no-fail` | Warn only, never exit non-zero (useful for advisory hooks) |
| `--hook pre-commit` | Only count staged + committed bump files |
| `--hook pre-push` | Only count committed bump files |
| Flag | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `--strict` | Fail if any changed package is not covered by a bump file |
| `--no-fail` | Warn only, never exit non-zero (useful for advisory hooks) |
| `--hook pre-commit` | Only count staged + committed bump files |
| `--hook pre-push` | Only count committed bump files |
| `--base <branch>` | Branch to compare against (default: `baseBranch`) — use the channel branch for feature branches targeting a [channel](prereleases.md) |

The check is skipped automatically on channel branches and release PR branches (they move/consume bump files by design).

### Hook context

Expand Down Expand Up @@ -240,6 +252,8 @@ bumpy ci release --auto-publish --tag beta

Requires `GH_TOKEN`. When `BUMPY_GH_TOKEN` is set, it is automatically used to push the version branch and create/edit the PR so that PR workflows trigger (see [GitHub Actions setup](github-actions.md#token-setup)).

**Channel branches:** when run on a branch configured as a [prerelease channel](prereleases.md), `ci release` switches to the channel flow — it publishes the cycle when the triggering push moved bump files into `.bumpy/<channel>/` (a release PR merge), and creates/updates the file-move release PR when pending bump files exist. When channels are configured and the branch is neither `baseBranch` nor a channel branch, the command exits with an error instead of guessing.

## `bumpy ci setup`

Interactive guide to set up `BUMPY_GH_TOKEN` for CI. Walks through creating a fine-grained PAT or GitHub App token and storing it as a repository secret.
Expand Down
24 changes: 24 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Bumpy is configured via `.bumpy/_config.json`, created by `bumpy init`. Per-pack
| `versionPr` | `{ title, branch, preamble }` | see below | Customize the version PR |
| `allowCustomCommands` | `boolean \| string[]` | `false` | Allow per-package custom commands from `package.json` (see below) |
| `packages` | `object` | `{}` | Per-package config overrides (keyed by package name) |
| `channels` | `object` | `{}` | Prerelease channels, keyed by channel name (see below) |

### Dependency bump rules

Expand Down Expand Up @@ -99,6 +100,29 @@ The `versionPr` object customizes the PR that `bumpy ci release` creates:
| `branch` | `string` | `"bumpy/version-packages"` | Branch name for the version PR |
| `preamble` | `string` | — | HTML content prepended to the PR body |

### Prerelease channels

The `channels` object maps long-lived branches to prerelease lines. See [prereleases.md](prereleases.md) for the full workflow.

```jsonc
{
"channels": {
"next": {
"branch": "next", // required — branch that triggers this channel
"preid": "rc", // version suffix (default: channel name)
"tag": "next", // npm dist-tag (default: channel name)
"versionPr": {
"title": "🐸 Versioned release (next)", // default: "<base title> (<name>)"
"branch": "bumpy/version-packages-next", // default: "<base branch>-<name>"
"automerge": false, // enable auto-merge on the release PR
},
},
},
}
```

Channel names become `.bumpy/<name>/` subdirectories (holding bump files that shipped on the channel), so they must be filesystem-safe and can't start with `_` or collide with reserved entries.

## Per-package config

Per-package settings can be defined in two places:
Expand Down
21 changes: 12 additions & 9 deletions docs/differences-from-changesets.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,18 @@ Bumpy replaces all of this with two CLI commands you run directly in standard wo
- [changesets#1242](https://github.com/changesets/changesets/issues/1242) — bot/action version upgrade issues
- [changesets#43](https://github.com/changesets/changesets/issues/43) — can't customize bot messages

### Prerelease channels that actually work

Changesets' prerelease mode is described in their own docs as "very complicated" with "mistakes that can lead to repository and publish states that are very hard to fix." Key problems: global mode state poisons unrelated merges, exiting pre bumps ALL packages, counters require committed state, dist-tags can't be controlled.

Bumpy replaces the mode with **branch-based channels** ([docs/prereleases.md](./prereleases.md)): a long-lived branch (e.g. `next`) maps to a prerelease line. Bump file location (`.bumpy/<channel>/`) is the only state; prerelease versions are never committed — targets derive from bump files, counters from the registry. Promotion to stable is just a merge.

- [changesets#729](https://github.com/changesets/changesets/issues/729) — exiting pre mode bumps all versions (14 comments)
- [changesets#786](https://github.com/changesets/changesets/issues/786) — can't control dist-tag in pre mode (13 comments)
- [changesets#635](https://github.com/changesets/changesets/issues/635) — prerelease workflow problems
- [changesets#239](https://github.com/changesets/changesets/issues/239) — prerelease mode design issues
- [changesets#381](https://github.com/changesets/changesets/issues/381) — prerelease counters require committed state

### Local bump file verification

`bumpy check` verifies that changed packages on the current branch have corresponding bump files. Compares your branch to the base branch, maps changed files to packages. By default it only fails if no bump files exist at all (matching changesets behavior). Use `--strict` to require every changed package to be covered, `--no-fail` for advisory-only mode, or `--hook pre-commit`/`--hook pre-push` to control which bump files count based on their git status. No GitHub API needed.
Expand All @@ -157,15 +169,6 @@ Changesets has no built-in equivalent — users rely on the CI bot comment to ca

## Planned / Not Yet Implemented

### Prerelease mode that actually works

Changesets' prerelease mode is described in their own docs as "very complicated" with "mistakes that can lead to repository and publish states that are very hard to fix." Key problems: no target on bump files, multi-branch corruption, exiting pre bumps ALL packages, bad interactions with linked/fixed groups.

- [changesets#729](https://github.com/changesets/changesets/issues/729) — exiting pre mode bumps all versions (14 comments)
- [changesets#786](https://github.com/changesets/changesets/issues/786) — can't control dist-tag in pre mode (13 comments)
- [changesets#635](https://github.com/changesets/changesets/issues/635) — prerelease workflow problems
- [changesets#239](https://github.com/changesets/changesets/issues/239) — prerelease mode design issues

### Root workspace / non-package changes

Track changes to CI, tooling, and monorepo-root-level config in changelogs — not just workspace packages.
Expand Down
Loading