Skip to content

dijdzv/moon-release

Repository files navigation

moon-release

Automated release management tool for MoonBit projects. Inspired by release-plz, optimized for the MoonBit ecosystem.

Features

  • 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

Table of Contents

GitHub Actions

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.

Required Setup

1. Repository Permissions

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.

2. Workflow File Setup

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.yml

The 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 history
  • GITHUB_TOKEN - Automatically provided by GitHub Actions
  • Supports dry-run and build-only modes 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 build and release jobs 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 use workflow_dispatch with build-only to trigger the build and release jobs manually.

Initial Setup

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 --tags

Alternatively, 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.

Publishing to mooncakes.io (Optional)

To also publish to mooncakes.io when releasing, add mooncakes credentials to the release job.

1. Configure GitHub Secrets

First, authenticate locally and register the credentials as GitHub Secrets:

# Authenticate with mooncakes.io (creates ~/.moon/credentials.json)
moon login

If 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

2. Workflow Configuration

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.json may have full account permissions. Use at your own risk with this understanding.

Publishing to npm (Optional)

moon-release uses npm Trusted Publishing (OIDC) for secure, tokenless authentication.

1. Configure Trusted Publishing on npmjs.com

  1. Go to npmjs.com → your package → SettingsConfigure Trusted Publishing
  2. 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

2. Update Configuration

Enable npm publishing in release.json:

{
  "npm_publish": true,
  "npm_build_command": "moon build --target js"
}

3. Update Workflow

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

Execution Order

When npm_publish is enabled, moon-release executes in this order:

  1. Build JS target - Runs npm_build_command (default: moon build --target js)
  2. Create Git tag
  3. Create GitHub Release
  4. Publish to mooncakes.io (if moon_publish: true)
  5. Publish to npm

If the JS build fails, no tag or release is created (atomic operation).

Configuration Summary

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

Workflow Template

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

Configuration

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": []
}

Configuration Options

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.

Template Variables

Variable Description
{{ version }} New version number
{{ changelog }} Auto-generated release notes (git_release_body only)

Conventional Commits

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.

API Compatibility Check (semver_check)

Setting semver_check: true detects breaking API changes before release.

{
  "semver_check": true
}

Detected Changes

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)

How It Works

  1. Checkout latest tag → Generate API with moon doc
  2. Return to current code → Generate API with moon doc
  3. Compare the two package_data.json files
  4. Display warning if breaking changes are found

Monorepo Support

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
    }
  ]
}

Versioning Strategies

Independent (default)

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 major

Fixed (bump_all_packages or --bump-all)

All 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-all
Config 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: --bump controls the bump type (major/minor/patch), while --bump-all controls the bump scope (changed-only vs all packages).

version_group

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.

CLI Usage

moon-release can also be used as a standalone CLI tool for local development and debugging.

Installation

From mooncakes.io (recommended)

moon install dijdzv/moon-release

Install script

curl -fsSL https://raw.githubusercontent.com/dijdzv/moon-release/main/install.sh | bash

Pin 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.0

Installs 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 | bash

Download binary

Download 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-release

Build from source

git 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-release

Commands

Typical workflow: checkupdaterelease-prrelease

moon-release check

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 format

moon-release update

Update 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 format

moon-release release-pr

Create 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-run

moon-release release

Create 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-run

moon-release set-version

Manually set the version.

moon-release set-version 1.0.0

moon-release init

Create the configuration file release.json.

moon-release init
moon-release init --force  # Overwrite

moon-release generate-completions

Generate 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.fish

moon-release generate-schema

Generate JSON Schema for the configuration file. Can be used for IDE autocompletion.

moon-release generate-schema > release-schema.json

Planned Features

  • 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)

Differences from release-plz

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

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors