just-release
The simplest way to release version-synchronized packages on GitHub.
Philosophy
just-release does one thing well: it makes releasing packages with synchronized versions as simple as running a single command.
This tool is opinionated by design. It doesn't try to support every weird release workflow - it supports the best one:
- ✅ Conventional commits for automatic version bumping
- ✅ GitHub for source control
- ✅ GitHub Actions for automated releases
- ✅ Per-package changelogs
- ✅ Unified versioning across all packages
- ✅ Works with JavaScript (npm/pnpm/yarn), Rust (Cargo), and Go ecosystems
- ✅ Supports mixed-ecosystem repos (e.g., JS + Rust in the same repo)
just-release handles the full lifecycle: version bumping, changelogs, release PRs, publishing to registries (npm, crates.io), and creating GitHub releases.
If this matches your workflow (and it should), just-release will make your life easier. If you need something else, this probably isn't the tool for you.
Features
- 🔍 Automatic ecosystem detection - Works with JavaScript, Rust, and Go (including mixed repos)
- 📝 Conventional commits - Analyzes commits to determine version bumps
- 📦 Unified versioning - All packages in the workspace share the same version
- 📄 Smart changelogs - Generates per-package changelogs only for packages with changes
- 🌿 Git automation - Creates release branches, commits, and pushes automatically
- 🔗 GitHub integration - Creates or updates PRs automatically
- ✂️ Smart PR truncation - Progressively truncates large PR bodies to stay within GitHub's 65k character limit
- 🔒 Dry-run by default - Safe to run locally without making changes
Installation
pnpm add -D just-release
Or run directly with npx:
npx just-release
Usage
Local Development (Dry-run)
By default, just-release runs in dry-run mode when not in a CI environment:
pnpm just-release
This will show you what would happen without making any actual changes.
CI Environment (Live mode)
Set CI=1 to execute the release process:
CI=1 GITHUB_TOKEN=$GITHUB_TOKEN pnpm just-release
How It Works
- Detects ecosystems - Scans for
package.json, Cargo.toml, and/or go.mod at the repo root. Discovers all packages across all detected ecosystems.
- Resolves current version - Reads the version from git history: last
release: X.Y.Z commit → latest vX.Y.Z tag → 0.0.0
- Analyzes commits - Gets all commits since the last release
- Calculates version bump - Based on conventional commit types:
feat: → minor version bump
fix: → patch version bump
BREAKING CHANGE: or feat!: → major version bump
chore:, docs: → no release
- Generates changelogs - Creates/updates
CHANGELOG.md in each affected package
- Creates release branch - Named
release/YYYY-MM-DD
- Updates versions - Updates version in ecosystem-specific manifest files (
package.json, Cargo.toml; Go versions are purely git tags)
- Commits and pushes - Creates commit with message
release: X.Y.Z
- Creates/updates PR - Opens or updates a pull request on GitHub
Supported Ecosystems
JavaScript (npm/pnpm/yarn)
- Detects
package.json at root
- Discovers packages from
pnpm-workspace.yaml or package.json workspaces field
- Updates
version in all package.json files
Rust (Cargo)
- Detects
Cargo.toml at root
- Discovers crates from
[workspace] members patterns
- Handles
version.workspace = true inheritance
- Updates
version in Cargo.toml files (preserving formatting and comments)
Go
- Detects
go.mod at root
- Discovers modules from
go.work use directives
- Version updates are a no-op — Go versions are purely git tags, which are created automatically by the GitHub release step
Mixed Repos
If your repo contains multiple ecosystems (e.g., a TypeScript CLI with a Rust native module), just-release discovers and processes all of them. Every package shares the same synchronized version.
Environment Variables
CI - Set to 1 to run in live mode (default: dry-run)
GITHUB_TOKEN - Required for creating/updating PRs (only in live mode)
Conventional Commit Format
<type>: <subject>
<body>
<footer>
Types
feat: - New feature (minor version bump)
fix: - Bug fix (patch version bump)
perf: - Performance improvement (patch version bump)
docs: - Documentation changes (no version bump)
chore: - Maintenance tasks (no version bump)
test: - Test changes (no version bump)
Breaking Changes
Add ! after the type or include BREAKING CHANGE: in the footer:
feat!: remove deprecated API
BREAKING CHANGE: The old API has been removed. Use the new API instead.
Workflow Setup
GitHub Actions
Repository Permissions
Important: Your repository (or organization) must allow GitHub Actions to create pull requests, or the workflow will fail.
Organization-Level Setting (Recommended)
Set this once for all repositories in your organization:
- Go to your organization's Settings → Actions → General
- Scroll to Workflow permissions
- Enable "Allow GitHub Actions to create and approve pull requests"
Once enabled at the organization level, this setting will apply to all repositories in the organization (unless individually overridden).
Repository-Level Setting
If you're not using organization-level settings, configure each repository individually:
- Go to your repository's Settings → Actions → General
- Scroll to Workflow permissions
- Enable "Allow GitHub Actions to create and approve pull requests"
Note: If your organization disables this setting, the repository-level option will be grayed out. You must enable it at the organization level first.
Workflow Configuration
Create .github/workflows/release.yml:
name: Release
on:
push:
branches:
- main
permissions:
contents: write
pull-requests: write
jobs:
release:
runs-on: ubuntu-latest
if: >-
!startsWith(github.event.head_commit.message, 'release:') &&
!(startsWith(github.event.head_commit.message, 'Merge') && contains(github.event.head_commit.message, 'release/'))
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- run: npx just-release
env:
CI: 1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Publishing
When just-release runs in post-release mode (i.e., the current commit is a release: X.Y.Z commit), it automatically:
- Publishes packages to their respective registries (npm, crates.io)
- Creates a GitHub release with changelog notes and a
vX.Y.Z tag
You just need to provide the right environment variables and ensure your project builds before just-release runs.
Below are publish workflow examples for each ecosystem.
Publishing npm Packages
just-release detects your package manager (pnpm/yarn/npm) and runs the appropriate publish command. It skips private packages automatically.
Authentication: Set NODE_AUTH_TOKEN or NPM_TOKEN as an environment variable. If neither is set, npm publishing is skipped.
Create .github/workflows/publish.yml:
name: Publish
on:
push:
branches:
- main
workflow_dispatch:
permissions:
contents: write
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
if: >-
startsWith(github.event.head_commit.message, 'release:') ||
(startsWith(github.event.head_commit.message, 'Merge') && contains(github.event.head_commit.message, 'release/'))
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
cache: 'pnpm'
registry-url: 'https://registry.npmjs.org'
- run: pnpm install
- run: pnpm build
- run: npx just-release
env:
CI: 1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM Authentication Options
Option 1: Trusted Publishing (Recommended)
Trusted publishing uses OIDC — no npm tokens required. Works with npmjs.org or any registry that supports it.
- Go to https://www.npmjs.com/package/YOUR-PACKAGE-NAME/access
- Click "Publishing access" → "Add a trusted publisher"
- Configure:
- Source: GitHub Actions
- Repository owner: Your GitHub username/org (case-sensitive!)
- Repository name: Your repo name
- Workflow filename:
publish.yml (optional but recommended)
- Environment: leave blank
Important:
- Your repository must be public for provenance to work
package.json must have a repository field matching your GitHub repo exactly:
- Format:
https://github.com/Owner/repo-name (no git+ prefix, no .git suffix)
- Case-sensitive: Owner name must match exactly (e.g.,
Aeolun, not aeolun)
Option 2: NPM Token
Publishing Rust Crates
just-release runs cargo publish for each non-private crate in workspace order.
Authentication: Set CARGO_REGISTRY_TOKEN as an environment variable. If not set, Rust publishing is skipped.
Create .github/workflows/publish.yml:
name: Publish
on:
push:
branches:
- main
permissions:
contents: write
jobs:
publish:
runs-on: ubuntu-latest
if: >-
startsWith(github.event.head_commit.message, 'release:') ||
(startsWith(github.event.head_commit.message, 'Merge') && contains(github.event.head_commit.message, 'release/'))
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- run: npx just-release
env:
CI: 1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
Note: If your crates have internal dependencies, just-release publishes them in workspace order so dependencies are available on crates.io before dependents are published.
Publishing Go Modules
Go modules don't need an explicit publish step. The go tool resolves modules directly from git tags, and just-release creates vX.Y.Z tags via the GitHub release.
Create .github/workflows/publish.yml:
name: Publish
on:
push:
branches:
- main
permissions:
contents: write
jobs:
publish:
runs-on: ubuntu-latest
if: >-
startsWith(github.event.head_commit.message, 'release:') ||
(startsWith(github.event.head_commit.message, 'Merge') && contains(github.event.head_commit.message, 'release/'))
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
- run: npx just-release
env:
CI: 1
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Users can then install your module with:
go get github.com/your-org/your-module@v1.2.3
PR Body Truncation
GitHub limits PR body text to 65,536 characters. For repositories with many commits since the last release, the PR description is progressively truncated in three tiers:
- Full detail (up to ~40k chars) — Each commit is shown with its hash, type prefix, subject, and full body text
- Summary only (40k–60k chars) — Remaining commits are listed with hash, type prefix, and subject only (no body)
- Counts only (after 60k chars) — Remaining commits are collapsed into a single line: "...and N more commits (X features, Y fixes, Z chores)"
This ensures the PR always stays within GitHub's limit while showing as much detail as possible.
Single-Package vs Monorepo
just-release automatically adapts to your repository structure:
- JavaScript monorepo - If
pnpm-workspace.yaml or package.json workspaces are found, all workspace packages are bumped to the same version
- Rust workspace - If
Cargo.toml has a [workspace] section with members, all crates are bumped together
- Go workspace - If
go.work exists, all modules listed in use directives are tracked together
- Single-package - If no workspace configuration is found, the root package is treated as the only package
- Mixed ecosystems - All ecosystems are detected simultaneously. A repo with both
package.json and Cargo.toml will have all packages from both ecosystems versioned together.
Requirements
- Node.js >= 18
- Git repository with
origin remote pointing to GitHub
- At least one ecosystem manifest at root:
package.json, Cargo.toml, or go.mod
- Public GitHub repository (only required if using trusted publishing with provenance for npm)
License
ISC