mirror of
https://github.com/therootcompany/golib.git
synced 2026-03-02 23:57:59 +00:00
feat(monorel): require binary-path args; support multi-binary modules
The tool now requires at least one positional argument — the path(s) to
the Go main package(s) to build — and must be run from the module root
(the directory containing go.mod).
# single binary (module root is the main package)
monorel .
# multiple binaries under one module
monorel ./cmd/gsheet2csv ./cmd/gsheet2tsv ./cmd/gsheet2env
Changes:
- Add `binary` struct {name, mainPath} and `parseBinaries()`
- "." is special-cased: binary name is taken from the CWD, not "."
- filepath.Clean's "./"-stripping is undone so goreleaser sees an
explicit relative path (./cmd/foo not cmd/foo)
- `goreleaserYAML` now takes `projectName + []binary`
- Each binary gets its own `builds` entry (with `id:` and `main:`)
and its own `archives` entry (with `ids:` to link it to the build)
- `main:` is omitted when mainPath is "." (goreleaser default)
- Checksum is named <projectName>_VERSION_checksums.txt
- `printScript` takes `projectName + []binary`
- Summary line says "Binaries:" (plural) when more than one
- Upload step globs tar.gz + zip for every binary, then the checksum
- Require go.mod in CWD; error out with usage message when no args given
Also regenerates cmd/tcpfwd/.goreleaser.yaml via the new code path
(adds `id: tcpfwd` to builds/archives; no functional change otherwise).
This commit is contained in:
parent
3676ef0f47
commit
12c025e5e2
@ -1,13 +1,17 @@
|
|||||||
// monorel: Monorepo Release Tool
|
// monorel: Monorepo Release Tool
|
||||||
//
|
//
|
||||||
// Run from a module subdirectory inside a git repo to:
|
// Run from a module directory and pass the paths to each binary's main
|
||||||
// - Generate (or update) .goreleaser.yaml for the module
|
// package. Supports both single-binary and multi-binary modules.
|
||||||
// - Print a ready-to-review bash release script to stdout
|
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
//
|
//
|
||||||
|
// # Single binary (path to the main package, or "." for module root)
|
||||||
// cd cmd/tcpfwd
|
// cd cmd/tcpfwd
|
||||||
// go run github.com/therootcompany/golib/tools/monorel
|
// monorel .
|
||||||
|
//
|
||||||
|
// # Multiple binaries under one module
|
||||||
|
// cd io/transform/gsheet2csv
|
||||||
|
// monorel ./cmd/gsheet2csv ./cmd/gsheet2tsv ./cmd/gsheet2env
|
||||||
//
|
//
|
||||||
// Install:
|
// Install:
|
||||||
//
|
//
|
||||||
@ -18,28 +22,58 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// binary describes one Go main package to build and release.
|
||||||
|
type binary struct {
|
||||||
|
name string // last path component, e.g. "gsheet2csv"
|
||||||
|
mainPath string // path relative to module dir, e.g. "./cmd/gsheet2csv" or "."
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// 1. Module prefix relative to .git root (e.g., "cmd/tcpfwd")
|
args := os.Args[1:]
|
||||||
|
if len(args) == 0 {
|
||||||
|
fmt.Fprintln(os.Stderr, "usage: monorel <binary-path> [<binary-path>...]")
|
||||||
|
fmt.Fprintln(os.Stderr, "")
|
||||||
|
fmt.Fprintln(os.Stderr, "Run from the module directory (where go.mod lives).")
|
||||||
|
fmt.Fprintln(os.Stderr, "Use '.' when the module root is itself the main package.")
|
||||||
|
fmt.Fprintln(os.Stderr, "")
|
||||||
|
fmt.Fprintln(os.Stderr, "Examples:")
|
||||||
|
fmt.Fprintln(os.Stderr, " monorel . # single binary at root")
|
||||||
|
fmt.Fprintln(os.Stderr, " monorel ./cmd/foo ./cmd/bar ./cmd/baz # multiple binaries")
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must run from the module directory so goreleaser can find go.mod and
|
||||||
|
// so that .goreleaser.yaml is written next to it.
|
||||||
|
if _, err := os.Stat("go.mod"); err != nil {
|
||||||
|
fatalf("no go.mod in current directory; run monorel from the module root")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Parse binary descriptors from positional args.
|
||||||
|
bins := parseBinaries(args)
|
||||||
|
|
||||||
|
// 2. Module prefix relative to the .git root (e.g., "io/transform/gsheet2csv").
|
||||||
|
// This is also the tag prefix, e.g. "io/transform/gsheet2csv/v1.2.3".
|
||||||
prefix := mustRun("git", "rev-parse", "--show-prefix")
|
prefix := mustRun("git", "rev-parse", "--show-prefix")
|
||||||
prefix = strings.TrimSuffix(prefix, "/")
|
prefix = strings.TrimSuffix(prefix, "/")
|
||||||
if prefix == "" {
|
if prefix == "" {
|
||||||
fatalf("run monorel from a module subdirectory, not the repo root")
|
fatalf("run monorel from a module subdirectory, not the repo root")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Binary name = last path component of prefix
|
// Project name = last path component (used in checksum filename and release title).
|
||||||
prefixParts := strings.Split(prefix, "/")
|
prefixParts := strings.Split(prefix, "/")
|
||||||
binName := prefixParts[len(prefixParts)-1]
|
projectName := prefixParts[len(prefixParts)-1]
|
||||||
|
|
||||||
// 3. Normalised GitHub repo path (e.g., "github.com/therootcompany/golib")
|
// 3. Normalised GitHub repo path (e.g., "github.com/therootcompany/golib").
|
||||||
rawURL := mustRun("git", "remote", "get-url", "origin")
|
rawURL := mustRun("git", "remote", "get-url", "origin")
|
||||||
repoPath := normalizeGitURL(rawURL)
|
repoPath := normalizeGitURL(rawURL)
|
||||||
|
|
||||||
// 4. Collect tags matching "<prefix>/v*" and sort by semver
|
// 4. Collect and semver-sort tags matching "<prefix>/v*".
|
||||||
rawTags := run("git", "tag", "--list", prefix+"/v*")
|
rawTags := run("git", "tag", "--list", prefix+"/v*")
|
||||||
var tags []string
|
var tags []string
|
||||||
for _, t := range strings.Split(rawTags, "\n") {
|
for _, t := range strings.Split(rawTags, "\n") {
|
||||||
@ -61,10 +95,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Detect dirty working tree (uncommitted / untracked files in this dir)
|
// 5. Detect dirty working tree (uncommitted / untracked files under CWD).
|
||||||
isDirty := run("git", "status", "--porcelain", "--", ".") != ""
|
isDirty := run("git", "status", "--porcelain", "--", ".") != ""
|
||||||
|
|
||||||
// 6. Count commits since latestTag that touch this directory
|
// 6. Count commits since latestTag that touch the module directory.
|
||||||
var commitCount int
|
var commitCount int
|
||||||
if latestTag != "" {
|
if latestTag != "" {
|
||||||
logOut := run("git", "log", "--oneline", latestTag+"..HEAD", "--", ".")
|
logOut := run("git", "log", "--oneline", latestTag+"..HEAD", "--", ".")
|
||||||
@ -73,37 +107,70 @@ func main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. Derive version string, full tag, and release flags
|
// 7. Derive version string, full tag, and release flags.
|
||||||
version, currentTag, isPreRelease, needsNewTag := computeVersion(
|
version, currentTag, isPreRelease, needsNewTag := computeVersion(
|
||||||
prefix, latestTag, commitCount, isDirty,
|
prefix, latestTag, commitCount, isDirty,
|
||||||
)
|
)
|
||||||
|
|
||||||
// For release notes: prevTag is the last tag that's not the one we're releasing.
|
// For release notes prevTag is the last stable tag before the one we're
|
||||||
// When pre-releasing, the last stable tag is latestTag (not prevStableTag).
|
// releasing. For a pre-release the "stable baseline" is latestTag.
|
||||||
prevTag := prevStableTag
|
prevTag := prevStableTag
|
||||||
if isPreRelease {
|
if isPreRelease {
|
||||||
prevTag = latestTag
|
prevTag = latestTag
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. Write .goreleaser.yaml
|
// 8. Write .goreleaser.yaml.
|
||||||
yamlContent := goreleaserYAML(binName)
|
yamlContent := goreleaserYAML(projectName, bins)
|
||||||
if err := os.WriteFile(".goreleaser.yaml", []byte(yamlContent), 0o644); err != nil {
|
if err := os.WriteFile(".goreleaser.yaml", []byte(yamlContent), 0o644); err != nil {
|
||||||
fatalf("writing .goreleaser.yaml: %v", err)
|
fatalf("writing .goreleaser.yaml: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Fprintln(os.Stderr, "wrote .goreleaser.yaml")
|
fmt.Fprintln(os.Stderr, "wrote .goreleaser.yaml")
|
||||||
|
|
||||||
// 9. Emit release script to stdout
|
// 9. Emit the release script to stdout.
|
||||||
headSHA := mustRun("git", "rev-parse", "HEAD")
|
headSHA := mustRun("git", "rev-parse", "HEAD")
|
||||||
printScript(binName, version, currentTag, prevTag, repoPath, headSHA,
|
printScript(projectName, bins, version, currentTag, prevTag, repoPath, headSHA,
|
||||||
isPreRelease, needsNewTag, isDirty)
|
isPreRelease, needsNewTag, isDirty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseBinaries converts positional CLI arguments into binary descriptors.
|
||||||
|
//
|
||||||
|
// Each arg is the path to a Go main package, relative to the module directory.
|
||||||
|
// "." is special-cased: the binary name is taken from the current working
|
||||||
|
// directory name rather than from ".".
|
||||||
|
func parseBinaries(args []string) []binary {
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
bins := make([]binary, 0, len(args))
|
||||||
|
for _, arg := range args {
|
||||||
|
// Normalise to a clean, forward-slash path.
|
||||||
|
clean := filepath.ToSlash(filepath.Clean(arg))
|
||||||
|
|
||||||
|
var name string
|
||||||
|
if clean == "." {
|
||||||
|
name = filepath.Base(cwd) // e.g., "tcpfwd" from working dir name
|
||||||
|
} else {
|
||||||
|
name = filepath.Base(clean) // e.g., "gsheet2csv"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore "./" prefix that filepath.Clean strips, so goreleaser sees
|
||||||
|
// an explicit relative path (e.g. "./cmd/gsheet2csv" not "cmd/gsheet2csv").
|
||||||
|
mainPath := clean
|
||||||
|
if clean != "." && !strings.HasPrefix(clean, "./") && !strings.HasPrefix(clean, "../") {
|
||||||
|
mainPath = "./" + clean
|
||||||
|
}
|
||||||
|
|
||||||
|
bins = append(bins, binary{name: name, mainPath: mainPath})
|
||||||
|
}
|
||||||
|
return bins
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Version computation ────────────────────────────────────────────────────
|
||||||
|
|
||||||
// computeVersion returns (version, fullTag, isPreRelease, needsNewTag).
|
// computeVersion returns (version, fullTag, isPreRelease, needsNewTag).
|
||||||
//
|
//
|
||||||
// Examples:
|
// Examples:
|
||||||
//
|
//
|
||||||
// At "cmd/tcpfwd/v1.1.0", no changes → ("1.1.0", "cmd/tcpfwd/v1.1.0", false, false)
|
// At "cmd/tcpfwd/v1.1.0", clean → ("1.1.0", "cmd/tcpfwd/v1.1.0", false, false)
|
||||||
// 3 commits past "cmd/tcpfwd/v1.1.0" → ("1.1.1-pre3", "cmd/tcpfwd/v1.1.1-pre3", true, true)
|
// 3 commits past v1.1.0, clean → ("1.1.1-pre3", "cmd/tcpfwd/v1.1.1-pre3", true, true)
|
||||||
// dirty, 0 new commits → ("1.1.1-pre1.dirty","cmd/tcpfwd/v1.1.1-pre1.dirty", true, false)
|
// dirty, 0 new commits → ("1.1.1-pre1.dirty","cmd/tcpfwd/v1.1.1-pre1.dirty", true, false)
|
||||||
func computeVersion(prefix, latestTag string, commitCount int, isDirty bool) (version, currentTag string, isPreRelease, needsNewTag bool) {
|
func computeVersion(prefix, latestTag string, commitCount int, isDirty bool) (version, currentTag string, isPreRelease, needsNewTag bool) {
|
||||||
if latestTag == "" {
|
if latestTag == "" {
|
||||||
@ -144,6 +211,8 @@ func computeVersion(prefix, latestTag string, commitCount int, isDirty bool) (ve
|
|||||||
return version, currentTag, true, needsNewTag
|
return version, currentTag, true, needsNewTag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Semver helpers ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// semverLess returns true if semver string a < b.
|
// semverLess returns true if semver string a < b.
|
||||||
// Handles "vX.Y.Z" and "vX.Y.Z-preN" forms.
|
// Handles "vX.Y.Z" and "vX.Y.Z-preN" forms.
|
||||||
func semverLess(a, b string) bool {
|
func semverLess(a, b string) bool {
|
||||||
@ -201,76 +270,93 @@ func preNum(s string) int {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
// goreleaserYAML returns the contents of .goreleaser.yaml for binName.
|
// ── goreleaser YAML generation ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
// goreleaserYAML returns .goreleaser.yaml content for one or more binaries.
|
||||||
//
|
//
|
||||||
// Key design decisions:
|
// Design decisions:
|
||||||
// - Uses {{.Env.VERSION}} instead of {{.Version}} everywhere so the
|
// - Uses {{.Env.VERSION}} instead of {{.Version}} everywhere so a prefixed
|
||||||
// prefixed monorepo tag (cmd/tcpfwd/v1.1.0) doesn't bleed into filenames.
|
// monorepo tag (e.g. io/transform/gsheet2csv/v1.2.3) never bleeds into
|
||||||
// - release.disable: true because we use `gh` to create the GitHub Release
|
// artifact filenames.
|
||||||
// (goreleaser Pro is required to publish with a prefixed tag).
|
// - Each binary gets its own build (with id) and its own archive (with ids)
|
||||||
// - Checksum file is named with VERSION so it matches the archive names.
|
// so cross-platform tarballs are separate per tool.
|
||||||
func goreleaserYAML(binName string) string {
|
// - The checksum file is named <projectName>_VERSION_checksums.txt and
|
||||||
// NOTE: "BINNAME" is our placeholder; goreleaser template markers
|
// covers every archive produced in the run.
|
||||||
// ({{ ... }}) are kept verbatim – this is NOT a Go text/template.
|
// - release.disable: true — goreleaser Pro is required to publish with a
|
||||||
const tpl = `# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
|
// prefixed tag; we use `gh release` in the generated script instead.
|
||||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj
|
func goreleaserYAML(projectName string, bins []binary) string {
|
||||||
# Generated by monorel (github.com/therootcompany/golib/tools/monorel)
|
var b strings.Builder
|
||||||
|
w := func(s string) { b.WriteString(s) }
|
||||||
|
wf := func(format string, args ...any) { fmt.Fprintf(&b, format, args...) }
|
||||||
|
|
||||||
version: 2
|
w("# yaml-language-server: $schema=https://goreleaser.com/static/schema.json\n")
|
||||||
|
w("# vim: set ts=2 sw=2 tw=0 fo=cnqoj\n")
|
||||||
|
w("# Generated by monorel (github.com/therootcompany/golib/tools/monorel)\n")
|
||||||
|
w("\nversion: 2\n")
|
||||||
|
w("\nbefore:\n hooks:\n - go mod tidy\n - go generate ./...\n")
|
||||||
|
|
||||||
before:
|
// ── builds ──────────────────────────────────────────────────────────────
|
||||||
hooks:
|
w("\nbuilds:\n")
|
||||||
- go mod tidy
|
for _, bin := range bins {
|
||||||
- go generate ./...
|
wf(" - id: %s\n", bin.name)
|
||||||
|
wf(" binary: %s\n", bin.name)
|
||||||
builds:
|
if bin.mainPath != "." {
|
||||||
- env:
|
wf(" main: %s\n", bin.mainPath)
|
||||||
- CGO_ENABLED=0
|
}
|
||||||
binary: BINNAME
|
w(" env:\n - CGO_ENABLED=0\n")
|
||||||
ldflags:
|
w(" ldflags:\n")
|
||||||
- -s -w -X main.version={{.Env.VERSION}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser
|
w(" - -s -w" +
|
||||||
goos:
|
" -X main.version={{.Env.VERSION}}" +
|
||||||
- linux
|
" -X main.commit={{.Commit}}" +
|
||||||
- windows
|
" -X main.date={{.Date}}" +
|
||||||
- darwin
|
" -X main.builtBy=goreleaser\n")
|
||||||
|
w(" goos:\n - linux\n - windows\n - darwin\n")
|
||||||
archives:
|
|
||||||
- formats: [tar.gz]
|
|
||||||
# name_template uses VERSION env var so the prefixed monorepo tag
|
|
||||||
# (e.g. cmd/tcpfwd/v1.1.0) doesn't appear in archive filenames.
|
|
||||||
name_template: >-
|
|
||||||
BINNAME_{{ .Env.VERSION }}_
|
|
||||||
{{- title .Os }}_
|
|
||||||
{{- if eq .Arch "amd64" }}x86_64
|
|
||||||
{{- else if eq .Arch "386" }}i386
|
|
||||||
{{- else }}{{ .Arch }}{{ end }}
|
|
||||||
{{- if .Arm }}v{{ .Arm }}{{ end }}
|
|
||||||
format_overrides:
|
|
||||||
- goos: windows
|
|
||||||
formats: [zip]
|
|
||||||
|
|
||||||
changelog:
|
|
||||||
sort: asc
|
|
||||||
filters:
|
|
||||||
exclude:
|
|
||||||
- "^docs:"
|
|
||||||
- "^test:"
|
|
||||||
|
|
||||||
checksum:
|
|
||||||
name_template: "BINNAME_{{ .Env.VERSION }}_checksums.txt"
|
|
||||||
disable: false
|
|
||||||
|
|
||||||
# Release is disabled: goreleaser Pro is required to publish with a
|
|
||||||
# prefixed monorepo tag. We use 'gh release' instead (see release script).
|
|
||||||
release:
|
|
||||||
disable: true
|
|
||||||
`
|
|
||||||
return strings.ReplaceAll(tpl, "BINNAME", binName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// printScript writes a bash release script to stdout.
|
// ── archives ────────────────────────────────────────────────────────────
|
||||||
|
w("\narchives:\n")
|
||||||
|
for _, bin := range bins {
|
||||||
|
wf(" - id: %s\n", bin.name)
|
||||||
|
wf(" ids: [%s]\n", bin.name)
|
||||||
|
w(" formats: [tar.gz]\n")
|
||||||
|
w(" # name_template uses VERSION env var so the prefixed monorepo tag\n")
|
||||||
|
w(" # doesn't appear in archive filenames.\n")
|
||||||
|
w(" name_template: >-\n")
|
||||||
|
wf(" %s_{{ .Env.VERSION }}_\n", bin.name)
|
||||||
|
w(" {{- title .Os }}_\n")
|
||||||
|
w(" {{- if eq .Arch \"amd64\" }}x86_64\n")
|
||||||
|
w(" {{- else if eq .Arch \"386\" }}i386\n")
|
||||||
|
w(" {{- else }}{{ .Arch }}{{ end }}\n")
|
||||||
|
w(" {{- if .Arm }}v{{ .Arm }}{{ end }}\n")
|
||||||
|
w(" format_overrides:\n")
|
||||||
|
w(" - goos: windows\n")
|
||||||
|
w(" formats: [zip]\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── changelog ───────────────────────────────────────────────────────────
|
||||||
|
w("\nchangelog:\n sort: asc\n filters:\n exclude:\n")
|
||||||
|
w(" - \"^docs:\"\n - \"^test:\"\n")
|
||||||
|
|
||||||
|
// ── checksum ────────────────────────────────────────────────────────────
|
||||||
|
w("\nchecksum:\n")
|
||||||
|
wf(" name_template: \"%s_{{ .Env.VERSION }}_checksums.txt\"\n", projectName)
|
||||||
|
w(" disable: false\n")
|
||||||
|
|
||||||
|
// ── release ─────────────────────────────────────────────────────────────
|
||||||
|
w("\n# Release is disabled: goreleaser Pro is required to publish with a\n")
|
||||||
|
w("# prefixed monorepo tag. We use 'gh release' instead (see release script).\n")
|
||||||
|
w("release:\n disable: true\n")
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Release script generation ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
// printScript writes a numbered, ready-to-review bash release script to stdout.
|
||||||
func printScript(
|
func printScript(
|
||||||
binName, version, currentTag, prevTag, repoPath, headSHA string,
|
projectName string,
|
||||||
|
bins []binary,
|
||||||
|
version, currentTag, prevTag, repoPath, headSHA string,
|
||||||
isPreRelease, needsNewTag, isDirty bool,
|
isPreRelease, needsNewTag, isDirty bool,
|
||||||
) {
|
) {
|
||||||
line := func(format string, args ...any) { fmt.Printf(format+"\n", args...) }
|
line := func(format string, args ...any) { fmt.Printf(format+"\n", args...) }
|
||||||
@ -294,7 +380,15 @@ func printScript(
|
|||||||
|
|
||||||
// Summary comment block.
|
// Summary comment block.
|
||||||
blank()
|
blank()
|
||||||
line("# %-16s %s", "Binary:", binName)
|
if len(bins) == 1 {
|
||||||
|
line("# %-16s %s", "Binary:", bins[0].name)
|
||||||
|
} else {
|
||||||
|
names := make([]string, len(bins))
|
||||||
|
for i, b := range bins {
|
||||||
|
names[i] = b.name
|
||||||
|
}
|
||||||
|
line("# %-16s %s", "Binaries:", strings.Join(names, ", "))
|
||||||
|
}
|
||||||
line("# %-16s %s", "VERSION:", version)
|
line("# %-16s %s", "VERSION:", version)
|
||||||
line("# %-16s %s", "Current tag:", currentTag)
|
line("# %-16s %s", "Current tag:", currentTag)
|
||||||
if prevTag != "" {
|
if prevTag != "" {
|
||||||
@ -309,7 +403,7 @@ func printScript(
|
|||||||
line("export VERSION=%q", version)
|
line("export VERSION=%q", version)
|
||||||
line("export GORELEASER_CURRENT_TAG=%q", currentTag)
|
line("export GORELEASER_CURRENT_TAG=%q", currentTag)
|
||||||
|
|
||||||
// Step 2 – create tag (only for clean pre-releases or first release).
|
// Step 2 – create tag (clean pre-releases and first releases only).
|
||||||
if needsNewTag {
|
if needsNewTag {
|
||||||
section("Step 2: Create git tag")
|
section("Step 2: Create git tag")
|
||||||
line("git tag %q", currentTag)
|
line("git tag %q", currentTag)
|
||||||
@ -318,13 +412,13 @@ func printScript(
|
|||||||
|
|
||||||
// Step 3 – build.
|
// Step 3 – build.
|
||||||
section("Step 3: Build with goreleaser")
|
section("Step 3: Build with goreleaser")
|
||||||
line("# release.disable=true is set in .goreleaser.yaml; goreleaser only builds.")
|
line("# release.disable=true in .goreleaser.yaml; goreleaser only builds.")
|
||||||
line("goreleaser release --clean --skip=validate,announce")
|
line("goreleaser release --clean --skip=validate,announce")
|
||||||
|
|
||||||
// Step 4 – release notes.
|
// Step 4 – release notes.
|
||||||
section("Step 4: Generate release notes")
|
section("Step 4: Generate release notes")
|
||||||
if prevTag != "" {
|
if prevTag != "" {
|
||||||
// Path-limited log: only commits that touched files under this directory.
|
// Path-limited: only commits touching files under the module directory.
|
||||||
line("RELEASE_NOTES=$(git --no-pager log %q..HEAD \\", prevTag)
|
line("RELEASE_NOTES=$(git --no-pager log %q..HEAD \\", prevTag)
|
||||||
line(" --pretty=format:'- %%h %%s' -- ./)")
|
line(" --pretty=format:'- %%h %%s' -- ./)")
|
||||||
} else {
|
} else {
|
||||||
@ -334,8 +428,8 @@ func printScript(
|
|||||||
|
|
||||||
// Step 5 – create draft release.
|
// Step 5 – create draft release.
|
||||||
section("Step 5: Create draft GitHub release")
|
section("Step 5: Create draft GitHub release")
|
||||||
tagVersion := currentTag[strings.LastIndex(currentTag, "/")+1:] // strip prefix
|
tagVersion := currentTag[strings.LastIndex(currentTag, "/")+1:] // strip module prefix
|
||||||
title := binName + " " + tagVersion
|
title := projectName + " " + tagVersion
|
||||||
line("gh release create %q \\", currentTag)
|
line("gh release create %q \\", currentTag)
|
||||||
line(" --title %q \\", title)
|
line(" --title %q \\", title)
|
||||||
line(" --notes \"${RELEASE_NOTES}\" \\")
|
line(" --notes \"${RELEASE_NOTES}\" \\")
|
||||||
@ -348,9 +442,11 @@ func printScript(
|
|||||||
// Step 6 – upload artifacts.
|
// Step 6 – upload artifacts.
|
||||||
section("Step 6: Upload artifacts")
|
section("Step 6: Upload artifacts")
|
||||||
line("gh release upload %q \\", currentTag)
|
line("gh release upload %q \\", currentTag)
|
||||||
line(" ./dist/%s_*.tar.gz \\", binName)
|
for _, bin := range bins {
|
||||||
line(" ./dist/%s_*.zip \\", binName)
|
line(" ./dist/%s_*.tar.gz \\", bin.name)
|
||||||
line(" \"./dist/%s_%s_checksums.txt\" \\", binName, version)
|
line(" ./dist/%s_*.zip \\", bin.name)
|
||||||
|
}
|
||||||
|
line(" \"./dist/%s_%s_checksums.txt\" \\", projectName, version)
|
||||||
line(" --clobber")
|
line(" --clobber")
|
||||||
|
|
||||||
// Step 7 – publish.
|
// Step 7 – publish.
|
||||||
@ -359,6 +455,8 @@ func printScript(
|
|||||||
blank()
|
blank()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// normalizeGitURL strips scheme, credentials, and .git suffix from a remote URL.
|
// normalizeGitURL strips scheme, credentials, and .git suffix from a remote URL.
|
||||||
//
|
//
|
||||||
// https://github.com/org/repo.git → github.com/org/repo
|
// https://github.com/org/repo.git → github.com/org/repo
|
||||||
@ -368,7 +466,6 @@ func normalizeGitURL(rawURL string) string {
|
|||||||
rawURL = strings.TrimSuffix(rawURL, ".git")
|
rawURL = strings.TrimSuffix(rawURL, ".git")
|
||||||
if idx := strings.Index(rawURL, "://"); idx >= 0 {
|
if idx := strings.Index(rawURL, "://"); idx >= 0 {
|
||||||
rawURL = rawURL[idx+3:]
|
rawURL = rawURL[idx+3:]
|
||||||
// Drop any "user:pass@" prefix.
|
|
||||||
if idx2 := strings.Index(rawURL, "@"); idx2 >= 0 {
|
if idx2 := strings.Index(rawURL, "@"); idx2 >= 0 {
|
||||||
rawURL = rawURL[idx2+1:]
|
rawURL = rawURL[idx2+1:]
|
||||||
}
|
}
|
||||||
@ -398,4 +495,3 @@ func fatalf(format string, args ...any) {
|
|||||||
fmt.Fprintf(os.Stderr, "monorel: error: "+format+"\n", args...)
|
fmt.Fprintf(os.Stderr, "monorel: error: "+format+"\n", args...)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user