From 9125e1dbefba7e95433cf20d33153266ec4f95e6 Mon Sep 17 00:00:00 2001 From: Salvydas Lukosius Date: Mon, 18 May 2026 22:37:11 +0100 Subject: [PATCH] feat(release): publish zunit binaries from tags --- .github/workflows/release.yml | 39 ++++++++++++++++++++++++++ .github/workflows/zsh-n.yml | 2 +- scripts/verify-release-tag.zsh | 30 ++++++++++++++++++++ tests/release.zunit | 51 ++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/release.yml create mode 100755 scripts/verify-release-tag.zsh create mode 100644 tests/release.zunit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..7ed89f7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +--- +name: "Release" + +on: + push: + tags: ["v*.*.*"] + +permissions: + contents: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + release: + if: github.repository == 'z-shell/zunit' + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Install zsh + run: sudo apt-get update && sudo apt-get install -yq zsh + - name: Build zunit + run: ./build.zsh + - name: Verify release tag + run: | + tag="${GITHUB_REF_NAME}" + scripts/verify-release-tag.zsh "$tag" ./zunit + - name: Publish GitHub release + env: + GH_TOKEN: ${{ github.token }} + run: | + tag="${GITHUB_REF_NAME}" + if gh release view "$tag" >/dev/null 2>&1; then + gh release upload "$tag" ./zunit#zunit --clobber + else + gh release create "$tag" ./zunit#zunit --title "$tag" --generate-notes + fi diff --git a/.github/workflows/zsh-n.yml b/.github/workflows/zsh-n.yml index 4fab7f9..6b51ec4 100644 --- a/.github/workflows/zsh-n.yml +++ b/.github/workflows/zsh-n.yml @@ -20,7 +20,7 @@ jobs: run: sudo apt-get update && sudo apt-get install -yq zsh - name: Check Zsh syntax run: | - find src -type f -name '*.zsh' -print | sort | while read -r file; do + find src scripts -type f -name '*.zsh' -print | sort | while read -r file; do zsh -n "$file" done zsh -n build.zsh diff --git a/scripts/verify-release-tag.zsh b/scripts/verify-release-tag.zsh new file mode 100755 index 0000000..382278b --- /dev/null +++ b/scripts/verify-release-tag.zsh @@ -0,0 +1,30 @@ +#!/usr/bin/env zsh +# -*- mode: zsh; sh-indentation: 2; indent-tabs-mode: nil; sh-basic-offset: 2; -*- +# vim: ft=zsh sw=2 ts=2 et + +builtin emulate -L zsh +builtin setopt err_return no_unset pipe_fail + +local tag="${1:-}" +local binary="${2:-./zunit}" + +if [[ ! "$tag" =~ '^v[0-9]+[.][0-9]+[.][0-9]+$' ]]; then + print -ru2 -- "Expected semantic version tag like v1.2.3, got '${tag:-}'" + exit 1 +fi + +if [[ ! -x "$binary" ]]; then + print -ru2 -- "Release binary '$binary' is missing or is not executable" + exit 1 +fi + +local expected_version="${tag#v}" +local actual_version +actual_version="$($binary --version)" + +if [[ "$actual_version" != "$expected_version" ]]; then + print -ru2 -- "Release tag $tag does not match zunit version $actual_version" + exit 1 +fi + +print -r -- "Release tag $tag matches zunit version $actual_version" diff --git a/tests/release.zunit b/tests/release.zunit new file mode 100644 index 0000000..01dc276 --- /dev/null +++ b/tests/release.zunit @@ -0,0 +1,51 @@ +#!/usr/bin/env zunit + +@test 'release tag guard accepts a matching semantic version tag' { + local sandbox binary + sandbox="$(mktemp -d)" + binary="$sandbox/zunit" + + print -r -- '#!/usr/bin/env zsh' > "$binary" + print -r -- '[[ $1 == --version ]] && print -r -- 1.2.3' >> "$binary" + chmod u+x "$binary" + + run "$PWD/scripts/verify-release-tag.zsh" v1.2.3 "$binary" + + assert "$state" equals 0 + assert "$output" contains 'Release tag v1.2.3 matches zunit version 1.2.3' +} + +@test 'release tag guard rejects a mismatched binary version' { + local sandbox binary + sandbox="$(mktemp -d)" + binary="$sandbox/zunit" + + print -r -- '#!/usr/bin/env zsh' > "$binary" + print -r -- '[[ $1 == --version ]] && print -r -- 1.2.4' >> "$binary" + chmod u+x "$binary" + + run "$PWD/scripts/verify-release-tag.zsh" v1.2.3 "$binary" + + assert "$state" equals 1 + assert "$output" contains 'Release tag v1.2.3 does not match zunit version 1.2.4' +} + +@test 'release tag guard rejects non-semantic tags' { + run "$PWD/scripts/verify-release-tag.zsh" latest ./zunit + + assert "$state" equals 1 + assert "$output" contains 'Expected semantic version tag like v1.2.3' +} + +@test 'release workflow publishes the zunit binary only for semantic tags' { + local workflow + workflow="$(< "$PWD/.github/workflows/release.yml")" + + assert "$workflow" contains 'tags: ["v*.*.*"]' + assert "$workflow" does_not_contain 'branches: [main]' + assert "$workflow" contains 'contents: write' + assert "$workflow" contains 'actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5' + assert "$workflow" contains 'scripts/verify-release-tag.zsh "$tag" ./zunit' + assert "$workflow" contains 'gh release create "$tag" ./zunit#zunit' + assert "$workflow" contains 'gh release upload "$tag" ./zunit#zunit --clobber' +}