Move release flow to Fish script

Amolith created

Change summary

Taskfile.yaml | 128 -----------------------
release.fish  | 282 +++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 284 insertions(+), 126 deletions(-)

Detailed changes

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

release.fish 🔗

@@ -0,0 +1,282 @@
+#!/usr/bin/env fish
+
+# SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
+#
+# SPDX-License-Identifier: CC0-1.0
+
+function __release_lune_usage
+    echo "Usage: release.fish [--from <stage>] [--only <stage>] [--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 <stage>   Start at this stage and continue through the rest
+    --only <stage>   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