From 347aedfb66810237c2477cb807f6d946d27c9694 Mon Sep 17 00:00:00 2001 From: Amolith Date: Tue, 21 Apr 2026 23:30:08 -0600 Subject: [PATCH] Add release process --- mise.toml | 14 +-- release.fish | 298 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+), 10 deletions(-) create mode 100755 release.fish diff --git a/mise.toml b/mise.toml index 022e806dfd7eff35a43e8f64ca4549ee31e3bf3a..ebb4da65254f78e791e903289ff41fd4242f88d9 100644 --- a/mise.toml +++ b/mise.toml @@ -23,16 +23,6 @@ run = "go test -v ./..." [tasks.fmt] run = "gofumpt -w ." -[tasks."fmt:check"] -run = """ -output=$(gofumpt -d .) -if [ -n "$output" ]; then - echo "$output" - echo "Files unformatted; execute 'mise run fmt'" - exit 1 -fi -""" - [tasks.fix] run = "jj --config 'fix.tools.gofumpt.command=[\"gofumpt\"]' --config 'fix.tools.gofumpt.patterns=[\"glob:**/*.go\"]' fix" @@ -58,3 +48,7 @@ fi [tasks.check] depends = ["fmt", "vet", "lint", "vuln", "build", "test:quiet"] + +[tasks.release] +description = "Interactive release workflow (tag, build, pack, upload)" +run = "fish release.fish" diff --git a/release.fish b/release.fish new file mode 100755 index 0000000000000000000000000000000000000000..17bce17a82c2952d7404caf3dffaa4b37e882cd3 --- /dev/null +++ b/release.fish @@ -0,0 +1,298 @@ +#!/usr/bin/env fish + +# SPDX-FileCopyrightText: Amolith +# +# SPDX-License-Identifier: CC0-1.0 + +function __release_sb_mcp_usage + echo "Usage: release.fish [--from ] [--only ] [--help] + +Stages (run in order): + tag Compute next version, create and push lightweight tag + build Cross-compile for all release targets + pack Compress Linux binaries with UPX + upload Upload artifacts via release(1) + +Flags: + --from Start at this stage and continue through the rest + --only Run just this one stage + --help, -h Show this help + +With no flags, all stages run in order (tag → build → pack → upload). +The tag stage also notifies the Go module proxy after pushing. + +Examples: + ./release.fish Full release + ./release.fish --from build Rebuild, pack, and upload (skip tagging) + ./release.fish --only build Just cross-compile + ./release.fish --only upload Re-upload existing dist/ artifacts" +end + +set -l binary sb-mcp +set -l module_path git.secluded.site/sb-mcp +set -l version_pkg git.secluded.site/sb-mcp/internal/server.Version +set -l remote soft + +set -l targets \ + linux/amd64 \ + linux/arm64 \ + darwin/amd64 \ + darwin/arm64 \ + windows/amd64 \ + freebsd/amd64 + +# UPX 5.x dropped macOS support; windows/amd64 is PE32 only and +# freebsd/amd64 is ELF32 only, so only Linux targets are packed. +set -l pack_targets \ + linux-amd64 \ + linux-arm64 + +# Global so __should_run can see them. Prefixed to avoid collisions. +set -g __rl_stages tag build pack upload +set -g __rl_from "" +set -g __rl_only "" + +# --- Parse flags --- + +set -l i 1 +while test $i -le (count $argv) + switch $argv[$i] + case --from + set i (math $i + 1) + if test $i -gt (count $argv) + echo "Error: --from requires a value" >&2 + __release_sb_mcp_usage >&2 + exit 1 + end + set -g __rl_from $argv[$i] + case --only + set i (math $i + 1) + if test $i -gt (count $argv) + echo "Error: --only requires a value" >&2 + __release_sb_mcp_usage >&2 + exit 1 + end + set -g __rl_only $argv[$i] + case --help -h + __release_sb_mcp_usage + exit 0 + case '*' + echo "Error: unknown flag '$argv[$i]'" >&2 + __release_sb_mcp_usage >&2 + exit 1 + end + set i (math $i + 1) +end + +if test -n "$__rl_from" -a -n "$__rl_only" + echo "Error: --from and --only are mutually exclusive" >&2 + exit 1 +end + +if test -n "$__rl_from"; and not contains -- "$__rl_from" $__rl_stages + echo "Error: unknown stage '$__rl_from' (valid: $__rl_stages)" >&2 + exit 1 +end + +if test -n "$__rl_only"; and not contains -- "$__rl_only" $__rl_stages + echo "Error: unknown stage '$__rl_only' (valid: $__rl_stages)" >&2 + exit 1 +end + +# Returns 0 if the given stage should execute, 1 otherwise. +function __should_run -a stage + if test -n "$__rl_only" + test "$stage" = "$__rl_only" + return + end + + if test -z "$__rl_from" + return 0 + end + + set -l reached 0 + for s in $__rl_stages + if test "$s" = "$__rl_from" + set reached 1 + end + if test $reached -eq 1 -a "$s" = "$stage" + return 0 + end + end + return 1 +end + +# --- Resolve tag --- +# +# When skipping the tag stage we still need a tag for filenames and +# ldflags, so fall back to git describe. + +set -l tag + +if __should_run tag + # --- Guards --- + + # In jj the working copy is always a draft commit; "clean" means + # the working-copy change has no file diffs. + if test -n "$(jj diff --summary)" + echo "Error: working copy has uncommitted changes" >&2 + exit 1 + end + + # --- Fetch tags --- + + git fetch $remote --tags + + # --- Compute next version --- + + set -l current (git describe --tags --abbrev=0 2>/dev/null; or echo v0.0.0) + set -l bump (gum choose major minor patch prerelease) + + # Detect whether the current tag is already a prerelease + # (e.g. v1.2.3-rc.4). + set -l current_is_prerelease no + if string match -rq -- '-[a-zA-Z]+\.[0-9]+$' $current + set current_is_prerelease yes + end + + # Ask whether to create a prerelease, unless the user already chose + # "prerelease". + set -l is_prerelease no + if test "$bump" = prerelease + set is_prerelease yes + else + if gum confirm "Create pre-release?" + set is_prerelease yes + end + end + + # Determine the prerelease suffix (e.g. "beta", "rc"). + set -l prerelease_suffix "" + if test "$bump" = prerelease -a "$current_is_prerelease" = yes + # Reuse the suffix from the current prerelease tag. + set prerelease_suffix (string replace -r '.*-([a-zA-Z]+)\.[0-9]+$' '$1' $current) + else if test "$is_prerelease" = yes + set prerelease_suffix (gum input --placeholder "Enter pre-release suffix (e.g. beta, rc)") + if test -z "$prerelease_suffix" + echo "Error: pre-release suffix is required" >&2 + exit 1 + end + end + + # Compute the base version (without prerelease suffix). + set -l base_next + if test "$bump" = prerelease -a "$current_is_prerelease" = yes + # Strip the prerelease suffix to get the base + # (e.g. v1.2.3-rc.4 → v1.2.3). + set base_next (string replace -r -- '-[a-zA-Z]+\.[0-9]+$' '' $current) + else + set base_next (svu $bump) + end + + # Compute the suffix version number. + set -l suffix_ver "" + if test "$is_prerelease" = yes -a -n "$prerelease_suffix" + if test "$bump" = prerelease -a "$current_is_prerelease" = yes + # Increment the current prerelease number. + set -l current_num (string replace -r '.*-[a-zA-Z]+\.([0-9]+)$' '$1' $current) + set suffix_ver (math $current_num + 1) + else + # Find existing tags with this suffix and increment past the + # highest. + set -l highest (git tag -l "$base_next-$prerelease_suffix.*" \ + | string replace -r ".*-$prerelease_suffix\." '' \ + | sort -n | tail -1) + if test -n "$highest" + set suffix_ver (math $highest + 1) + else + set suffix_ver 0 + end + end + end + + # Assemble the final tag. + if test "$is_prerelease" = yes -a -n "$prerelease_suffix" + set tag "$base_next-$prerelease_suffix.$suffix_ver" + else + set tag "$base_next" + end + + # --- Confirm --- + + read -P "Release $tag? [y/N] " confirm + if test "$confirm" != y -a "$confirm" != Y + echo Aborted. + exit 0 + end + + # --- Tag and push --- + # + # jj tag set creates a lightweight tag pointing at the given + # revision. We tag @- (the parent of the working copy) which is + # the commit main points to. Then push via git since jj git push + # does not push tags. + + jj tag set $tag -r @-; or begin + echo "Error: tagging failed" >&2 + exit 1 + end + + git push $remote $tag; or begin + echo "Error: tag push failed — deleting local tag" >&2 + jj tag delete $tag 2>/dev/null + exit 1 + end + + echo "Released $tag" + + # --- Notify Go module proxy --- + + go list -m $module_path@$tag >/dev/null; or begin + echo "Warning: module proxy notification failed" >&2 + end +else + set tag (git describe --tags --always 2>/dev/null; or echo dev) +end + +# --- Build --- + +if __should_run build + set -l ldflags "-s -w -X $version_pkg=$tag" + + rm -rf dist + mkdir -p dist + + for target in $targets + set -l os (echo $target | cut -d/ -f1) + set -l arch (echo $target | cut -d/ -f2) + set -l ext "" + if test "$os" = windows + set ext .exe + end + + echo "Building $os/$arch..." + env CGO_ENABLED=0 GOOS=$os GOARCH=$arch \ + go build -o "dist/$binary-$tag-$os-$arch$ext" -ldflags "$ldflags" ./cmd/$binary/; or begin + echo "Error: build failed for $os/$arch" >&2 + exit 1 + end + end +end + +# --- Pack --- + +if __should_run pack + for suffix in $pack_targets + set -l bin "dist/$binary-$tag-$suffix" + if test -f "$bin" + echo "Packing $suffix..." + upx -q "$bin" + end + end +end + +# --- Upload --- + +if __should_run upload + fish -c "release upload $binary $tag --latest dist/*" +end \ No newline at end of file