Skip to content

Commit 36ed36b

Browse files
chore: add GitHub release workflow and installer
1 parent b4a4d03 commit 36ed36b

File tree

3 files changed

+315
-0
lines changed

3 files changed

+315
-0
lines changed

.github/workflows/release.yml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
name: release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
build:
13+
name: build-${{ matrix.archive_os }}-${{ matrix.archive_arch }}
14+
runs-on: ${{ matrix.runner }}
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
include:
19+
- runner: ubuntu-latest
20+
archive_os: linux
21+
archive_arch: x86_64
22+
- runner: macos-13
23+
archive_os: darwin
24+
archive_arch: x86_64
25+
- runner: macos-14
26+
archive_os: darwin
27+
archive_arch: aarch64
28+
29+
steps:
30+
- name: Checkout
31+
uses: actions/checkout@v4
32+
33+
- name: Setup Rust
34+
uses: dtolnay/rust-toolchain@stable
35+
36+
- name: Build binaries
37+
run: cargo build --release -p claudeform
38+
39+
- name: Package artifacts
40+
shell: bash
41+
run: |
42+
set -euo pipefail
43+
asset="claudeform_${{ matrix.archive_os }}_${{ matrix.archive_arch }}.tar.gz"
44+
mkdir -p dist
45+
cp target/release/claudeform dist/claudeform
46+
cp target/release/cf dist/cf
47+
chmod +x dist/claudeform dist/cf
48+
tar -C dist -czf "$asset" claudeform cf
49+
if command -v sha256sum >/dev/null 2>&1; then
50+
sha256sum "$asset" > "${asset}.sha256"
51+
else
52+
shasum -a 256 "$asset" > "${asset}.sha256"
53+
fi
54+
55+
- name: Upload packaged artifacts
56+
uses: actions/upload-artifact@v4
57+
with:
58+
name: release-${{ matrix.archive_os }}-${{ matrix.archive_arch }}
59+
path: |
60+
claudeform_${{ matrix.archive_os }}_${{ matrix.archive_arch }}.tar.gz
61+
claudeform_${{ matrix.archive_os }}_${{ matrix.archive_arch }}.tar.gz.sha256
62+
if-no-files-found: error
63+
64+
publish:
65+
name: publish-release
66+
runs-on: ubuntu-latest
67+
needs: [build]
68+
69+
steps:
70+
- name: Download build artifacts
71+
uses: actions/download-artifact@v4
72+
with:
73+
path: dist
74+
75+
- name: Prepare release files
76+
shell: bash
77+
run: |
78+
set -euo pipefail
79+
find dist -type f -name "*.tar.gz" -exec cp {} dist/ \;
80+
find dist -type f -name "*.tar.gz.sha256" -print0 \
81+
| sort -z \
82+
| xargs -0 cat > dist/SHA256SUMS
83+
ls -lah dist
84+
85+
- name: Detect release type
86+
id: meta
87+
shell: bash
88+
run: |
89+
set -euo pipefail
90+
tag="${GITHUB_REF_NAME}"
91+
if [[ "${tag}" == *-* ]]; then
92+
echo "is_prerelease=true" >> "$GITHUB_OUTPUT"
93+
else
94+
echo "is_prerelease=false" >> "$GITHUB_OUTPUT"
95+
fi
96+
97+
- name: Publish GitHub release
98+
uses: softprops/action-gh-release@v2
99+
with:
100+
prerelease: ${{ steps.meta.outputs.is_prerelease }}
101+
files: |
102+
dist/*.tar.gz
103+
dist/SHA256SUMS

contrib/DEVELOPMENT.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,27 @@ codex login status
1414

1515
Expected for authenticated runs: a successful status output.
1616

17+
## Install From Releases (No Source Build)
18+
19+
Install latest stable release:
20+
21+
```bash
22+
curl -fsSL https://raw.githubusercontent.com/peterschmidt85/claudeform/main/install.sh | sh
23+
```
24+
25+
Install a pinned version (including pre-release tags):
26+
27+
```bash
28+
CLAUDEFORM_VERSION=v0.1.0 curl -fsSL https://raw.githubusercontent.com/peterschmidt85/claudeform/main/install.sh | sh
29+
CLAUDEFORM_VERSION=v0.2.0-rc.1 curl -fsSL https://raw.githubusercontent.com/peterschmidt85/claudeform/main/install.sh | sh
30+
```
31+
32+
Notes:
33+
34+
- default install target is `~/.local/bin`
35+
- installer verifies `SHA256SUMS` from GitHub Release artifacts
36+
- `latest` resolves only latest stable GitHub release (pre-releases are opt-in via explicit version pinning)
37+
1738
## Build
1839

1940
From repository root:
@@ -135,6 +156,23 @@ Notes:
135156
- They may consume API credits and run slower/flakier than mock tests.
136157
- Keep them opt-in locally and in CI.
137158

159+
## Release Automation
160+
161+
GitHub release workflow:
162+
163+
- file: `.github/workflows/release.yml`
164+
- trigger: push tag matching `v*`
165+
- outputs:
166+
- `claudeform_linux_x86_64.tar.gz`
167+
- `claudeform_darwin_x86_64.tar.gz`
168+
- `claudeform_darwin_aarch64.tar.gz`
169+
- `SHA256SUMS`
170+
171+
Release type:
172+
173+
- tags without `-` become stable releases (for example `v0.3.0`)
174+
- tags with `-` become pre-releases (for example `v0.3.0-rc.1`)
175+
138176
## Troubleshooting
139177

140178
1. `rustc: command not found`

install.sh

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#!/bin/sh
2+
set -eu
3+
4+
REPO="${CLAUDEFORM_REPO:-peterschmidt85/claudeform}"
5+
INSTALL_DIR="${CLAUDEFORM_INSTALL_DIR:-$HOME/.local/bin}"
6+
VERSION="${CLAUDEFORM_VERSION:-latest}"
7+
8+
usage() {
9+
cat <<'EOF'
10+
Claudeform installer
11+
12+
Usage:
13+
sh install.sh [--version <tag>] [--install-dir <path>] [--repo <owner/name>]
14+
15+
Environment overrides:
16+
CLAUDEFORM_VERSION Tag to install (example: v0.1.0, v0.2.0-rc.1)
17+
CLAUDEFORM_INSTALL_DIR Destination directory (default: ~/.local/bin)
18+
CLAUDEFORM_REPO GitHub repo (default: peterschmidt85/claudeform)
19+
20+
Examples:
21+
curl -fsSL https://raw.githubusercontent.com/peterschmidt85/claudeform/main/install.sh | sh
22+
CLAUDEFORM_VERSION=v0.2.0-rc.1 curl -fsSL https://raw.githubusercontent.com/peterschmidt85/claudeform/main/install.sh | sh
23+
EOF
24+
}
25+
26+
while [ "$#" -gt 0 ]; do
27+
case "$1" in
28+
--version)
29+
[ "$#" -ge 2 ] || { echo "error: --version requires a value" >&2; exit 1; }
30+
VERSION="$2"
31+
shift 2
32+
;;
33+
--version=*)
34+
VERSION="${1#*=}"
35+
shift
36+
;;
37+
--install-dir)
38+
[ "$#" -ge 2 ] || { echo "error: --install-dir requires a value" >&2; exit 1; }
39+
INSTALL_DIR="$2"
40+
shift 2
41+
;;
42+
--install-dir=*)
43+
INSTALL_DIR="${1#*=}"
44+
shift
45+
;;
46+
--repo)
47+
[ "$#" -ge 2 ] || { echo "error: --repo requires a value" >&2; exit 1; }
48+
REPO="$2"
49+
shift 2
50+
;;
51+
--repo=*)
52+
REPO="${1#*=}"
53+
shift
54+
;;
55+
-h|--help)
56+
usage
57+
exit 0
58+
;;
59+
*)
60+
echo "error: unknown argument: $1" >&2
61+
usage >&2
62+
exit 1
63+
;;
64+
esac
65+
done
66+
67+
detect_os() {
68+
case "$(uname -s)" in
69+
Linux) echo "linux" ;;
70+
Darwin) echo "darwin" ;;
71+
*)
72+
echo "error: unsupported OS: $(uname -s)" >&2
73+
exit 1
74+
;;
75+
esac
76+
}
77+
78+
detect_arch() {
79+
case "$(uname -m)" in
80+
x86_64|amd64) echo "x86_64" ;;
81+
arm64|aarch64) echo "aarch64" ;;
82+
*)
83+
echo "error: unsupported architecture: $(uname -m)" >&2
84+
exit 1
85+
;;
86+
esac
87+
}
88+
89+
sha256_file() {
90+
file="$1"
91+
if command -v shasum >/dev/null 2>&1; then
92+
shasum -a 256 "$file" | awk '{print $1}'
93+
return
94+
fi
95+
if command -v sha256sum >/dev/null 2>&1; then
96+
sha256sum "$file" | awk '{print $1}'
97+
return
98+
fi
99+
if command -v openssl >/dev/null 2>&1; then
100+
openssl dgst -sha256 "$file" | awk '{print $NF}'
101+
return
102+
fi
103+
echo "error: no SHA-256 tool found (need shasum, sha256sum, or openssl)" >&2
104+
exit 1
105+
}
106+
107+
fetch_latest_stable_tag() {
108+
api_url="https://api.github.com/repos/${REPO}/releases/latest"
109+
tag="$(curl -fsSL "$api_url" | sed -n 's/.*"tag_name":[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)"
110+
if [ -z "$tag" ]; then
111+
echo "error: could not resolve latest stable release tag from $api_url" >&2
112+
exit 1
113+
fi
114+
echo "$tag"
115+
}
116+
117+
normalize_tag() {
118+
raw="$1"
119+
case "$raw" in
120+
v*) echo "$raw" ;;
121+
*) echo "v$raw" ;;
122+
esac
123+
}
124+
125+
os="$(detect_os)"
126+
arch="$(detect_arch)"
127+
128+
if [ "$VERSION" = "latest" ]; then
129+
tag="$(fetch_latest_stable_tag)"
130+
else
131+
tag="$(normalize_tag "$VERSION")"
132+
fi
133+
134+
asset="claudeform_${os}_${arch}.tar.gz"
135+
base_url="https://github.com/${REPO}/releases/download/${tag}"
136+
asset_url="${base_url}/${asset}"
137+
checksums_url="${base_url}/SHA256SUMS"
138+
139+
tmp_dir="$(mktemp -d)"
140+
trap 'rm -rf "$tmp_dir"' EXIT INT TERM
141+
142+
echo "Installing Claudeform ${tag} from ${REPO}"
143+
echo "Detected target: ${os}/${arch}"
144+
echo "Downloading: ${asset}"
145+
146+
curl -fL "$asset_url" -o "${tmp_dir}/${asset}"
147+
curl -fL "$checksums_url" -o "${tmp_dir}/SHA256SUMS"
148+
149+
expected="$(awk -v n="$asset" '$2 == n {print $1}' "${tmp_dir}/SHA256SUMS" | head -n 1)"
150+
if [ -z "$expected" ]; then
151+
echo "error: checksum for ${asset} not found in SHA256SUMS" >&2
152+
exit 1
153+
fi
154+
155+
actual="$(sha256_file "${tmp_dir}/${asset}")"
156+
if [ "$expected" != "$actual" ]; then
157+
echo "error: checksum mismatch for ${asset}" >&2
158+
echo "expected: $expected" >&2
159+
echo "actual: $actual" >&2
160+
exit 1
161+
fi
162+
163+
mkdir -p "$INSTALL_DIR"
164+
tar -xzf "${tmp_dir}/${asset}" -C "$tmp_dir"
165+
166+
install -m 0755 "${tmp_dir}/claudeform" "${INSTALL_DIR}/claudeform"
167+
install -m 0755 "${tmp_dir}/cf" "${INSTALL_DIR}/cf"
168+
169+
echo "Installed:"
170+
echo " ${INSTALL_DIR}/claudeform"
171+
echo " ${INSTALL_DIR}/cf"
172+
echo
173+
echo "If needed, add to PATH:"
174+
echo " export PATH=\"${INSTALL_DIR}:\$PATH\""

0 commit comments

Comments
 (0)