monorel

Manage independently-versioned modules and releases in a single repository.

  • initializes monorepo-compatible .goreleaser.yaml
  • bumps versions correctly with git (tags by commit to amodule)
  • releases with goreleaser and gh (multi-arch, cross-compiled)
  • single or multiple binaries per release
monorel release  --recursive ./tools/monorel/

Use git tags like this:

git tag:

v0.1.0
v0.1.1
cmd/sql-migrate/v1.0.2
cmd/sql-migrate/v2.0.3
io/transform/gsheet2csv/v1.0.4
io/transform/gsheet2csv/v1.0.5
tools/monorel/v0.6.5
tools/monorel/v1.0.0

to manage packaged releases for a project like this:

./                                  # v0.1.1
├── go.mod                          # (module-only)
├── cmd/
│   └── sql-migrate/                # cmd/sql-migrate/v2.0.3
│           ├── .goreleaser.yaml    # (module for binary)
│           └── go.mod
├── io/
│   └── transform/
│       └── gsheet2csv/             # io/transform/gsheet2csv/v1.0.5
│           ├── .goreleaser.yaml    # (module with 3 binaries)
│           ├── go.mod
│           └── cmd/
│               ├── gsheet2csv/
│               ├── gsheet2env/
│               └── gsheet2tsv/
└── tools/
    └── monorel/                    # tools/monorel/v1.0.0
        ├── .goreleaser.yaml        # (module for binaries)
        └── go.mod

Install

monorel also uses

Linux & macOS

webi monorel
source ~/.config/envman/PATH.env

Go

webi go goreleaser gh
source ~/.config/envman/PATH.env
go install github.com/therootcompany/golib/tools/monorel@latest

Usage

  1. init
    monorel init --recursive ./
    
    Generates a .goreleaser.yaml in every module directory (next to each go.mod) that contains at least one command package (package main), configured to build and release all discovered binaries together (per each target build release package). Also tags an initial v0.1.0 if none is present. Uses a hard-coded command name with the {{ .Env.VERSION }} placeholder to sidestep monorepo config issues.
  2. bump
    monorel bump --recursive ./
    
    Uses git log -- <path> and git tag to tag NOT the latest commit of the repo, but the most recent commit changing in the module, with the next semver version.
  3. release
    monorel release --recursive ./
    
    Uses goreleaser to cross-compile a wide range of binaries and then creates a release with gh (the GitHub CLI) - first as a draft, then uploading the assets, then finalizing the release as public (non-draft).

Init

Creates a .goreleaser.yaml for the go.mod of each package main.

monorel init --recursive ./
Flag Default Description
--almost-all off Widen build matrix to include esoteric platforms (see Build matrix)
--android-ndk off Adds additional Android build entries (ones that require CGO_ENABLED=1 and the Android NDK)
--dry-run off Print what would happen without writing or tagging
--ios off Add an iOS build entry (requires CGO_ENABLED=1 and Xcode)
--recursive off Find all main packages recursively under each path

Bump

Maths out the previous and proper next semver release.

monorel bump --recursive ./
Flag Default Description
--dry-run off Print the tag that would be created without creating it
--force off If no new commits, create an empty bump commit and tag it
--recursive off Find all main packages recursively

Release

Build all binaries, puts them in common package formats (.tar.gz, .tar.zst, .zip), creates a GitHub Release, uploads the packages, and makes the release public.

monorel release --recursive ./
Flag Default Description
--draft off Keep the GitHub release in draft state after uploading
--dry-run off Show each step without running it
--prerelease off Keep the GitHub release marked as pre-release even for clean tags
--recursive off Find all main packages recursively
--yes off Run all steps without prompting

Interactive Prompts

  1. Create git tag — skipped when a tag for this version already exists.
  2. Push commits and tagsgit push && git push --tags.
  3. Build with goreleasergoreleaser release --clean --skip=validate,announce with VERSION=<semver> set in the environment.
  4. Create GitHub release — always created as --draft --prerelease so artifacts can be uploaded before the release goes public.
  5. Upload artifactsgh release upload with all .tar.gz, .tar.zst, .zip, and checksum files from dist/.
  6. Finalize release visibility — removes --draft and/or --prerelease flags as appropriate (see --draft / --prerelease below).

Build matrix

The generated .goreleaser.yaml targets CGO_ENABLED=0 for by default. Platforms that require CGO or a special toolchain are only included when the corresponding flag is given.

The default matrix contains:

Values
goos darwin freebsd js linux netbsd openbsd wasip1 windows
goarch amd64 arm arm64 mips64le mipsle ppc64le riscv64 wasm
goamd64 v1 v2
goamd64 v3 v4
goarm 6 7

--almost-all adds less-commonly-targeted platforms:

Added values
goos aix dragonfly illumos plan9 solaris
goarch 386 loong64 mips mips64 ppc64 s390x
goamd64 v1 v2 v3 v4

--ios adds an ios build entry, which requires the Xcode toolchain:

- id: <binary>-ios
  env:
      - CGO_ENABLED=1
  goos:
      - ios
  goarch:
      - arm64

--android-ndk adds a build entry for Android/arm64, which requires the Android NDK:

- id: <binary>-android
  env:
      - CGO_ENABLED=1
  goos:
      - android
  goarch:
      - arm64

vs GoReleaser Pro

This isn't a replacement for GoRoleaser Pro.

Although I wouldn't have created it if multi-module version management were available in a (free or paid) version of GoReleaser without a subscription, this handles initialization, versioning, and releasing in the way that I've wanted for my workflow (and this repository).