From 16e43cd8435ed1d7cb474ae18860f7ca98b47753 Mon Sep 17 00:00:00 2001 From: Amolith Date: Wed, 11 Mar 2026 12:02:15 -0600 Subject: [PATCH] Move release flow to Fish script --- Taskfile.yaml | 128 +---------------------- release.fish | 282 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+), 126 deletions(-) create mode 100755 release.fish diff --git a/Taskfile.yaml b/Taskfile.yaml index 5049c5be4acde89038d9047981fa7ced58360c9b..3fe929e845d6dac7bae5ae58572e49a56e9d1ef5 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -90,131 +90,7 @@ tasks: cmds: - rm -rf lune config.toml - release:build: - desc: Cross-compile for all release targets - vars: - TARGETS: >- - linux/amd64 - linux/arm64 - darwin/amd64 - darwin/arm64 - windows/amd64 - freebsd/amd64 - cmds: - - rm -rf dist - - mkdir -p dist - - for: {var: TARGETS, split: " "} - cmd: | - os=$(echo {{.ITEM}} | cut -d/ -f1) - arch=$(echo {{.ITEM}} | cut -d/ -f2) - ext=""; if [ "$os" = "windows" ]; then ext=".exe"; fi - echo "Building ${os}/${arch}..." - GOOS=${os} GOARCH=${arch} go build \ - -o "dist/lune-{{.VERSION}}-${os}-${arch}${ext}" \ - -ldflags "-s -w -X main.version={{.VERSION}}" - - release:pack: - desc: Compress release binaries with UPX where supported - vars: - # UPX 5.x dropped macOS support; windows/amd64 is PE32 only and - # freebsd/amd64 is ELF32 only, so only Linux targets are packed. - PACK_TARGETS: >- - linux-amd64 - linux-arm64 - cmds: - - for: {var: PACK_TARGETS, split: " "} - cmd: | - bin="dist/lune-{{.VERSION}}-{{.ITEM}}" - if [ -f "$bin" ]; then - echo "Packing {{.ITEM}}..." - upx -q "$bin" - fi - - release:upload: - desc: Upload release artifacts - cmds: - - fish -c 'release upload lune {{.VERSION}} --latest dist/*' - - release:all: - desc: Tag, build, pack, and upload a release - cmds: - - task release - - task release:build - - task release:pack - - task release:upload - release: - desc: Interactive release workflow - vars: - BUMP: - sh: gum choose "major" "minor" "patch" "prerelease" - CURRENT_VERSION: - sh: git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0" - IS_CURRENT_PRERELEASE: - sh: | - current="{{.CURRENT_VERSION}}" - if echo "$current" | grep -qE '\-[a-zA-Z]+\.[0-9]+$'; then - echo "yes" - else - echo "no" - fi - IS_PRERELEASE: - sh: | - if [ "{{.BUMP}}" = "prerelease" ]; then - echo "yes" - else - gum confirm "Create pre-release?" && echo "yes" || echo "no" - fi - PRERELEASE_SUFFIX: - sh: | - if [ "{{.BUMP}}" = "prerelease" ] && [ "{{.IS_CURRENT_PRERELEASE}}" = "yes" ]; then - # Extract suffix from current version (e.g., v1.2.3-beta.0 -> beta) - echo "{{.CURRENT_VERSION}}" | sed -E 's/.*-([a-zA-Z]+)\.[0-9]+$/\1/' - elif [ "{{.IS_PRERELEASE}}" = "yes" ]; then - gum input --placeholder "Enter pre-release suffix (e.g., beta, rc)" - fi - BASE_NEXT: - sh: | - if [ "{{.BUMP}}" = "prerelease" ] && [ "{{.IS_CURRENT_PRERELEASE}}" = "yes" ]; then - # Extract base version from current prerelease (e.g., v1.2.3-beta.0 -> v1.2.3) - echo "{{.CURRENT_VERSION}}" | sed -E 's/-[a-zA-Z]+\.[0-9]+$//' - else - svu {{.BUMP}} - fi - SUFFIX_VERSION: - sh: | - if [ "{{.IS_PRERELEASE}}" = "yes" ] && [ -n "{{.PRERELEASE_SUFFIX}}" ]; then - if [ "{{.BUMP}}" = "prerelease" ] && [ "{{.IS_CURRENT_PRERELEASE}}" = "yes" ]; then - # Increment the current prerelease number - current_num=$(echo "{{.CURRENT_VERSION}}" | sed -E 's/.*-[a-zA-Z]+\.([0-9]+)$/\1/') - echo $((current_num + 1)) - else - # Find existing tags with this suffix and get the highest version number - highest=$(git tag -l "{{.BASE_NEXT}}-{{.PRERELEASE_SUFFIX}}.*" | \ - sed 's/.*-{{.PRERELEASE_SUFFIX}}\.//' | \ - sort -n | tail -1) - if [ -n "$highest" ]; then - echo $((highest + 1)) - else - echo 0 - fi - fi - fi - NEXT: - sh: | - if [ "{{.IS_PRERELEASE}}" = "yes" ] && [ -n "{{.PRERELEASE_SUFFIX}}" ]; then - echo "{{.BASE_NEXT}}-{{.PRERELEASE_SUFFIX}}.{{.SUFFIX_VERSION}}" - else - echo "{{.BASE_NEXT}}" - fi - prompt: "Release {{.NEXT}}?" - preconditions: - - sh: '[ $(git symbolic-ref --short HEAD) = "main" ] || [ $(git symbolic-ref --short HEAD) = "dev" ]' - msg: Not on main or dev branch - - sh: "[ $(git status --porcelain=2 | wc -l) = 0 ]" - msg: "Git is dirty" + desc: Interactive release workflow (tag, build, pack, upload) cmds: - - git tag {{.NEXT}} - - git push soft {{.NEXT}} - - go list -m git.secluded.site/lune@{{.NEXT}} > /dev/null - - echo "Released {{.NEXT}} and notified module proxy" + - ./release.fish diff --git a/release.fish b/release.fish new file mode 100755 index 0000000000000000000000000000000000000000..8e0f28727acc68bf9dada9ba7de4c163e0bfe7ba --- /dev/null +++ b/release.fish @@ -0,0 +1,282 @@ +#!/usr/bin/env fish + +# SPDX-FileCopyrightText: Amolith +# +# SPDX-License-Identifier: CC0-1.0 + +function __release_lune_usage + echo "Usage: release.fish [--from ] [--only ] [--help] + +Stages (run in order): + tag Compute next version, create and push annotated 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 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) + set -g __rl_from $argv[$i] + case --only + set i (math $i + 1) + set -g __rl_only $argv[$i] + case --help -h + __release_lune_usage + exit 0 + case '*' + echo "Error: unknown flag '$argv[$i]'" >&2 + __release_lune_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 --- + + set -l branch (git symbolic-ref --short HEAD) + if test "$branch" != main -a "$branch" != dev + echo "Error: not on main or dev branch (on $branch)" >&2 + exit 1 + end + + if test (git status --porcelain=2 | wc -l) -ne 0 + echo "Error: git working tree is dirty" >&2 + exit 1 + end + + # --- Fetch tags --- + + git fetch soft --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 --- + + git tag -a $tag; or begin + echo "Error: tagging failed" >&2 + exit 1 + end + + git push soft $tag; or begin + echo "Error: tag push failed — deleting local tag" >&2 + git tag -d $tag 2>/dev/null + exit 1 + end + + echo "Released $tag" + + # --- Notify Go module proxy --- + + go list -m git.secluded.site/lune@$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 main.version=$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/lune-$tag-$os-$arch$ext" -ldflags "$ldflags"; 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/lune-$tag-$suffix" + if test -f "$bin" + echo "Packing $suffix..." + upx -q "$bin" + end + end +end + +# --- Upload --- + +if __should_run upload + fish -c "release upload lune $tag --latest dist/*" +end