Files
omarchy-pkgs/bin/omarchy-pkgs
T
David Heinemeier HanssonandClaude Opus 5 34326295e9 Name the server OMARCHY_REPO_HOST, not OMARCHY_BUILD_HOST
Builds now happen wherever the operator likes, so naming the destination after
building described the old arrangement rather than the current one. What the
push and deploy commands reach is the machine that serves pkgs.omarchy.org and
holds the signing key: the repository host. It also runs the scheduled builds,
which is why the trigger in omarchy-pkgs release points at the same place.

Resolution moves into helpers/host-helpers.sh, which all three commands now
share instead of repeating: --host, then OMARCHY_REPO_HOST, then .repo-host.
OMARCHY_BUILD_HOST and .build-host keep working as fallbacks, so existing
environments and checkouts are unaffected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 03:46:58 -07:00

660 lines
25 KiB
Bash
Executable File

#!/bin/bash
# Omarchy release management for the omarchy + omarchy-settings package pair.
#
# Cuts a release by rewriting both PKGBUILDs in lockstep (same _tag/_commit/
# pkgver/sha256sums), committing, pushing to master, and poking the build host.
# RCs publish to edge only; stable receives finals via `bin/repo migrate`.
#
# Versioning convention (see the PKGBUILD header comments):
# finals X.Y.Z from upstream tag vX.Y.Z
# RCs X.Y.ZrcN attached form ONLY — vercmp orders rc1 < rc2 < final;
# separator forms (X.Y.Z.rcN, X.Y.Z_rcN) sort AFTER final
# pkgrel resets to 1 on every pkgver change; epoch is never set by tooling.
set -e
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/host-helpers.sh"
UPSTREAM_URL="${OMARCHY_UPSTREAM_URL:-https://github.com/basecamp/omarchy.git}"
EDGE_DB_URL="${OMARCHY_EDGE_DB_URL:-https://pkgs.omarchy.org/edge/x86_64/omarchy.db.tar.zst}"
RELEASE_PACKAGES=(omarchy omarchy-settings)
DEFAULT_RC_REF="quattro"
SRCDEST_DIR="$BUILD_ROOT/.srcdest"
CLONE_DIR="$SRCDEST_DIR/omarchy"
show_usage() {
cat <<EOF
Usage: $0 <command> [options]
Commands:
release <vX.Y.Z | vX.Y.Z-rcN> Cut a release from an upstream tag
release latest [rc|beta|alpha] Cut from the newest upstream tag, optionally
restricted to one pre-release channel
release rc [vX.Y.Z] Newest upstream vX.Y.Z-rcN tag → X.Y.ZrcN
release beta|alpha [vX.Y.Z] Same for beta/alpha channels
release rc --commit <sha> Untagged pre-release from a bare commit,
auto-numbered past published edge + PKGBUILD
self-test Run version-normalization and ordering tests
Options for release:
--base <X.Y.Z> (rc) Base version the RC leads up to (default: base of
the current PKGBUILD pkgver)
--commit <sha> (rc) Upstream commit to pin (default: tip of --ref)
--ref <branch> (rc) Upstream branch whose tip to pin (default: $DEFAULT_RC_REF)
--yes Skip confirmation prompts
--host <host> ssh destination of the repository host to trigger,
overriding \$OMARCHY_REPO_HOST and .repo-host
--no-push Rewrite and commit locally; skip push and build trigger
--pr When releasing from a non-master branch, open a GitHub PR
to master with gh after pushing
--rebuild Same version + same commit: bump pkgrel to force a rebuild
--force Allow a version lower than the current PKGBUILD when that
version was never published (rewind an unshipped seed)
--dry-run Resolve, validate, and show the plan; write nothing
-h, --help Show this help message
Every release updates ${RELEASE_PACKAGES[*]} together: same _tag, _commit,
pkgver, and sha256sums. RCs build for edge only. Promote a final to stable
after verifying the edge build:
bin/repo migrate --package omarchy && bin/repo migrate --package omarchy-settings
EOF
}
# --- version helpers ---------------------------------------------------------
# v4.0.0 / v4.0.0-rc1 / v4.0.0-rc.1 / v4.0.0.rc1 / v4.0.0_rc1 → pacman pkgver.
# Pre-release channels alpha/beta/rc normalize to the ATTACHED form, which is
# the only one vercmp orders before the final (and alpha < beta < rc < final).
normalize_tag() {
local v="${1#v}"
if [[ "$v" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "$v"
elif [[ "$v" =~ ^([0-9]+\.[0-9]+\.[0-9]+)[-._](alpha|beta|rc)\.?([0-9]+)$ ]]; then
echo "${BASH_REMATCH[1]}${BASH_REMATCH[2]}${BASH_REMATCH[3]}"
else
return 1
fi
}
version_base() { echo "$1" | sed -E 's/(alpha|beta|rc)[0-9]+$//'; }
version_is_prerelease() { [[ "$1" =~ (alpha|beta|rc)[0-9]+$ ]]; }
# Back-compat alias used by channel output
version_is_rc() { version_is_prerelease "$1"; }
pkgbuild_var() {
local pkg="$1" var="$2"
(cd "$BUILD_ROOT/pkgbuilds/$pkg" && bash -c "source PKGBUILD 2>/dev/null; echo \"\${$var}\"")
}
# Prints the published version of $pkg from the edge DB. Distinguishes
# "package absent" (empty output, rc 0) from "could not fetch/read the DB"
# (rc 2) so callers can fail closed instead of mistaking an outage for a
# first release.
published_edge_version() {
local pkg="$1" tmp descs
tmp=$(mktemp) || return 2
if ! curl -sf "$EDGE_DB_URL" -o "$tmp"; then
rm -f "$tmp"
return 2
fi
if ! descs=$(tar -xO --zstd -f "$tmp" --wildcards '*/desc' 2>/dev/null); then
rm -f "$tmp"
return 2
fi
rm -f "$tmp"
awk -v pkg="$pkg" '
function emit() {
if (!found && name == pkg && version != "") { print version; found = 1 }
name = ""; version = ""
}
$0 == "%FILENAME%" { emit(); next }
$0 == "%NAME%" { getline; name = $0; next }
$0 == "%VERSION%" { getline; version = $0; next }
END { emit() }
' <<<"$descs"
}
# --- upstream resolution -----------------------------------------------------
resolve_tag_commit() {
local tag="$1" peeled sha
peeled=$(git ls-remote "$UPSTREAM_URL" "refs/tags/$tag^{}" | awk '{print $1}')
sha=$(git ls-remote "$UPSTREAM_URL" "refs/tags/$tag" | awk '{print $1}')
echo "${peeled:-$sha}"
}
resolve_ref_commit() {
git ls-remote "$UPSTREAM_URL" "refs/heads/$1" | awk '{print $1}'
}
# Newest upstream tag by vercmp, pre-releases included: v4.0.0-rc1 outranks
# v3.8.4, and a final outranks its own rc/beta/alpha builds. With a channel
# argument (rc/beta/alpha), only tags of that channel are considered.
latest_upstream_tag() {
local channel_filter="${1:-}" best_tag="" best_ver="" tag ver
while IFS= read -r tag; do
ver=$(normalize_tag "$tag") || continue
if [[ -n "$channel_filter" ]] && [[ ! "$ver" =~ ${channel_filter}[0-9]+$ ]]; then
continue
fi
if [[ -z "$best_ver" ]] || [[ $(vercmp "$ver" "$best_ver") -gt 0 ]]; then
best_ver="$ver" best_tag="$tag"
fi
done < <(git ls-remote --tags "$UPSTREAM_URL" | awk -F/ '!/\^\{\}/{print $3}')
[[ -n "$best_tag" ]] && echo "$best_tag"
}
# Newest upstream tag of the form v<base>-<channel>N (any accepted separator),
# picked by vercmp over normalized candidates.
latest_channel_tag() {
local channel="$1" base="$2" best_tag="" best_ver="" tag ver
while IFS= read -r tag; do
[[ "${tag#v}" =~ ^${base//./\\.}[-._]${channel}\.?[0-9]+$ ]] || continue
ver=$(normalize_tag "$tag") || continue
if [[ -z "$best_ver" ]] || [[ $(vercmp "$ver" "$best_ver") -gt 0 ]]; then
best_ver="$ver" best_tag="$tag"
fi
done < <(git ls-remote --tags "$UPSTREAM_URL" | awk -F/ '!/\^\{\}/{print $3}')
[[ -n "$best_tag" ]] && echo "$best_tag"
}
ensure_clone() {
if [[ -d "$CLONE_DIR" ]]; then
git -C "$CLONE_DIR" fetch --quiet origin
else
mkdir -p "$SRCDEST_DIR"
print_info "Cloning $UPSTREAM_URL (cached in $SRCDEST_DIR for future releases)..."
git clone --mirror --quiet "$UPSTREAM_URL" "$CLONE_DIR"
fi
}
# --- guards ------------------------------------------------------------------
# Only the two files the release rewrites must be clean — the commit stages
# and commits exactly those paths, so dirt elsewhere cannot enter the release.
guard_clean_tree() {
local dirty
dirty=$(cd "$BUILD_ROOT" && git status --porcelain -- \
pkgbuilds/omarchy/PKGBUILD pkgbuilds/omarchy-settings/PKGBUILD)
if [[ -n "$dirty" ]]; then
print_error "Uncommitted changes on the release PKGBUILDs — commit or stash them first:"
echo "$dirty"
exit 1
fi
}
# Decides how this cut relates to what exists and sets RELEASE_PKGREL:
# new version -> pkgrel 1 (after the ordering floor)
# same version, different commit -> re-release: pkgrel +1 (tag was moved)
# same version, same commit -> no-op, or pkgrel +1 with --rebuild
guard_version_ordering() {
local new_pkgver="$1" new_commit="$2" rebuild="$3" force="$4" published
if ! published=$(published_edge_version omarchy); then
print_error "Could not read the published edge DB ($EDGE_DB_URL) — refusing to release blind"
exit 1
fi
local current_pkgver current_commit
current_pkgver=$(pkgbuild_var "${RELEASE_PACKAGES[0]}" pkgver)
current_commit=$(pkgbuild_var "${RELEASE_PACKAGES[0]}" _commit)
local current_pkgrel
current_pkgrel=$(pkgbuild_var "${RELEASE_PACKAGES[0]}" pkgrel)
RELEASE_PKGREL=1
if [[ "$new_pkgver" == "$current_pkgver" && "$new_commit" == "$current_commit" ]]; then
if [[ "$rebuild" == true ]]; then
RELEASE_PKGREL=$((current_pkgrel + 1))
print_warning "Rebuild: same source ($new_pkgver @ ${new_commit:0:12}), pkgrel $current_pkgrel → $RELEASE_PKGREL"
return 0
fi
print_success "Nothing to release — omarchy is already at $new_pkgver from commit ${new_commit:0:12} (use --rebuild to force a repackage)"
exit 0
fi
if [[ "$new_pkgver" == "$current_pkgver" ]]; then
# Same version, different commit: the upstream tag was moved/recut.
RELEASE_PKGREL=$((current_pkgrel + 1))
if [[ -n "$published" ]]; then
local published_pkgver="${published%-*}"
if [[ $(vercmp "$new_pkgver" "$published_pkgver") -lt 0 ]]; then
print_error "Refusing re-release of $new_pkgver: edge has already moved on to $published_pkgver"
exit 1
fi
fi
print_warning "Re-release: $new_pkgver was cut from ${current_commit:0:12}, now ${new_commit:0:12} — pkgrel $current_pkgrel → $RELEASE_PKGREL"
return 0
fi
# The floor is the newest version anyone could already have: the published
# edge DB, or the PKGBUILD itself (a cut that hasn't built/synced yet).
local floor="$current_pkgver" floor_src="current PKGBUILD"
if [[ -n "$published" ]]; then
local published_pkgver="${published%-*}"
if [[ $(vercmp "$published_pkgver" "$floor") -gt 0 ]]; then
floor="$published_pkgver" floor_src="published edge DB"
fi
else
print_warning "omarchy not found in the published edge DB yet — guarding against the current PKGBUILD only"
fi
if [[ $(vercmp "$new_pkgver" "$floor") -le 0 ]]; then
if [[ "$force" == true && "$floor_src" == "current PKGBUILD" ]]; then
# Nothing at or above the floor has shipped — rewinding the unshipped
# seed is safe. A published floor is never forceable.
print_warning "--force: rewinding unshipped $floor (current PKGBUILD) → $new_pkgver"
return 0
fi
print_error "Refusing: $new_pkgver does not sort after $floor ($floor_src)"
if [[ "$floor_src" == "published edge DB" ]]; then
print_error "That version is already published — users would never see this release. --force cannot override a published floor."
else
print_error "Cut a newer version, or use --force to rewind the unshipped PKGBUILD seed."
fi
exit 1
fi
print_info "Ordering OK: $floor ($floor_src) → $new_pkgver"
}
guard_rc_before_final() {
local pkgver="$1"
version_is_rc "$pkgver" || return 0
local base
base=$(version_base "$pkgver")
if [[ $(vercmp "$pkgver" "$base") -ge 0 ]]; then
print_error "Refusing: RC pkgver $pkgver does not sort before final $base (normalization bug)"
exit 1
fi
}
guard_lockstep() {
local a b
for var in _tag _commit pkgver pkgrel sha256sums; do
a=$(pkgbuild_var "${RELEASE_PACKAGES[0]}" "$var")
b=$(pkgbuild_var "${RELEASE_PACKAGES[1]}" "$var")
if [[ "$a" != "$b" ]]; then
print_error "Lockstep violation: $var differs between ${RELEASE_PACKAGES[*]} ('$a' vs '$b')"
exit 1
fi
done
}
# --- PKGBUILD rewriting ------------------------------------------------------
rewrite_pkgbuilds() {
local tag="$1" commit="$2" pkgver="$3" pkg
for pkg in "${RELEASE_PACKAGES[@]}"; do
sed -i \
-e "s|^_tag=.*|_tag='$tag'|" \
-e "s|^_commit=.*|_commit='$commit'|" \
-e "s|^pkgver=.*|pkgver=$pkgver|" \
-e "s|^pkgrel=.*|pkgrel=${RELEASE_PKGREL:-1}|" \
"$BUILD_ROOT/pkgbuilds/$pkg/PKGBUILD"
done
}
regenerate_checksums() {
local sum pkg
print_info "Generating sha256sums (makepkg -g)..."
sum=$(cd "$BUILD_ROOT/pkgbuilds/${RELEASE_PACKAGES[0]}" && SRCDEST="$SRCDEST_DIR" makepkg -g 2>/dev/null | grep -oE '[a-f0-9]{64}')
if [[ -z "$sum" ]]; then
print_error "makepkg -g produced no checksum — is the pinned commit reachable upstream?"
exit 1
fi
for pkg in "${RELEASE_PACKAGES[@]}"; do
sed -i -E "s|^(\s*)sha256sums=\('[^']+'\)|\1sha256sums=('$sum')|" "$BUILD_ROOT/pkgbuilds/$pkg/PKGBUILD"
done
for pkg in "${RELEASE_PACKAGES[@]}"; do
print_info "Verifying source integrity for $pkg..."
(cd "$BUILD_ROOT/pkgbuilds/$pkg" && SRCDEST="$SRCDEST_DIR" makepkg --verifysource --skippgpcheck >/dev/null)
done
print_success "sha256sums verified: $sum"
}
# --- trigger -----------------------------------------------------------------
trigger_build_host() {
local host
if ! host=$(resolve_repo_host "${REPO_HOST_OVERRIDE:-}"); then
print_info "No repository host configured (set OMARCHY_REPO_HOST or $BUILD_ROOT/.repo-host)."
print_info "The 6-hourly auto-release timer will pick this up, or trigger manually:"
echo " ssh <build-host> 'git -C /root/omarchy-pkgs pull --ff-only && touch /root/.state/.sync-needed-edge && systemctl start omarchy-auto-release-edge.service'"
return 0
fi
print_info "Triggering edge build on $host..."
if ssh "$host" 'git -C /root/omarchy-pkgs pull --ff-only && mkdir -p /root/.state && touch /root/.state/.sync-needed-edge && systemctl start --no-block omarchy-auto-release-edge.service'; then
print_success "Edge build triggered on $host"
else
print_warning "Could not trigger $host — the 6-hourly timer will pick it up"
fi
}
# --- release command ---------------------------------------------------------
cmd_release() {
local target="" target_arg="" base="" commit_arg="" ref="" dry_run=false assume_yes=false no_push=false open_pr=false rebuild=false force=false
while [[ $# -gt 0 ]]; do
case $1 in
--base) base="$2"; shift 2 ;;
--no-push) no_push=true; shift ;;
--pr) open_pr=true; shift ;;
--rebuild) rebuild=true; shift ;;
--force) force=true; shift ;;
--commit) commit_arg="$2"; shift 2 ;;
--ref) ref="$2"; shift 2 ;;
--host) REPO_HOST_OVERRIDE="$2"; shift 2 ;;
--yes) assume_yes=true; shift ;;
--dry-run) dry_run=true; shift ;;
-h | --help) show_usage; exit 0 ;;
-*) print_error "Unknown option: $1"; exit 1 ;;
*)
if [[ -z "$target" ]]; then
target="$1"
elif [[ -z "$target_arg" ]]; then
target_arg="$1"
else
print_error "Unexpected argument: $1"; exit 1
fi
shift ;;
esac
done
# A bare-commit release is an RC: --commit/--ref/--base imply rc mode.
if [[ -z "$target" && ( -n "$commit_arg" || -n "$ref" || -n "$base" ) ]]; then
target="rc"
fi
if [[ -z "$target" ]]; then
print_error "Usage: $0 release <vX.Y.Z | vX.Y.Z-rcN | latest | rc | beta | alpha> [options]"
exit 1
fi
if [[ ! "$target" =~ ^(rc|beta|alpha)$ && ( -n "$base" || -n "$commit_arg" || -n "$ref" ) ]]; then
print_error "--base/--commit/--ref only apply to pre-release modes (rc/beta/alpha)"
exit 1
fi
print_header "Omarchy Release"
# Fail fast on a dirty tree before touching the network. --no-push commits
# only the two PKGBUILD files locally, so a dirty tree elsewhere is fine.
if [[ "$dry_run" != true && "$no_push" != true ]]; then
guard_clean_tree
elif [[ "$no_push" == true ]]; then
print_warning "--no-push: will commit locally to $(cd "$BUILD_ROOT" && git rev-parse --abbrev-ref HEAD); no push, no build trigger"
fi
local tag="" commit="" pkgver=""
case "$target" in
latest)
local channel_filter=""
if [[ -n "$target_arg" ]]; then
if [[ "$target_arg" =~ ^(rc|beta|alpha)$ ]]; then
channel_filter="$target_arg"
else
print_error "Unknown argument '$target_arg' — usage: release latest [rc|beta|alpha]"
exit 1
fi
fi
print_info "Finding newest upstream ${channel_filter:-}${channel_filter:+ }tag..."
tag=$(latest_upstream_tag "$channel_filter" || true)
if [[ -z "$tag" ]]; then
print_error "No ${channel_filter:+$channel_filter }release tags found at $UPSTREAM_URL"
exit 1
fi
pkgver=$(normalize_tag "$tag")
commit=$(resolve_tag_commit "$tag")
;;
rc | beta | alpha)
local channel="$target"
# `release rc v4.0.0` → newest upstream v4.0.0-rcN tag
if [[ -n "$target_arg" && -z "$commit_arg" && -z "$ref" ]]; then
base="${target_arg#v}"
if [[ ! "$base" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
print_error "Invalid base '$target_arg' (expected vX.Y.Z or X.Y.Z)"
exit 1
fi
print_info "Finding newest upstream ${channel} tag for $base..."
tag=$(latest_channel_tag "$channel" "$base" || true)
if [[ -z "$tag" ]]; then
print_error "No v$base-${channel}N tag found at $UPSTREAM_URL"
print_error "Tag one upstream (on quattro), or cut an untagged ${channel} with: $0 release $channel --commit <sha> --base $base"
exit 1
fi
pkgver=$(normalize_tag "$tag")
commit=$(resolve_tag_commit "$tag")
if [[ -z "$commit" ]]; then
print_error "Could not resolve $tag to a commit"
exit 1
fi
else
if [[ -n "$target_arg" ]]; then base="${base:-${target_arg#v}}"; fi
if [[ -z "$base" ]]; then
base=$(version_base "$(pkgbuild_var "${RELEASE_PACKAGES[0]}" pkgver)")
print_info "No --base given; using current PKGBUILD base: $base"
fi
if [[ ! "$base" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
print_error "Invalid --base '$base' (expected X.Y.Z)"
exit 1
fi
if [[ -n "$commit_arg" ]]; then
ensure_clone
commit=$(git -C "$CLONE_DIR" rev-parse --verify --quiet "$commit_arg^{commit}") || {
print_error "Commit '$commit_arg' not found in upstream $UPSTREAM_URL"
exit 1
}
else
ref="${ref:-$DEFAULT_RC_REF}"
commit=$(resolve_ref_commit "$ref")
if [[ -z "$commit" ]]; then
print_error "Branch '$ref' not found upstream"
exit 1
fi
fi
# Next rc number: one past the highest of the published edge DB and the
# current PKGBUILD for this base.
local published_ver
if ! published_ver=$(published_edge_version omarchy); then
print_error "Could not read the published edge DB ($EDGE_DB_URL) — refusing to auto-number the RC blind"
exit 1
fi
local highest=0 candidate
for candidate in "${published_ver%-*}" "$(pkgbuild_var "${RELEASE_PACKAGES[0]}" pkgver)"; do
if [[ "$candidate" =~ ^${base//./\\.}${channel}([0-9]+)$ ]] && (( BASH_REMATCH[1] > highest )); then
highest=${BASH_REMATCH[1]}
fi
done
pkgver="${base}${channel}$((highest + 1))"
tag=""
fi
;;
v*)
pkgver=$(normalize_tag "$target") || {
print_error "Cannot parse '$target' as a release tag."
print_error "Accepted: vX.Y.Z, vX.Y.Z-rcN, vX.Y.Z-rc.N, vX.Y.Z.rcN, vX.Y.Z_rcN"
exit 1
}
tag="$target"
print_info "Resolving $tag upstream..."
commit=$(resolve_tag_commit "$tag")
if [[ -z "$commit" ]]; then
print_error "Tag '$tag' not found at $UPSTREAM_URL"
exit 1
fi
;;
*)
print_error "Unknown release target '$target' (expected vX.Y.Z, latest, or rc)"
exit 1
;;
esac
guard_rc_before_final "$pkgver"
guard_version_ordering "$pkgver" "$commit" "$rebuild" "$force"
echo ""
print_info "Packages: ${RELEASE_PACKAGES[*]}"
print_info "Tag: ${tag:-<none — cut from bare commit>}"
print_info "Commit: $commit"
print_info "Pkgver: $pkgver-$RELEASE_PKGREL"
if version_is_prerelease "$pkgver"; then
print_info "Channel: edge only (pre-release)"
else
print_info "Channel: edge, then promote to stable via bin/repo migrate"
fi
echo ""
if [[ "$dry_run" == true ]]; then
print_success "Dry run complete — nothing written."
exit 0
fi
if [[ "$assume_yes" != true ]]; then
local reply
read -r -p "Cut release $pkgver from ${tag:-$commit}? [y/N] " reply
[[ "$reply" =~ ^[Yy]$ ]] || { print_info "Aborted."; exit 1; }
fi
ensure_clone
rewrite_pkgbuilds "$tag" "$commit" "$pkgver"
regenerate_checksums
guard_lockstep
if [[ "$no_push" == true ]]; then
print_info "Committing locally (--no-push)..."
(cd "$BUILD_ROOT" &&
git commit -m "Release omarchy $pkgver" -- pkgbuilds/omarchy/PKGBUILD pkgbuilds/omarchy-settings/PKGBUILD)
print_success "Committed release omarchy $pkgver (not pushed, build not triggered)"
else
local branch
branch=$(cd "$BUILD_ROOT" && git rev-parse --abbrev-ref HEAD)
print_info "Committing and pushing to $branch..."
(cd "$BUILD_ROOT" &&
git commit -m "Release omarchy $pkgver" -- pkgbuilds/omarchy/PKGBUILD pkgbuilds/omarchy-settings/PKGBUILD &&
git push origin HEAD)
print_success "Pushed release omarchy $pkgver to $branch"
if [[ "$branch" == "master" ]]; then
trigger_build_host
else
if [[ "$open_pr" == true ]]; then
if command -v gh >/dev/null; then
gh pr create --base master --head "$branch" \
--title "Release omarchy $pkgver" \
--body "$(printf 'Cut with bin/omarchy-pkgs release.\n\n- pkgver: %s-1\n- tag: %s\n- commit: %s\n\nMerging publishes to edge on the next auto-release cycle.%s' \
"$pkgver" "${tag:-<none — bare commit>}" "$commit" \
"$(version_is_prerelease "$pkgver" || echo ' Promote to stable afterwards with: bin/repo migrate --package omarchy && bin/repo migrate --package omarchy-settings')")"
else
print_warning "--pr requested but gh is not installed — open the PR manually"
fi
else
print_info "Released on branch '$branch' — merge it to master to go live (or rerun with --pr); the build host follows master."
fi
fi
fi
echo ""
if version_is_prerelease "$pkgver"; then
print_info "Pre-release flow: $pkgver builds for edge only. Stable is untouched."
else
print_info "After the edge build completes and you have verified it, promote to stable:"
echo " bin/repo migrate --package omarchy && bin/repo migrate --package omarchy-settings"
echo " bin/repo sync --mirror stable"
fi
}
# --- self-test ---------------------------------------------------------------
cmd_self_test() {
local failures=0
check_norm() {
local input="$1" expected="$2" got
got=$(normalize_tag "$input" 2>/dev/null) || got="<reject>"
if [[ "$got" == "$expected" ]]; then
echo " ok: $input → $got"
else
echo " FAIL: $input → $got (expected $expected)"
failures=$((failures + 1))
fi
}
check_vercmp() {
local a="$1" op="$2" b="$3" got
got=$(vercmp "$a" "$b")
local ok=false
case "$op" in
"<") [[ "$got" -lt 0 ]] && ok=true ;;
">") [[ "$got" -gt 0 ]] && ok=true ;;
"=") [[ "$got" -eq 0 ]] && ok=true ;;
esac
if [[ "$ok" == true ]]; then
echo " ok: $a $op $b"
else
echo " FAIL: expected $a $op $b (vercmp said $got)"
failures=$((failures + 1))
fi
}
print_header "omarchy-pkgs self-test"
echo "Tag normalization:"
check_norm v4.0.0 4.0.0
check_norm v4.0.0-rc1 4.0.0rc1
check_norm v4.0.0-rc.2 4.0.0rc2
check_norm v4.0.0.rc3 4.0.0rc3
check_norm v4.0.0_rc4 4.0.0rc4
check_norm 4.1.0 4.1.0
check_norm v4.0.0-rc10 4.0.0rc10
check_norm v4.0 "<reject>"
check_norm v4.0.0-beta1 4.0.0beta1
check_norm v4.0.0-alpha.2 4.0.0alpha2
check_norm v4.0.0-preview1 "<reject>"
check_norm v4.0.0rc "<reject>"
check_norm garbage "<reject>"
check_norm v4.0.0-rc "<reject>"
echo "Pacman ordering of normalized outputs:"
check_vercmp 4.0.0rc1 "<" 4.0.0
check_vercmp 4.0.0rc1 "<" 4.0.0rc2
check_vercmp 4.0.0rc2 "<" 4.0.0rc10
check_vercmp 4.0.0 ">" 4.0.0rc99
check_vercmp 4.0.1 ">" 4.0.0
check_vercmp 4.0.0 "<" 4.1.0rc1
check_vercmp 4.0.0alpha1 "<" 4.0.0beta1
check_vercmp 4.0.0beta1 "<" 4.0.0rc1
check_vercmp 4.0.0beta2 "<" 4.0.0
check_vercmp 4.0.0beta2 "<" 4.0.0beta10
echo "Version helpers:"
[[ $(version_base 4.0.0beta3) == 4.0.0 ]] && echo " ok: version_base 4.0.0beta3 → 4.0.0" || { echo " FAIL: version_base beta"; failures=$((failures + 1)); }
[[ $(version_base 4.0.0rc7) == 4.0.0 ]] && echo " ok: version_base 4.0.0rc7 → 4.0.0" || { echo " FAIL: version_base"; failures=$((failures + 1)); }
version_is_rc 4.0.0rc1 && echo " ok: 4.0.0rc1 is rc" || { echo " FAIL: version_is_rc positive"; failures=$((failures + 1)); }
version_is_rc 4.0.0 && { echo " FAIL: version_is_rc negative"; failures=$((failures + 1)); } || echo " ok: 4.0.0 is not rc"
echo ""
if [[ "$failures" -eq 0 ]]; then
print_success "Self-test passed"
else
print_error "$failures self-test failure(s)"
exit 1
fi
}
# --- dispatch ----------------------------------------------------------------
case "${1:-}" in
release)
shift
cmd_release "$@"
;;
self-test)
cmd_self_test
;;
-h | --help | "")
show_usage
;;
*)
print_error "Unknown command: $1"
show_usage
exit 1
;;
esac