1#!/usr/bin/env fish
2
3# SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
4#
5# SPDX-License-Identifier: CC0-1.0
6
7function __release_lune_usage
8 echo "Usage: release.fish [--from <stage>] [--only <stage>] [--help]
9
10Stages (run in order):
11 tag Compute next version, create and push annotated tag
12 build Cross-compile for all release targets
13 pack Compress Linux binaries with UPX
14 upload Upload artifacts via release(1)
15
16Flags:
17 --from <stage> Start at this stage and continue through the rest
18 --only <stage> Run just this one stage
19 --help, -h Show this help
20
21With no flags, all stages run in order (tag → build → pack → upload).
22The tag stage also notifies the Go module proxy after pushing.
23
24Examples:
25 ./release.fish Full release
26 ./release.fish --from build Rebuild, pack, and upload (skip tagging)
27 ./release.fish --only build Just cross-compile
28 ./release.fish --only upload Re-upload existing dist/ artifacts"
29end
30
31set -l targets \
32 linux/amd64 \
33 linux/arm64 \
34 darwin/amd64 \
35 darwin/arm64 \
36 windows/amd64 \
37 freebsd/amd64
38
39# UPX 5.x dropped macOS support; windows/amd64 is PE32 only and
40# freebsd/amd64 is ELF32 only, so only Linux targets are packed.
41set -l pack_targets \
42 linux-amd64 \
43 linux-arm64
44
45# Global so __should_run can see them. Prefixed to avoid collisions.
46set -g __rl_stages tag build pack upload
47set -g __rl_from ""
48set -g __rl_only ""
49
50# --- Parse flags ---
51
52set -l i 1
53while test $i -le (count $argv)
54 switch $argv[$i]
55 case --from
56 set i (math $i + 1)
57 if test $i -gt (count $argv)
58 echo "Error: --from requires a value" >&2
59 __release_lune_usage >&2
60 exit 1
61 end
62 set -g __rl_from $argv[$i]
63 case --only
64 set i (math $i + 1)
65 if test $i -gt (count $argv)
66 echo "Error: --only requires a value" >&2
67 __release_lune_usage >&2
68 exit 1
69 end
70 set -g __rl_only $argv[$i]
71 case --help -h
72 __release_lune_usage
73 exit 0
74 case '*'
75 echo "Error: unknown flag '$argv[$i]'" >&2
76 __release_lune_usage >&2
77 exit 1
78 end
79 set i (math $i + 1)
80end
81
82if test -n "$__rl_from" -a -n "$__rl_only"
83 echo "Error: --from and --only are mutually exclusive" >&2
84 exit 1
85end
86
87if test -n "$__rl_from"; and not contains -- "$__rl_from" $__rl_stages
88 echo "Error: unknown stage '$__rl_from' (valid: $__rl_stages)" >&2
89 exit 1
90end
91
92if test -n "$__rl_only"; and not contains -- "$__rl_only" $__rl_stages
93 echo "Error: unknown stage '$__rl_only' (valid: $__rl_stages)" >&2
94 exit 1
95end
96
97# Returns 0 if the given stage should execute, 1 otherwise.
98function __should_run -a stage
99 if test -n "$__rl_only"
100 test "$stage" = "$__rl_only"
101 return
102 end
103
104 if test -z "$__rl_from"
105 return 0
106 end
107
108 set -l reached 0
109 for s in $__rl_stages
110 if test "$s" = "$__rl_from"
111 set reached 1
112 end
113 if test $reached -eq 1 -a "$s" = "$stage"
114 return 0
115 end
116 end
117 return 1
118end
119
120# --- Resolve tag ---
121#
122# When skipping the tag stage we still need a tag for filenames and
123# ldflags, so fall back to git describe.
124
125set -l tag
126
127if __should_run tag
128 # --- Guards ---
129
130 set -l branch (git symbolic-ref --short HEAD)
131 if test "$branch" != main -a "$branch" != dev
132 echo "Error: not on main or dev branch (on $branch)" >&2
133 exit 1
134 end
135
136 if test (git status --porcelain=2 | wc -l) -ne 0
137 echo "Error: git working tree is dirty" >&2
138 exit 1
139 end
140
141 # --- Fetch tags ---
142
143 git fetch soft --tags
144
145 # --- Compute next version ---
146
147 set -l current (git describe --tags --abbrev=0 2>/dev/null; or echo v0.0.0)
148 set -l bump (gum choose major minor patch prerelease)
149
150 # Detect whether the current tag is already a prerelease
151 # (e.g. v1.2.3-rc.4).
152 set -l current_is_prerelease no
153 if string match -rq -- '-[a-zA-Z]+\.[0-9]+$' $current
154 set current_is_prerelease yes
155 end
156
157 # Ask whether to create a prerelease, unless the user already chose
158 # "prerelease".
159 set -l is_prerelease no
160 if test "$bump" = prerelease
161 set is_prerelease yes
162 else
163 if gum confirm "Create pre-release?"
164 set is_prerelease yes
165 end
166 end
167
168 # Determine the prerelease suffix (e.g. "beta", "rc").
169 set -l prerelease_suffix ""
170 if test "$bump" = prerelease -a "$current_is_prerelease" = yes
171 # Reuse the suffix from the current prerelease tag.
172 set prerelease_suffix (string replace -r '.*-([a-zA-Z]+)\.[0-9]+$' '$1' $current)
173 else if test "$is_prerelease" = yes
174 set prerelease_suffix (gum input --placeholder "Enter pre-release suffix (e.g. beta, rc)")
175 if test -z "$prerelease_suffix"
176 echo "Error: pre-release suffix is required" >&2
177 exit 1
178 end
179 end
180
181 # Compute the base version (without prerelease suffix).
182 set -l base_next
183 if test "$bump" = prerelease -a "$current_is_prerelease" = yes
184 # Strip the prerelease suffix to get the base
185 # (e.g. v1.2.3-rc.4 → v1.2.3).
186 set base_next (string replace -r -- '-[a-zA-Z]+\.[0-9]+$' '' $current)
187 else
188 set base_next (svu $bump)
189 end
190
191 # Compute the suffix version number.
192 set -l suffix_ver ""
193 if test "$is_prerelease" = yes -a -n "$prerelease_suffix"
194 if test "$bump" = prerelease -a "$current_is_prerelease" = yes
195 # Increment the current prerelease number.
196 set -l current_num (string replace -r '.*-[a-zA-Z]+\.([0-9]+)$' '$1' $current)
197 set suffix_ver (math $current_num + 1)
198 else
199 # Find existing tags with this suffix and increment past the
200 # highest.
201 set -l highest (git tag -l "$base_next-$prerelease_suffix.*" \
202 | string replace -r ".*-$prerelease_suffix\." '' \
203 | sort -n | tail -1)
204 if test -n "$highest"
205 set suffix_ver (math $highest + 1)
206 else
207 set suffix_ver 0
208 end
209 end
210 end
211
212 # Assemble the final tag.
213 if test "$is_prerelease" = yes -a -n "$prerelease_suffix"
214 set tag "$base_next-$prerelease_suffix.$suffix_ver"
215 else
216 set tag "$base_next"
217 end
218
219 # --- Confirm ---
220
221 read -P "Release $tag? [y/N] " confirm
222 if test "$confirm" != y -a "$confirm" != Y
223 echo Aborted.
224 exit 0
225 end
226
227 # --- Tag and push ---
228
229 git tag -a $tag; or begin
230 echo "Error: tagging failed" >&2
231 exit 1
232 end
233
234 git push soft $tag; or begin
235 echo "Error: tag push failed — deleting local tag" >&2
236 git tag -d $tag 2>/dev/null
237 exit 1
238 end
239
240 echo "Released $tag"
241
242 # --- Notify Go module proxy ---
243
244 go list -m git.secluded.site/lune@$tag >/dev/null; or begin
245 echo "Warning: module proxy notification failed" >&2
246 end
247else
248 set tag (git describe --tags --always 2>/dev/null; or echo dev)
249end
250
251# --- Build ---
252
253if __should_run build
254 set -l ldflags "-s -w -X main.version=$tag"
255
256 rm -rf dist
257 mkdir -p dist
258
259 for target in $targets
260 set -l os (echo $target | cut -d/ -f1)
261 set -l arch (echo $target | cut -d/ -f2)
262 set -l ext ""
263 if test "$os" = windows
264 set ext .exe
265 end
266
267 echo "Building $os/$arch..."
268 env CGO_ENABLED=0 GOOS=$os GOARCH=$arch \
269 go build -o "dist/lune-$tag-$os-$arch$ext" -ldflags "$ldflags"; or begin
270 echo "Error: build failed for $os/$arch" >&2
271 exit 1
272 end
273 end
274end
275
276# --- Pack ---
277
278if __should_run pack
279 for suffix in $pack_targets
280 set -l bin "dist/lune-$tag-$suffix"
281 if test -f "$bin"
282 echo "Packing $suffix..."
283 upx -q "$bin"
284 end
285 end
286end
287
288# --- Upload ---
289
290if __should_run upload
291 fish -c "release upload lune $tag --latest dist/*"
292end