Automated release management tool for MoonBit projects. Inspired by release-plz, optimized for the MoonBit ecosystem.
- Conventional Commits - Automatically determine semantic version from commits
- GitHub Release - Automatic creation of tags and release notes
- Release PR - Automatic creation and update of release pull requests
- mooncakes.io - Automatic publishing to the MoonBit package registry
- npm - Automatic publishing to the npm registry
- API Compatibility Check - semver-checks for breaking change detection
- Monorepo Support - Version synchronization via version_group
- GitHub Actions (CI)
- Configuration
- Conventional Commits
- API Compatibility Check
- Monorepo Support
- CLI Usage
- Planned Features
- Differences from release-plz
This is the primary way to use moon-release. Set up CI/CD to automatically create release PRs, create GitHub Releases with tags, and optionally publish to mooncakes.io / npm.
Go to Settings → Actions → General → Workflow permissions:
- Select "Read and write permissions"
- Check "Allow GitHub Actions to create and approve pull requests"
Without these settings, moon-release cannot create release PRs.
Copy templates/release.yml to .github/workflows/release.yml and change BINARY_NAME to your project's binary name.
mkdir -p .github/workflows
curl -fsSL -o .github/workflows/release.yml \
https://raw.githubusercontent.com/dijdzv/moon-release/main/templates/release.ymlThe template includes three jobs:
| Job | Trigger | Description |
|---|---|---|
release-pr |
Push to main (non-release commits) | Creates/updates a release PR with version bump |
build |
Release PR merged | Builds binaries for Linux, macOS, Windows |
release |
After build completes |
Creates Git tag + GitHub Release + uploads binaries + publishes to mooncakes.io |
Key points:
fetch-depth: 0- Required to analyze commit historyGITHUB_TOKEN- Automatically provided by GitHub Actions- Supports
dry-runandbuild-onlymodes via manual workflow dispatch
With this setup, merging a release PR will automatically create a Git tag, a GitHub Release with release notes, and upload cross-platform binaries. No additional secrets are needed for the base workflow.
Important: The workflow template assumes release PRs are squash merged. The
buildandreleasejobs detect merged release PRs by matching the commit message against the PR title pattern (e.g.chore: release v...). With squash merge, the PR title becomes the commit message automatically. If you use a different merge strategy or edit the PR title before merging, these jobs will not trigger. In that case, create a Git tag (git tag v<version>+ push) to ensure moon-release can track releases, and useworkflow_dispatchwithbuild-onlyto trigger the build and release jobs manually.
moon-release determines the next version by analyzing commits since the last release. When no tag or release commit exists yet, it analyzes all commits in the repository — which may produce unexpectedly large version bumps.
Create a Git tag as a baseline before the first run:
# Tag the commit that represents your latest release
git tag v0.1.0
git push origin --tags
# If the release was made in a past commit
git tag v0.1.0 <commit-hash>
git push origin --tagsAlternatively, if migrating from another release tool, set bootstrap_sha in release.json:
{
"bootstrap_sha": "<commit-hash>"
}bootstrap_sha acts as a lower bound — tags or release commits after it take precedence automatically, so you can leave it in the config after the first release without side effects.
To also publish to mooncakes.io when releasing, add mooncakes credentials to the release job.
First, authenticate locally and register the credentials as GitHub Secrets:
# Authenticate with mooncakes.io (creates ~/.moon/credentials.json)
moon loginIf you have jq and gh installed, you can register the secrets in one step:
#!/usr/bin/env bash
set -euo pipefail
CREDS="$HOME/.moon/credentials.json"
if [ ! -f "$CREDS" ]; then
echo "Error: $CREDS not found. Run 'moon login' first."
exit 1
fi
TOKEN=$(jq -r '.token' "$CREDS")
USERNAME=$(jq -r '.username' "$CREDS")
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
echo "Error: token not found in $CREDS"
exit 1
fi
if [ -z "$USERNAME" ] || [ "$USERNAME" = "null" ]; then
echo "Error: username not found in $CREDS"
exit 1
fi
echo "Setting MOONCAKES_TOKEN..."
echo "$TOKEN" | gh secret set MOONCAKES_TOKEN
echo "Setting MOONCAKES_USERNAME..."
echo "$USERNAME" | gh secret set MOONCAKES_USERNAME
echo "Done! Mooncakes credentials registered as GitHub Secrets."Or set them manually at Settings → Secrets and variables → Actions → New repository secret:
| Secret Name | Value |
|---|---|
MOONCAKES_TOKEN |
The token value from ~/.moon/credentials.json |
MOONCAKES_USERNAME |
The username value from ~/.moon/credentials.json |
The template already includes the mooncakes credentials setup step. Just configure the GitHub Secrets above — no workflow changes are needed.
No changes to release.json are needed — moon_publish is true by default.
Warning: mooncakes.io does not currently provide limited-scope tokens for CI use. The token in
~/.moon/credentials.jsonmay have full account permissions. Use at your own risk with this understanding.
moon-release uses npm Trusted Publishing (OIDC) for secure, tokenless authentication.
- Go to npmjs.com → your package → Settings → Configure Trusted Publishing
- Add a new trusted publisher:
- Repository owner: Your GitHub username or organization
- Repository name: Your repository name
- Workflow filename:
release.yml(or your workflow file) - Environment: (leave empty or set if using environments)
If this is a new package, you need to publish it once manually first:
npm publish --access public
Enable npm publishing in release.json:
{
"npm_publish": true,
"npm_build_command": "moon build --target js"
}Add id-token: write permission and a Node.js setup step to the release job:
release:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write # Required for npm Trusted Publishing
# ...
steps:
# ... (after Checkout, before Create Release)
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'Trusted Publishing is more secure than access tokens:
- No secrets to store or rotate
- Short-lived tokens generated per-workflow
- Scoped to specific repository and workflow
When npm_publish is enabled, moon-release executes in this order:
- Build JS target - Runs
npm_build_command(default:moon build --target js) - Create Git tag
- Create GitHub Release
- Publish to mooncakes.io (if
moon_publish: true) - Publish to npm
If the JS build fails, no tag or release is created (atomic operation).
| Setting | Required | Where to Configure |
|---|---|---|
| Repository write permissions | Yes | Settings → Actions → General |
| Allow PR creation | Yes | Settings → Actions → General |
GITHUB_TOKEN |
Yes (auto) | Automatically provided |
MOONCAKES_TOKEN |
For mooncakes publish | Settings → Secrets |
MOONCAKES_USERNAME |
For mooncakes publish | Settings → Secrets |
id-token: write |
For npm publish | Workflow permissions |
| Trusted Publishing | For npm publish | npmjs.com package settings |
See templates/release.yml for a complete example.
Recommended: Pin the moon-release version in your workflow to avoid unexpected breaking changes. The template uses minor version pinning (
bash -s -- 0.3) which auto-updates patch releases. Check Releases for the latest version.To always use the latest version instead, remove the version argument:
curl -fsSL https://raw.githubusercontent.com/dijdzv/moon-release/main/install.sh | bash
Customize behavior with release.json.
{
"pr_title": "chore: release v{{ version }}",
"pr_draft": false,
"pr_labels": ["release"],
"pr_body": "## Release v{{ version }}",
"pr_branch_prefix": "release/",
"base_branch": "main",
"allow_prerelease": false,
"git_tag_enable": true,
"git_tag_name": "v{{ version }}",
"git_release_enable": true,
"git_release_draft": false,
"git_release_name": "Release v{{ version }}",
"git_release_body": "{{ changelog }}",
"git_release_latest": true,
"moon_publish": true,
"moon_publish_frozen": false,
"moon_publish_timeout": 300,
"npm_publish": false,
"npm_build_command": null,
"npm_publish_provenance": true,
"npm_publish_timeout": 300,
"semver_check": false,
"registry_check": false,
"allow_dirty": false,
"custom_major_increment_regex": null,
"custom_minor_increment_regex": null,
"max_analyze_commits": null,
"bump_all_packages": false,
"bootstrap_sha": null,
"packages": []
}| Option | Default | Description |
|---|---|---|
pr_title |
"chore: release v{{ version }}" |
PR title template |
pr_draft |
false |
Create as draft PR |
pr_labels |
[] |
Labels to add to PR |
pr_body |
- | PR body template |
pr_branch_prefix |
"release/" |
Release branch prefix |
base_branch |
"main" |
PR base branch |
allow_prerelease |
false |
Allow prerelease versions (e.g., --prerelease alpha) |
git_tag_enable |
true |
Whether to create Git tag |
git_tag_name |
"v{{ version }}" |
Tag name template |
git_release_enable |
true |
Whether to create GitHub Release |
git_release_draft |
false |
Create as draft release |
git_release_name |
"Release v{{ version }}" |
Release name template |
git_release_body |
null |
Release body template (null for auto-generate) |
git_release_latest |
true |
Mark GitHub Release as latest |
moon_publish |
true |
Whether to publish to mooncakes.io |
moon_publish_frozen |
false |
Use moon publish --frozen |
moon_publish_timeout |
300 |
Publish timeout in seconds |
npm_publish |
false |
Whether to publish to npm registry |
npm_build_command |
null |
Custom build command before npm publish (default: moon build --target js). Note: split by spaces, paths with spaces are not supported. |
npm_publish_provenance |
true |
Use --provenance flag for npm publish. Set to false for local or non-GitHub CI publishing. |
npm_publish_timeout |
300 |
npm publish timeout in seconds |
semver_check |
false |
Run API compatibility check |
registry_check |
false |
Check registry version before publish |
allow_dirty |
false |
Allow uncommitted changes |
custom_major_increment_regex |
null |
Custom substring pattern for major bump (matched against commit description) |
custom_minor_increment_regex |
null |
Custom substring pattern for minor bump (matched against commit description) |
max_analyze_commits |
null |
Maximum number of commits to analyze |
bump_all_packages |
false |
Bump all packages uniformly in monorepo mode (fixed versioning) |
bootstrap_sha |
null |
Starting commit SHA for commit analysis. Acts as a lower bound — tags and release commits after it take precedence. See Initial Setup. |
| Variable | Description |
|---|---|
{{ version }} |
New version number |
{{ changelog }} |
Auto-generated release notes (git_release_body only) |
moon-release parses Conventional Commits to automatically determine the version.
| Commit Type | Bump | Example |
|---|---|---|
feat |
Minor | feat: add new feature |
fix |
Patch | fix: resolve bug |
feat! / fix! |
Major | feat!: breaking change |
BREAKING CHANGE: |
Major | In footer |
Other types (docs, style, refactor, perf, test, build, ci, chore) do not affect the version.
Setting semver_check: true detects breaking API changes before release.
{
"semver_check": true
}| Change | Impact |
|---|---|
| Removal of pub type/function/method | Breaking (Major) |
| Signature change | Breaking (Major) |
| Struct field removal/change | Breaking (Major) |
| Addition of new pub type/function | Addition (Minor) |
- Checkout latest tag → Generate API with
moon doc - Return to current code → Generate API with
moon doc - Compare the two
package_data.jsonfiles - Display warning if breaking changes are found
For monorepos with multiple packages, configure each package individually with packages.
{
"packages": [
{
"name": "core",
"path": "packages/core",
"moon_publish": true,
"npm_publish": true,
"version_group": "main"
},
{
"name": "utils",
"path": "packages/utils",
"moon_publish": true,
"npm_publish": false,
"version_group": "main"
},
{
"name": "internal",
"path": "packages/internal",
"moon_publish": false,
"npm_publish": false
}
]
}Only packages with changes are bumped. Each package gets its own bump type based on its commits.
# Only changed packages are bumped
moon-release update
# Force major bump on changed packages only
moon-release update --bump majorAll packages are bumped uniformly using the highest bump type found across changed packages.
{
"bump_all_packages": true
}Or use the CLI flag for one-time override:
# Bump all packages (uses max bump type from changed packages)
moon-release update --bump-all
# Force major bump on ALL packages
moon-release update --bump major --bump-allConfig bump_all_packages |
CLI | Behavior |
|---|---|---|
false |
(none) | Changed packages only, auto bump type |
false |
--bump major |
Changed packages only, forced major |
false |
--bump-all |
All packages, max bump type from changes |
false |
--bump major --bump-all |
All packages, forced major |
true |
(none) | All packages, max bump type from changes |
true |
--bump major |
All packages, forced major |
Note:
--bumpcontrols the bump type (major/minor/patch), while--bump-allcontrols the bump scope (changed-only vs all packages).
Packages with the same version_group are aligned to the largest bump type within the group.
Example: If core has a breaking change and utils has a feat, both will receive a major bump.
moon-release can also be used as a standalone CLI tool for local development and debugging.
moon install dijdzv/moon-releasecurl -fsSL https://raw.githubusercontent.com/dijdzv/moon-release/main/install.sh | bashPin to a specific version:
# Pin to minor version (recommended: auto-updates patch releases)
curl -fsSL https://raw.githubusercontent.com/dijdzv/moon-release/main/install.sh | bash -s -- 0.3
# Pin to exact version
curl -fsSL https://raw.githubusercontent.com/dijdzv/moon-release/main/install.sh | bash -s -- 0.3.0Installs to ~/.local/bin by default. Override with INSTALL_DIR:
INSTALL_DIR=/usr/local/bin curl -fsSL https://raw.githubusercontent.com/dijdzv/moon-release/main/install.sh | bashDownload pre-built binaries from GitHub Releases:
# Linux (x86_64)
curl -fsSL -o moon-release https://github.com/dijdzv/moon-release/releases/latest/download/moon-release-linux-x86_64
chmod +x moon-release
# macOS (Apple Silicon)
curl -fsSL -o moon-release https://github.com/dijdzv/moon-release/releases/latest/download/moon-release-macos-arm64
chmod +x moon-releasegit clone https://github.com/dijdzv/moon-release
cd moon-release
moon build --target native --release
cp _build/native/release/build/moon-release.exe ~/.local/bin/moon-releaseTypical workflow: check → update → release-pr → release
Check the current state and display recommended version bump.
moon-release check
moon-release check --verbose # Show commit details
moon-release check -o json # Output in JSON formatUpdate the version in moon.mod.json (no commit/push).
moon-release update # Auto-determine
moon-release update --bump major # Force major bump (changed packages only)
moon-release update --bump-all # Bump all packages (monorepo)
moon-release update --bump major --bump-all # Force major bump on all packages
moon-release update --dry-run # Preview only
moon-release update -o json # Output in JSON formatCreate or update a release Pull Request.
moon-release release-pr
moon-release release-pr --bump major # Force major bump
moon-release release-pr --bump-all # Include all packages
moon-release release-pr --bump major --bump-all # Force major on all packages
moon-release release-pr --dry-runCreate Git tag and GitHub Release, and publish to mooncakes.io.
moon-release release
moon-release release --bump major # Force major bump
moon-release release --bump-all # Include all packages
moon-release release --bump major --bump-all # Force major on all packages
moon-release release --prerelease alpha # Prerelease
moon-release release --dry-runManually set the version.
moon-release set-version 1.0.0Create the configuration file release.json.
moon-release init
moon-release init --force # OverwriteGenerate shell completion scripts.
moon-release generate-completions bash > ~/.local/share/bash-completion/completions/moon-release
moon-release generate-completions zsh > ~/.zfunc/_moon-release
moon-release generate-completions fish > ~/.config/fish/completions/moon-release.fishGenerate JSON Schema for the configuration file. Can be used for IDE autocompletion.
moon-release generate-schema > release-schema.json- Per-package release PRs - Create separate release PRs for each package (or version_group) in a monorepo, instead of bundling all changes into a single PR
- Per-package release - Create tags and publish each package independently when its release PR is merged
- WebAssembly registry publishing - Publish to a Wasm component registry (e.g., warg, wa.dev)
| Feature | release-plz | moon-release |
|---|---|---|
| Platform | GitHub, GitLab, Gitea | GitHub only |
| CHANGELOG | Auto-generated with git-cliff | None (use GitHub Release instead) |
| Dependency Update | cargo update |
None (MoonBit uses minimal version selection) |
| API Compatibility Check | cargo-semver-checks |
Built-in (uses moon doc) |
| Config Format | TOML | JSON |
| Registry | crates.io | mooncakes.io |
MIT