-
Notifications
You must be signed in to change notification settings - Fork 0
Monorepos
A Go module nested in a repository releases independently, from tags named
<dir>/vX.Y.Z, where <dir> is its directory relative to the git top level.
repo/
go.mod ← tagged v1.0.0
services/
api/
go.mod ← tagged services/api/v1.2.0
letsgo.mod
worker/
go.mod ← tagged services/worker/v0.4.1
There is nothing to configure. Run any letsgo command inside a nested module and it scopes itself to that module — the working directory is the whole interface.
cd services/api
letsgo plan # sees services/api/v1.2.0, not v1.0.0
letsgo releaseThe tag prefix comes from the module directory and nothing else. No directive sets it, and there is no flag for it, because a prefix that could disagree with the directory is a way to publish a release under a name that does not belong to it.
letsgo tag proposes <prefix>vX.Y.Z. plan accepts only tags of that shape
for a scoped module, and says which directory to cd into when it finds a
prefixed tag belonging to somewhere else.
-
The previous tag is chosen only from tags carrying the same prefix. A root
release whose history contains
web/v9.0.0still compares againstv1.0.0. -
The changelog includes only commits touching
<dir>, excluding nested module directories inside it. -
The API gate compares
<dir>at the previous tag with<dir>at HEAD. -
verifywith no tag picks the highest release carrying the module's prefix. -
yankadds the stripped version'sretractto<dir>/go.mod. -
install.shdownloads fromreleases/download/<tag>/, with the prefix URL-escaped consistently in the formula, the cask and the script.
The manifest gains tag_prefix, which may be empty. tag keeps the prefix and
version is stripped of it:
{ "tag": "services/api/v1.2.0", "version": "1.2.0", "tag_prefix": "services/api/" }The release is titled <dir> vX.Y.Z — services/api v1.2.0 — so a releases page
carrying several modules reads as a list rather than a collision. At the root the
title is just the tag.
GitHub has one "latest" per repository, and a module release quietly taking it breaks every consumer who was following the root.
release latest=auto # the default
release latest=true
release latest=false
Under auto, only a root-scope release may be marked latest.
Scoped builds run with GOWORK=off. A go.work file exists to point the
toolchain at your uncommitted local edits, which is exactly what a release must
not build. With it off, a module in a workspace produces the same digests it
would produce from a clean clone.
For the same reason, plan fails when go.mod has a replace pointing at
a local path, and cites the line. A release that depends on a directory outside
the module is a release nobody else can reproduce.
selfupdate.Options.TagPrefix keeps an updater inside its own module's
releases — without it, a binary from services/api would happily offer to
update itself to the root project's newest tag.
module web in letsgo.mod builds ./web under a root tag: one project,
one version, one tag. That is the right tool when a directory was split out for
dependency hygiene but is still versioned with the repository.
It is not a scoped release, and v1.2.0 is not a version of the module
.../web, so go install .../web/cmd/x@v1.2.0 does not resolve and the proxy
has nothing to warm. letsgo raises a plan Warn saying so and skips the proxy
warm rather than failing on it every time.
If the nested module wants its own version, give it its own tag prefix — that is what scoped releases are.
A tag names one module, so every other matrix entry finds no release of its own at that commit. Guard on the prefix:
on:
push:
tags: ["*/v*"]
jobs:
release:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
module: [services/api, services/worker]
steps:
- uses: actions/checkout@v7
with: { fetch-depth: 0 }
- uses: actions/setup-go@v7
with: { go-version-file: '${{ matrix.module }}/go.mod' }
- uses: danielriddell21/letsgo-action@v1
id: release
if: startsWith(github.ref_name, matrix.module)
with:
working-directory: ${{ matrix.module }}
- run: echo "released ${{ steps.release.outputs.tag }}"
if: steps.release.outputs.tag != ''The action's tag output carries the prefix, so a downstream step knows which
module it just published.
Working out which modules changed, and building the matrix from that, is left to CI. letsgo releases the module you point it at; deciding which ones to point it at is a repository's own question and not a property of any one release.
With an empty scope every command behaves exactly as it does in a single-module repository — same tags, same manifest fields, same output.
Start here
Releasing
Checking
Extending
Running it
About