Skip to content
This repository was archived by the owner on May 6, 2026. It is now read-only.

Commit 8f61d24

Browse files
yysjasmineclaude
andcommitted
release: add GitHub Actions workflow + local gh release script
Two ways to publish a Release, depending on the situation: - .github/workflows/release.yml — on every v*.*.* tag push, builds the Windows .exe on windows-latest (via choco install innosetup + iscc) and the macOS .pkg on macos-latest (via macos/build-pkg.sh) in parallel, then attaches both + SHA256SUMS to a Release for that tag. Also exposed as workflow_dispatch for dry runs. - scripts/release.sh <tag> — publish from locally-built dist/ artifacts when CI is unavailable. Requires the .exe, includes the .pkg if present (warns otherwise), generates SHA256SUMS, creates + pushes the tag if missing, then `gh release create`s the release. Prefer the workflow — clean build envs and no local toolchain required. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent bebee5f commit 8f61d24

2 files changed

Lines changed: 236 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
name: Release
2+
3+
# Triggered when a semver tag (v1.0.0, v1.2.3-beta) is pushed.
4+
# Builds the Windows .exe and macOS .pkg in parallel on platform-native
5+
# runners, then attaches both to the GitHub Release for that tag (creating
6+
# the Release if the tag was pushed without one).
7+
on:
8+
push:
9+
tags:
10+
- "v*.*.*"
11+
# Also allow a manual trigger from the Actions tab for dry runs.
12+
workflow_dispatch:
13+
inputs:
14+
tag:
15+
description: "Tag to release (e.g. v1.0.0). Required for workflow_dispatch."
16+
required: true
17+
type: string
18+
19+
permissions:
20+
# Needed so the release job can create / upload to a Release.
21+
contents: write
22+
23+
jobs:
24+
build-windows:
25+
name: Build Windows .exe
26+
runs-on: windows-latest
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- name: Install Inno Setup
31+
# chocolatey is preinstalled on windows-latest runners.
32+
run: choco install -y innosetup
33+
34+
- name: Build installer
35+
shell: pwsh
36+
working-directory: windows
37+
run: iscc installer.iss
38+
39+
- name: Upload artifact
40+
uses: actions/upload-artifact@v4
41+
with:
42+
name: windows-exe
43+
path: dist/AgentPack-Setup-*.exe
44+
if-no-files-found: error
45+
46+
build-macos:
47+
name: Build macOS .pkg
48+
runs-on: macos-latest
49+
steps:
50+
- uses: actions/checkout@v4
51+
52+
- name: Build .pkg
53+
working-directory: macos
54+
run: ./build-pkg.sh
55+
56+
- name: Upload artifact
57+
uses: actions/upload-artifact@v4
58+
with:
59+
name: macos-pkg
60+
path: dist/AgentPack-*.pkg
61+
if-no-files-found: error
62+
63+
release:
64+
name: Create GitHub Release
65+
needs: [build-windows, build-macos]
66+
runs-on: ubuntu-latest
67+
steps:
68+
- name: Download Windows artifact
69+
uses: actions/download-artifact@v4
70+
with:
71+
name: windows-exe
72+
path: dist
73+
74+
- name: Download macOS artifact
75+
uses: actions/download-artifact@v4
76+
with:
77+
name: macos-pkg
78+
path: dist
79+
80+
- name: Resolve tag
81+
id: tag
82+
# On tag pushes, github.ref_name is the tag (e.g. v1.0.0).
83+
# On workflow_dispatch, the user-supplied input is the tag.
84+
run: |
85+
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
86+
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
87+
else
88+
echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
89+
fi
90+
91+
- name: Generate checksums
92+
working-directory: dist
93+
run: sha256sum AgentPack-* > SHA256SUMS
94+
95+
- name: Create / update Release
96+
uses: softprops/action-gh-release@v2
97+
with:
98+
tag_name: ${{ steps.tag.outputs.tag }}
99+
name: Agent Pack ${{ steps.tag.outputs.tag }}
100+
generate_release_notes: true
101+
# fail_on_unmatched_files keeps us honest if a build artifact
102+
# silently went missing before upload.
103+
fail_on_unmatched_files: true
104+
files: |
105+
dist/AgentPack-Setup-*.exe
106+
dist/AgentPack-*.pkg
107+
dist/SHA256SUMS

scripts/release.sh

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/usr/bin/env bash
2+
# release.sh — Publish a GitHub Release from locally-built artifacts.
3+
#
4+
# Usage:
5+
# scripts/release.sh <tag> [--draft] [--prerelease] [--notes-file <path>]
6+
#
7+
# Examples:
8+
# scripts/release.sh v1.0.0
9+
# scripts/release.sh v1.1.0-rc1 --prerelease
10+
# scripts/release.sh v1.0.0 --notes-file RELEASE_NOTES.md
11+
#
12+
# What it does:
13+
# 1. Verifies dist/AgentPack-Setup-<ver>.exe exists (mandatory).
14+
# 2. Includes dist/AgentPack-<ver>.pkg if present (skipped with a warning
15+
# otherwise — you can re-run with --include-pkg-only after you build
16+
# the .pkg on a Mac, and `gh release upload` will add it then).
17+
# 3. Generates dist/SHA256SUMS over whatever files are being uploaded.
18+
# 4. Creates the git tag locally if missing, pushes it, then calls
19+
# `gh release create` with all artifacts attached.
20+
#
21+
# Prefer the GitHub Actions workflow (.github/workflows/release.yml) when
22+
# possible: push a tag, CI builds both platforms in clean environments, and
23+
# attaches .exe + .pkg automatically. Use this script only when you need
24+
# to publish a one-off release from your own machine.
25+
26+
set -euo pipefail
27+
28+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
29+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
30+
DIST_DIR="$PROJECT_ROOT/dist"
31+
32+
die() { echo "[!] $*" >&2; exit 1; }
33+
note() { echo "[*] $*"; }
34+
ok() { echo "[OK] $*"; }
35+
36+
[ $# -ge 1 ] || die "Usage: $(basename "$0") <tag> [--draft] [--prerelease] [--notes-file <path>]"
37+
TAG="$1"; shift
38+
39+
# Passthrough flags for `gh release create` — we don't interpret them.
40+
GH_FLAGS=()
41+
while [ $# -gt 0 ]; do
42+
case "$1" in
43+
--draft|--prerelease|--latest|--generate-notes)
44+
GH_FLAGS+=("$1"); shift ;;
45+
--notes-file|--notes|--title|--target)
46+
GH_FLAGS+=("$1" "$2"); shift 2 ;;
47+
*)
48+
die "Unknown flag: $1" ;;
49+
esac
50+
done
51+
52+
# Guard rails.
53+
command -v gh >/dev/null 2>&1 || die "gh CLI not found. Install from https://cli.github.com/"
54+
command -v git >/dev/null 2>&1 || die "git not found."
55+
command -v sha256sum >/dev/null 2>&1 || command -v shasum >/dev/null 2>&1 \
56+
|| die "sha256sum / shasum not found."
57+
58+
# Extract the version from the tag (strip leading v). We use it to locate
59+
# files by exact name, so a tag like v1.0.0 must match AgentPack-Setup-1.0.0.exe.
60+
VER="${TAG#v}"
61+
EXE="$DIST_DIR/AgentPack-Setup-${VER}.exe"
62+
PKG="$DIST_DIR/AgentPack-${VER}.pkg"
63+
64+
# Collect whatever's actually built. The .exe is required — releasing
65+
# without the Windows installer would mean the README links 404 on Windows.
66+
FILES=()
67+
[ -f "$EXE" ] || die "Missing $EXE. Build it first (see README.md → Building from Source → Windows)."
68+
FILES+=("$EXE")
69+
ok "Found $(basename "$EXE")"
70+
71+
if [ -f "$PKG" ]; then
72+
FILES+=("$PKG")
73+
ok "Found $(basename "$PKG")"
74+
else
75+
echo "[!] Missing $PKG — publishing without the macOS installer." >&2
76+
echo " Build it on a Mac with: cd macos && ./build-pkg.sh" >&2
77+
echo " Then: gh release upload $TAG '$PKG' --clobber" >&2
78+
fi
79+
80+
# Generate checksums for everything we're about to upload.
81+
CHECKSUMS="$DIST_DIR/SHA256SUMS"
82+
note "Writing $CHECKSUMS"
83+
(
84+
cd "$DIST_DIR"
85+
if command -v sha256sum >/dev/null 2>&1; then
86+
sha256sum "${FILES[@]##*/}" > SHA256SUMS
87+
else
88+
# macOS native shasum — output format matches sha256sum.
89+
shasum -a 256 "${FILES[@]##*/}" > SHA256SUMS
90+
fi
91+
)
92+
FILES+=("$CHECKSUMS")
93+
94+
# Tag handling: if the tag doesn't exist locally, create it on HEAD; then
95+
# make sure origin has it (release creation needs the tag to be visible to
96+
# GitHub, not just local).
97+
if git rev-parse "$TAG" >/dev/null 2>&1; then
98+
ok "Local tag $TAG already exists"
99+
else
100+
note "Creating local tag $TAG on HEAD ($(git rev-parse --short HEAD))"
101+
git tag "$TAG"
102+
fi
103+
104+
if git ls-remote --tags origin "$TAG" | grep -q "$TAG"; then
105+
ok "Remote tag $TAG already pushed"
106+
else
107+
note "Pushing tag $TAG to origin"
108+
git push origin "$TAG"
109+
fi
110+
111+
# Default release notes come from the git log since the previous tag; if
112+
# the caller wants custom notes they pass --notes-file themselves.
113+
HAS_NOTES=0
114+
for f in "${GH_FLAGS[@]}"; do
115+
case "$f" in
116+
--notes|--notes-file|--generate-notes) HAS_NOTES=1 ;;
117+
esac
118+
done
119+
if [ "$HAS_NOTES" -eq 0 ]; then
120+
GH_FLAGS+=(--generate-notes)
121+
fi
122+
123+
note "Creating release $TAG with ${#FILES[@]} asset(s)"
124+
gh release create "$TAG" \
125+
--title "Agent Pack $TAG" \
126+
"${GH_FLAGS[@]}" \
127+
"${FILES[@]}"
128+
129+
ok "Release published: $(gh release view "$TAG" --json url -q .url)"

0 commit comments

Comments
 (0)