Skip to content

Commit 5d847b1

Browse files
committed
feat: introduce automated release script and gh actionjob
1 parent a5d2621 commit 5d847b1

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed

.github/workflows/build.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,3 +139,27 @@ jobs:
139139
docker push karrio/dashboard:${{ env.tag }}
140140
docker tag karrio/dashboard:${{ env.tag }} karrio/dashboard:latest-rc
141141
docker push karrio/dashboard:latest-rc
142+
143+
create-release:
144+
# Create a GitHub release after all builds complete successfully
145+
# This job is idempotent - it will skip if the release already exists
146+
needs: [python-packages, server-build, dashboard-build]
147+
runs-on: ubuntu-latest
148+
permissions:
149+
contents: write
150+
151+
steps:
152+
- uses: actions/checkout@v4
153+
154+
- id: get_tag
155+
run: |
156+
cat ./apps/api/karrio/server/VERSION
157+
echo "tag=$(cat ./apps/api/karrio/server/VERSION)" >> "$GITHUB_ENV"
158+
159+
- name: Create GitHub release
160+
env:
161+
GH_TOKEN: ${{ github.token }}
162+
MARK_LATEST: "1"
163+
run: |
164+
echo 'Creating GitHub release for version ${{ env.tag }}...'
165+
./bin/create-github-release ${{ env.tag }}

bin/create-github-release

Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
#!/usr/bin/env bash
2+
#
3+
# create-github-release - Idempotently create a GitHub release from CHANGELOG.md
4+
#
5+
# Usage:
6+
# ./bin/create-github-release [version]
7+
#
8+
# If no version is provided, it reads from apps/api/karrio/server/VERSION
9+
#
10+
# This script:
11+
# 1. Extracts release notes from CHANGELOG.md for the given version
12+
# 2. Determines the release title format (major vs patch)
13+
# 3. Creates the release idempotently (skips if it already exists)
14+
#
15+
# Environment variables:
16+
# DRY_RUN=1 - Print what would be done without creating the release
17+
# MARK_LATEST=1 - Mark the release as latest (default: auto-detect)
18+
# REPO=owner/repo - Override the repository (default: current repo)
19+
#
20+
21+
set -euo pipefail
22+
23+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
24+
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
25+
26+
# Colors for output
27+
RED='\033[0;31m'
28+
GREEN='\033[0;32m'
29+
YELLOW='\033[1;33m'
30+
NC='\033[0m' # No Color
31+
32+
log_info() {
33+
echo -e "${GREEN}[INFO]${NC} $1"
34+
}
35+
36+
log_warn() {
37+
echo -e "${YELLOW}[WARN]${NC} $1"
38+
}
39+
40+
log_error() {
41+
echo -e "${RED}[ERROR]${NC} $1"
42+
}
43+
44+
# Get version from argument or VERSION file
45+
get_version() {
46+
if [[ -n "${1:-}" ]]; then
47+
echo "$1"
48+
else
49+
cat "$ROOT_DIR/apps/api/karrio/server/VERSION"
50+
fi
51+
}
52+
53+
# Determine if version is a major release (X.Y) or patch release (X.Y.Z)
54+
is_major_release() {
55+
local version="$1"
56+
# Count the number of dots - if only one dot, it's a major release
57+
local dot_count
58+
dot_count=$(echo "$version" | tr -cd '.' | wc -c | tr -d ' ')
59+
[[ "$dot_count" -eq 1 ]]
60+
}
61+
62+
# Generate release title based on version type
63+
get_release_title() {
64+
local version="$1"
65+
if is_major_release "$version"; then
66+
echo "Karrio OSS $version"
67+
else
68+
echo "Karrio patch $version"
69+
fi
70+
}
71+
72+
# Extract release notes from CHANGELOG.md for a specific version
73+
extract_release_notes() {
74+
local version="$1"
75+
local changelog="$ROOT_DIR/CHANGELOG.md"
76+
77+
if [[ ! -f "$changelog" ]]; then
78+
log_error "CHANGELOG.md not found at $changelog"
79+
return 1
80+
fi
81+
82+
# Use awk to extract the section for this version
83+
# The section starts with "# Karrio $version" and ends at the next "---" or "# Karrio"
84+
# Also handles trimming leading/trailing blank lines
85+
awk -v version="$version" '
86+
BEGIN { found=0; printing=0 }
87+
/^# Karrio / {
88+
if (found) { exit }
89+
if ($0 ~ "# Karrio " version "$") {
90+
found=1
91+
printing=1
92+
next
93+
}
94+
}
95+
/^---$/ {
96+
if (printing) { exit }
97+
}
98+
printing { print }
99+
' "$changelog" | awk '
100+
# Remove leading and trailing blank lines, collapse multiple blank lines
101+
NF { blank=0; for(i=1;i<=n;i++) print saved[i]; n=0; print; next }
102+
{ saved[++n]=$0 }
103+
'
104+
}
105+
106+
# Check if release already exists
107+
release_exists() {
108+
local tag="$1"
109+
local repo="${REPO:-}"
110+
111+
local repo_flag=""
112+
if [[ -n "$repo" ]]; then
113+
repo_flag="--repo $repo"
114+
fi
115+
116+
# shellcheck disable=SC2086
117+
gh release view "$tag" $repo_flag >/dev/null 2>&1
118+
}
119+
120+
# Print release notes body
121+
print_release_notes() {
122+
local notes="$1"
123+
echo ""
124+
echo "Release notes:"
125+
echo "----------------------------------------"
126+
echo "$notes"
127+
echo "----------------------------------------"
128+
echo ""
129+
}
130+
131+
# Create the GitHub release
132+
create_release() {
133+
local version="$1"
134+
local tag="v$version"
135+
local title
136+
local notes
137+
local repo="${REPO:-}"
138+
local dry_run="${DRY_RUN:-}"
139+
local mark_latest="${MARK_LATEST:-}"
140+
141+
title=$(get_release_title "$version")
142+
notes=$(extract_release_notes "$version")
143+
144+
if [[ -z "$notes" ]]; then
145+
log_error "Could not extract release notes for version $version from CHANGELOG.md"
146+
log_error "Make sure CHANGELOG.md contains a section starting with '# Karrio $version'"
147+
return 1
148+
fi
149+
150+
log_info "Tag: $tag"
151+
log_info "Title: $title"
152+
print_release_notes "$notes"
153+
154+
# Check if release already exists
155+
if release_exists "$tag"; then
156+
log_info "Release $tag already exists, skipping creation"
157+
return 0
158+
fi
159+
160+
# Build the gh release create command
161+
local cmd="gh release create \"$tag\" --title \"$title\""
162+
163+
if [[ -n "$repo" ]]; then
164+
cmd="$cmd --repo \"$repo\""
165+
fi
166+
167+
# Auto-detect latest flag based on version comparison
168+
# By default, mark as latest only if explicitly requested
169+
if [[ "$mark_latest" == "1" ]]; then
170+
cmd="$cmd --latest"
171+
fi
172+
173+
if [[ "$dry_run" == "1" ]]; then
174+
log_info "[DRY RUN] Would create release with command:"
175+
echo " $cmd --notes-file <temp_file>"
176+
return 0
177+
fi
178+
179+
log_info "Creating GitHub release $tag..."
180+
181+
# Create a temporary file for the release notes
182+
local notes_file
183+
notes_file=$(mktemp)
184+
echo "$notes" > "$notes_file"
185+
186+
# Create the release
187+
local repo_flag=""
188+
if [[ -n "$repo" ]]; then
189+
repo_flag="--repo $repo"
190+
fi
191+
192+
local latest_flag=""
193+
if [[ "$mark_latest" == "1" ]]; then
194+
latest_flag="--latest"
195+
fi
196+
197+
# shellcheck disable=SC2086
198+
if gh release create "$tag" \
199+
--title "$title" \
200+
--notes-file "$notes_file" \
201+
$repo_flag \
202+
$latest_flag; then
203+
log_info "Successfully created release $tag"
204+
rm -f "$notes_file"
205+
return 0
206+
else
207+
log_error "Failed to create release $tag"
208+
rm -f "$notes_file"
209+
return 1
210+
fi
211+
}
212+
213+
# Main
214+
main() {
215+
local version
216+
version=$(get_version "${1:-}")
217+
218+
if [[ -z "$version" ]]; then
219+
log_error "Could not determine version"
220+
exit 1
221+
fi
222+
223+
log_info "Processing release for version: $version"
224+
225+
# Verify gh CLI is available
226+
if ! command -v gh &> /dev/null; then
227+
log_error "GitHub CLI (gh) is not installed or not in PATH"
228+
exit 1
229+
fi
230+
231+
# Verify gh is authenticated
232+
if ! gh auth status &> /dev/null; then
233+
log_error "GitHub CLI is not authenticated. Run 'gh auth login' first."
234+
exit 1
235+
fi
236+
237+
create_release "$version"
238+
}
239+
240+
main "$@"

0 commit comments

Comments
 (0)