Move release to Fish script

Amolith created

Change summary

Makefile     | 113 --------------------
release.fish | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 290 insertions(+), 109 deletions(-)

Detailed changes

Makefile 🔗

@@ -1,23 +1,7 @@
-BINDIR  := $(or $(XDG_BIN_HOME),$(XDG_BIN_DIR),$(HOME)/.local/bin)
-JJ_FIX  := jj --config 'fix.tools.rustfmt.command=["rustfmt","--emit","stdout","--edition","2021"]' --config 'fix.tools.rustfmt.patterns=["glob:**/*.rs"]' fix
-VERSION := $(shell git describe --tags --always 2>/dev/null || echo v0.0.0)
+BINDIR := $(or $(XDG_BIN_HOME),$(XDG_BIN_DIR),$(HOME)/.local/bin)
+JJ_FIX := jj --config 'fix.tools.rustfmt.command=["rustfmt","--emit","stdout","--edition","2021"]' --config 'fix.tools.rustfmt.patterns=["glob:**/*.rs"]' fix
 
-TARGETS := \
-	x86_64-unknown-linux-gnu \
-	aarch64-unknown-linux-gnu \
-	x86_64-apple-darwin \
-	aarch64-apple-darwin \
-	x86_64-pc-windows-gnu \
-	x86_64-unknown-freebsd
-
-# UPX 5.x dropped macOS support; Windows and FreeBSD are limited to 32-bit
-# formats. Only Linux targets are packable.
-PACK_TARGETS := \
-	x86_64-unknown-linux-gnu \
-	aarch64-unknown-linux-gnu
-
-.PHONY: all check test fmt clippy verify install clean
-.PHONY: release release-build release-pack release-upload release-all
+.PHONY: all check test fmt clippy verify install clean release
 
 all: fmt check test
 
@@ -47,94 +31,5 @@ install:
 clean:
 	rm -rf dist
 
-release-build:
-	rm -rf dist
-	mkdir -p dist
-	@for target in $(TARGETS); do \
-		echo "Building $$target..."; \
-		ext=""; case $$target in *windows*) ext=".exe";; esac; \
-		cargo zigbuild --target $$target --release || exit 1; \
-		cp "target/$$target/release/td$$ext" "dist/td-$(VERSION)-$$target$$ext"; \
-	done
-
-release-pack:
-	@for target in $(PACK_TARGETS); do \
-		bin="dist/td-$(VERSION)-$$target"; \
-		if [ -f "$$bin" ]; then \
-			echo "Packing $$target..."; \
-			upx -q "$$bin"; \
-		fi; \
-	done
-
-release-upload:
-	fish -c 'release upload yatd $(VERSION) --latest dist/*'
-
-release-all: release release-build release-pack release-upload
-
 release:
-	@set -e; \
-	BUMP=$$(gum choose "major" "minor" "patch" "prerelease"); \
-	CURRENT=$$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0"); \
-	\
-	IS_CURRENT_PRE=no; \
-	echo "$$CURRENT" | grep -qE '\-[a-zA-Z]+\.[0-9]+$$' && IS_CURRENT_PRE=yes; \
-	\
-	IS_PRE=no; \
-	if [ "$$BUMP" = "prerelease" ]; then \
-		IS_PRE=yes; \
-	else \
-		gum confirm "Create pre-release?" && IS_PRE=yes || true; \
-	fi; \
-	\
-	PRE_SUFFIX=""; \
-	if [ "$$BUMP" = "prerelease" ] && [ "$$IS_CURRENT_PRE" = "yes" ]; then \
-		PRE_SUFFIX=$$(echo "$$CURRENT" | sed -E 's/.*-([a-zA-Z]+)\.[0-9]+$$/\1/'); \
-	elif [ "$$IS_PRE" = "yes" ]; then \
-		PRE_SUFFIX=$$(gum input --placeholder "Pre-release suffix (e.g. beta, rc)"); \
-	fi; \
-	\
-	if [ "$$BUMP" = "prerelease" ] && [ "$$IS_CURRENT_PRE" = "yes" ]; then \
-		BASE_NEXT=$$(echo "$$CURRENT" | sed -E 's/-[a-zA-Z]+\.[0-9]+$$//'); \
-	else \
-		BASE_NEXT=$$(svu $$BUMP); \
-	fi; \
-	\
-	SUFFIX_VER=""; \
-	if [ "$$IS_PRE" = "yes" ] && [ -n "$$PRE_SUFFIX" ]; then \
-		if [ "$$BUMP" = "prerelease" ] && [ "$$IS_CURRENT_PRE" = "yes" ]; then \
-			cur_num=$$(echo "$$CURRENT" | sed -E 's/.*-[a-zA-Z]+\.([0-9]+)$$/\1/'); \
-			SUFFIX_VER=$$((cur_num + 1)); \
-		else \
-			highest=$$(git tag -l "$$BASE_NEXT-$$PRE_SUFFIX.*" \
-				| sed "s/.*-$$PRE_SUFFIX\.//" | sort -n | tail -1); \
-			if [ -n "$$highest" ]; then \
-				SUFFIX_VER=$$((highest + 1)); \
-			else \
-				SUFFIX_VER=0; \
-			fi; \
-		fi; \
-	fi; \
-	\
-	if [ "$$IS_PRE" = "yes" ] && [ -n "$$PRE_SUFFIX" ]; then \
-		NEXT="$$BASE_NEXT-$$PRE_SUFFIX.$$SUFFIX_VER"; \
-	else \
-		NEXT="$$BASE_NEXT"; \
-	fi; \
-	\
-	PARENT=$$(jj log -r '@-' --no-graph -T 'bookmarks' 2>/dev/null); \
-	case "$$PARENT" in \
-		*main*|*dev*) ;; \
-		*) echo "error: parent commit is not on main or dev" >&2; exit 1;; \
-	esac; \
-	if jj diff --summary 2>/dev/null | grep -qE '^[MD] '; then \
-		echo "error: working copy has modified or deleted files" >&2; exit 1; \
-	fi; \
-	\
-	gum confirm "Release $$NEXT?" || exit 1; \
-	CARGO_VER=$$(echo "$$NEXT" | sed 's/^v//'); \
-	sed -i "s/^version = \".*\"/version = \"$$CARGO_VER\"/" Cargo.toml; \
-	jj commit -m "Bump version to $$NEXT"; \
-	jj tag set "$$NEXT" -r @-; \
-	git push soft "$$NEXT"; \
-	jj git push --remote soft -b main; \
-	echo "Tagged and pushed $$NEXT"
+	./release.fish

release.fish 🔗

@@ -0,0 +1,286 @@
+#!/usr/bin/env fish
+
+function __release_yatd_usage
+    echo "Usage: release.fish [--from <stage>] [--only <stage>] [--help]
+
+Stages (run in order):
+    tag       Bump Cargo.toml, commit, tag, and push
+    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).
+
+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 \
+    x86_64-unknown-linux-gnu \
+    aarch64-unknown-linux-gnu \
+    x86_64-apple-darwin \
+    aarch64-apple-darwin \
+    x86_64-pc-windows-gnu \
+    x86_64-unknown-freebsd
+
+# UPX 5.x dropped macOS support; Windows and FreeBSD are limited to 32-bit
+# formats. Only Linux targets are packable.
+set -l pack_targets \
+    x86_64-unknown-linux-gnu \
+    aarch64-unknown-linux-gnu
+
+# Global so __should_run can see them. Prefixed to avoid collisions.
+set -g __ry_stages tag build pack upload
+set -g __ry_from ""
+set -g __ry_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 __ry_from $argv[$i]
+        case --only
+            set i (math $i + 1)
+            set -g __ry_only $argv[$i]
+        case --help -h
+            __release_yatd_usage
+            exit 0
+        case '*'
+            echo "Error: unknown flag '$argv[$i]'" >&2
+            __release_yatd_usage >&2
+            exit 1
+    end
+    set i (math $i + 1)
+end
+
+if test -n "$__ry_from" -a -n "$__ry_only"
+    echo "Error: --from and --only are mutually exclusive" >&2
+    exit 1
+end
+
+if test -n "$__ry_from"; and not contains -- "$__ry_from" $__ry_stages
+    echo "Error: unknown stage '$__ry_from' (valid: $__ry_stages)" >&2
+    exit 1
+end
+
+if test -n "$__ry_only"; and not contains -- "$__ry_only" $__ry_stages
+    echo "Error: unknown stage '$__ry_only' (valid: $__ry_stages)" >&2
+    exit 1
+end
+
+# Returns 0 if the given stage should execute, 1 otherwise.
+function __should_run -a stage
+    if test -n "$__ry_only"
+        test "$stage" = "$__ry_only"
+        return
+    end
+
+    if test -z "$__ry_from"
+        return 0
+    end
+
+    set -l reached 0
+    for s in $__ry_stages
+        if test "$s" = "$__ry_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 parent_bookmarks (jj log -r '@-' --no-graph -T 'bookmarks' 2>/dev/null)
+    if not string match -rq 'main|dev' -- "$parent_bookmarks"
+        echo "Error: parent commit is not on main or dev" >&2
+        exit 1
+    end
+
+    if jj diff --summary 2>/dev/null | grep -qE '^[MD] '
+        echo "Error: working copy has modified or deleted files" >&2
+        exit 1
+    end
+
+    # --- Fetch tags ---
+
+    jj git fetch --remote soft
+
+    # --- Compute next version ---
+
+    # jj tag list output is "<tag>: <hash> <desc>"; extract the tag name.
+    set -l current (jj tag list 2>/dev/null | awk '{print $1}' | string replace -r ':$' '' | sort -V | tail -1)
+    if test -z "$current"
+        set current v0.0.0
+    end
+    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 (jj tag list 2>/dev/null \
+                | awk '{print $1}' \
+                | string replace -r ':$' '' \
+                | string replace -r "^$base_next-$prerelease_suffix\\." '' \
+                | string match -r '^\d+$' \
+                | 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
+
+    # --- Bump Cargo.toml and commit ---
+
+    set -l cargo_ver (string replace -r '^v' '' $tag)
+    sed -i "s/^version = \".*\"/version = \"$cargo_ver\"/" Cargo.toml
+    jj commit -m "Bump version to $tag"; or begin
+        echo "Error: jj commit failed" >&2
+        exit 1
+    end
+
+    # --- Tag and push ---
+
+    jj tag set $tag -r @-; or begin
+        echo "Error: tagging failed" >&2
+        exit 1
+    end
+
+    git push soft $tag; or begin
+        echo "Error: tag push failed" >&2
+        exit 1
+    end
+
+    jj git push --remote soft -b main; or begin
+        echo "Error: branch push failed" >&2
+        exit 1
+    end
+
+    echo "Tagged and pushed $tag"
+else
+    set tag (git describe --tags --always 2>/dev/null; or echo dev)
+end
+
+# --- Build ---
+
+if __should_run build
+    rm -rf dist
+    mkdir -p dist
+
+    for target in $targets
+        echo "Building $target..."
+        set -l ext ""
+        if string match -q '*windows*' $target
+            set ext .exe
+        end
+        cargo zigbuild --target $target --release; or begin
+            echo "Error: build failed for $target" >&2
+            exit 1
+        end
+        cp "target/$target/release/td$ext" "dist/td-$tag-$target$ext"
+    end
+end
+
+# --- Pack ---
+
+if __should_run pack
+    for target in $pack_targets
+        set -l bin "dist/td-$tag-$target"
+        if test -f "$bin"
+            echo "Packing $target..."
+            upx -q "$bin"
+        end
+    end
+end
+
+# --- Upload ---
+
+if __should_run upload
+    fish -c "release upload yatd $tag --latest dist/*"
+end