Skip to content

Release

Release #27

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
mode:
description: Release workflow mode.
required: true
default: prepare
type: choice
options:
- prepare
- publish
version:
description: Version without v, e.g. 0.3.2.
required: true
type: string
release_date:
description: Optional release date in YYYY-MM-DD format. Defaults to today.
required: false
default: ""
type: string
dry_run:
description: Validate without opening a PR.
required: false
default: true
type: boolean
push:
tags:
- 'v*'
concurrency:
group: release-${{ github.event_name == 'workflow_dispatch' && inputs.version || github.ref_name }}
cancel-in-progress: false
jobs:
prepare-release-notes:
if: ${{ github.event_name == 'workflow_dispatch' && inputs.mode == 'prepare' }}
runs-on: macos-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve release inputs
id: release
run: |
version="${{ inputs.version }}"
version="${version#v}"
release_date="${{ inputs.release_date }}"
if [ -z "$release_date" ]; then
release_date="$(date +%Y-%m-%d)"
fi
case "$version" in
*[!A-Za-z0-9.+-]*)
echo "Unsupported version: use SemVer-compatible characters." >&2
exit 1
;;
esac
safe_version="$(printf '%s' "$version" | tr '+.' '--')"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "release_date=$release_date" >> "$GITHUB_OUTPUT"
echo "branch=codex/release-notes/v${safe_version}" >> "$GITHUB_OUTPUT"
- name: Make Scripts Executable
run: chmod +x scripts/*.sh
- name: Add dry-run fragment
if: ${{ inputs.dry_run && endsWith(steps.release.outputs.version, '-test') }}
run: |
if ! find changelog.d -maxdepth 1 -type f -name '*.md' ! -name 'README.md' | grep -q .; then
printf -- '- Exercise release preparation dry-run.\n' > changelog.d/dry-run.changed.md
fi
- name: Prepare Changelog
run: ./scripts/prepare-changelog-release.sh "${{ steps.release.outputs.version }}" "${{ steps.release.outputs.release_date }}"
- name: Validate Release Notes
run: ./scripts/extract-release-notes.sh "${{ steps.release.outputs.version }}"
- name: Show dry-run diff
if: ${{ inputs.dry_run }}
run: git diff -- CHANGELOG.md changelog.d
- name: Open release notes PR
if: ${{ !inputs.dry_run }}
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: ${{ steps.release.outputs.branch }}
delete-branch: false
add-paths: |
CHANGELOG.md
changelog.d
commit-message: "chore(release): prepare v${{ steps.release.outputs.version }} notes"
title: "chore(release): prepare v${{ steps.release.outputs.version }} notes"
body: |
Prepares the finalized `CHANGELOG.md` section for `v${{ steps.release.outputs.version }}`.
Review the release notes before merging. Publishing should happen only after this PR lands on `main`.
publish-release:
if: ${{ (github.event_name == 'workflow_dispatch' && inputs.mode == 'publish') || startsWith(github.ref, 'refs/tags/v') }}
runs-on: macos-latest
environment: release
permissions:
contents: write
steps:
- name: Checkout Code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- name: Install XcodeGen
run: brew install xcodegen
- name: Resolve Release
id: release
run: |
if [ "${GITHUB_REF#refs/tags/}" != "$GITHUB_REF" ]; then
version="${GITHUB_REF#refs/tags/v}"
create_tag="false"
dry_run="false"
else
version="${{ inputs.version }}"
version="${version#v}"
create_tag="true"
dry_run="${{ inputs.dry_run }}"
fi
if [ -z "$version" ]; then
echo "Could not resolve a release version." >&2
exit 1
fi
case "$version" in
*[!A-Za-z0-9.+-]*)
echo "Unsupported version: use SemVer-compatible characters." >&2
exit 1
;;
esac
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=v$version" >> "$GITHUB_OUTPUT"
echo "create_tag=$create_tag" >> "$GITHUB_OUTPUT"
echo "dry_run=$dry_run" >> "$GITHUB_OUTPUT"
- name: Make Scripts Executable
run: chmod +x scripts/*.sh
- name: Check Tag Availability
id: tag_check
if: ${{ steps.release.outputs.create_tag == 'true' }}
run: |
git fetch --tags --force
if git rev-parse -q --verify "refs/tags/${{ steps.release.outputs.tag }}" >/dev/null; then
echo "Tag already exists: ${{ steps.release.outputs.tag }}." >&2
exit 1
fi
echo "publish=true" >> "$GITHUB_OUTPUT"
- name: Validate Release Notes
if: ${{ steps.release.outputs.create_tag != 'true' || steps.tag_check.outputs.publish == 'true' }}
run: ./scripts/extract-release-notes.sh "${{ steps.release.outputs.version }}" >/dev/null
- name: Run Quality Gate
if: ${{ steps.release.outputs.create_tag != 'true' || steps.tag_check.outputs.publish == 'true' }}
run: ./scripts/swift-quality-gate.sh local
- name: Build and Package
if: ${{ steps.release.outputs.create_tag != 'true' || steps.tag_check.outputs.publish == 'true' }}
run: ./scripts/build-release.sh ${{ steps.release.outputs.version }}
- name: Extract Release Notes
if: ${{ steps.release.outputs.create_tag != 'true' || steps.tag_check.outputs.publish == 'true' }}
id: extract_notes
run: |
VERSION="${{ steps.release.outputs.version }}"
NOTES_FILE="$RUNNER_TEMP/release-notes.md"
./scripts/extract-release-notes.sh "$VERSION" > "$NOTES_FILE"
echo "NOTES<<EOF" >> $GITHUB_OUTPUT
cat "$NOTES_FILE" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Upload Release Artifact
if: ${{ steps.release.outputs.create_tag != 'true' || steps.tag_check.outputs.publish == 'true' }}
uses: actions/upload-artifact@v4
with:
name: Kubebar-${{ steps.release.outputs.tag }}
path: Kubebar.zip
- name: Create Tag
if: ${{ steps.release.outputs.dry_run != 'true' && steps.release.outputs.create_tag == 'true' && steps.tag_check.outputs.publish == 'true' }}
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "${{ steps.release.outputs.tag }}" -m "Release ${{ steps.release.outputs.tag }}"
git push origin "${{ steps.release.outputs.tag }}"
- name: Create GitHub Release
if: ${{ steps.release.outputs.dry_run != 'true' && (steps.release.outputs.create_tag != 'true' || steps.tag_check.outputs.publish == 'true') }}
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.release.outputs.tag }}
files: Kubebar.zip
body: |
${{ steps.extract_notes.outputs.NOTES }}
---
**Installation Note (Ad-hoc Signature)**:
Kubebar is currently distributed with an Ad-hoc signature. To run the app:
1. Download and extract `Kubebar.zip`.
2. **Right-click** `Kubebar.app` and select **Open**.
3. Click **Open** again in the security dialog.
4. Alternatively, run `xattr -cr /Applications/Kubebar.app` in your terminal to remove the quarantine flag.
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}