1919#
2020# Usage:
2121# ./bin/create_release <VERSION> [PR_NUMBER]
22+ # ./bin/create_release <VERSION> --notes "Release notes text"
23+ # ./bin/create_release <VERSION> --notes-file path/to/notes.md
2224#
2325# Examples:
24- # ./bin/create_release 0.5.0 4417 # Use PR #4417's description
25- # ./bin/create_release 0.5.0 # Auto-find merged PR for this version
26+ # ./bin/create_release 0.5.0 4417 # Use PR #4417's description
27+ # ./bin/create_release 0.5.0 # Auto-find merged PR for this version
28+ # ./bin/create_release 0.5.0rc1 --notes "RC1 for 0.5.0" # Release candidate, no PR
29+ # ./bin/create_release 0.5.0rc1 --notes-file rc-notes.md
2630#
2731# This script will:
2832# 1. Verify you're on main branch with latest changes
29- # 2. Fetch the release PR description from GitHub
33+ # 2. Fetch release notes ( from PR or --notes/--notes-file)
3034# 3. Create an annotated git tag (py/v<VERSION>)
3135# 4. Push the tag to origin
32- # 5. Create a GitHub release using the PR description
36+ # 5. Create a GitHub release
3337
3438set -euo pipefail
3539
@@ -40,17 +44,78 @@ YELLOW='\033[1;33m'
4044BLUE=' \033[0;34m'
4145NC=' \033[0m' # No Color
4246
43- # Check arguments
44- if [ $# -lt 1 ]; then
47+ # Parse arguments
48+ VERSION=" "
49+ PR_NUMBER=" "
50+ NOTES_STRING=" "
51+ NOTES_FILE=" "
52+
53+ while [[ $# -gt 0 ]]; do
54+ case " $1 " in
55+ --notes)
56+ if [[ -z " ${2:- } " || " $2 " == -* ]]; then
57+ echo -e " ${RED} Error: --notes requires a value.${NC} "
58+ exit 1
59+ fi
60+ NOTES_STRING=" $2 "
61+ shift 2
62+ ;;
63+ --notes-file)
64+ if [[ -z " ${2:- } " || " $2 " == -* ]]; then
65+ echo -e " ${RED} Error: --notes-file requires a value.${NC} "
66+ exit 1
67+ fi
68+ NOTES_FILE=" $2 "
69+ shift 2
70+ ;;
71+ -h|--help)
72+ echo " Usage: $0 <VERSION> [PR_NUMBER]"
73+ echo " $0 <VERSION> --notes \" ...\" "
74+ echo " $0 <VERSION> --notes-file path/to/notes.md"
75+ echo " "
76+ echo " Options:"
77+ echo " --notes TEXT Use this text as release notes (skips PR lookup)"
78+ echo " --notes-file F Use file contents as release notes (skips PR lookup)"
79+ exit 0
80+ ;;
81+ * )
82+ if [ -z " $VERSION " ]; then
83+ VERSION=" $1 "
84+ elif [ -z " $PR_NUMBER " ] && [[ " $1 " =~ ^[0-9]+$ ]]; then
85+ PR_NUMBER=" $1 "
86+ else
87+ echo -e " ${RED} Error: Unexpected argument: $1 ${NC} "
88+ exit 1
89+ fi
90+ shift
91+ ;;
92+ esac
93+ done
94+
95+ if [ -z " $VERSION " ]; then
4596 echo -e " ${RED} Usage: $0 <VERSION> [PR_NUMBER]${NC} "
46- echo " Examples:"
47- echo " $0 0.5.0 4417 # Use specific PR"
48- echo " $0 0.5.0 # Auto-find merged PR"
97+ echo " $0 <VERSION> --notes \" ...\" "
98+ echo " $0 <VERSION> --notes-file path/to/notes.md"
99+ exit 1
100+ fi
101+
102+ # Can't use both notes options
103+ if [ -n " $NOTES_STRING " ] && [ -n " $NOTES_FILE " ]; then
104+ echo -e " ${RED} Error: Use only one of --notes or --notes-file${NC} "
49105 exit 1
50106fi
51107
52- VERSION=" $1 "
53- PR_NUMBER=" ${2:- } "
108+ # If using notes-file, verify file exists
109+ if [ -n " $NOTES_FILE " ] && [ ! -f " $NOTES_FILE " ]; then
110+ echo -e " ${RED} Error: Notes file not found: ${NOTES_FILE}${NC} "
111+ exit 1
112+ fi
113+
114+ USE_PR_NOTES=true
115+ if [ -n " $NOTES_STRING " ] || [ -n " $NOTES_FILE " ]; then
116+ USE_PR_NOTES=false
117+ fi
118+
54119TAG_NAME=" py/v${VERSION} "
55120
56121echo -e " ${BLUE} === Genkit Python SDK Release: v${VERSION} ===${NC} "
@@ -110,61 +175,74 @@ echo -e "${YELLOW}Step 3: Pulling latest changes...${NC}"
110175git pull origin main
111176echo -e " ${GREEN} ✓ Pulled latest from origin/main${NC} "
112177
113- # Step 4: Find or verify PR
114- echo " "
115- echo -e " ${YELLOW} Step 4: Finding release PR...${NC} "
116- if [ -z " $PR_NUMBER " ]; then
117- # Auto-find the merged PR for this version
118- PR_NUMBER=$( gh pr list --repo firebase/genkit --state merged \
119- --search " Python SDK ${VERSION} in:title" \
120- --json number --limit 1 | jq -r ' .[0].number // empty' )
121-
178+ # Step 4 & 5: Get release notes (from PR or --notes/--notes-file)
179+ RELEASE_NOTES_FILE=$( mktemp)
180+ trap ' rm -f "$RELEASE_NOTES_FILE"' EXIT
181+
182+ if [ " $USE_PR_NOTES " = true ]; then
183+ echo " "
184+ echo -e " ${YELLOW} Step 4: Finding release PR...${NC} "
122185 if [ -z " $PR_NUMBER " ]; then
123- # Try searching by version in body
186+ # Auto-find the merged PR for this version
124187 PR_NUMBER=$( gh pr list --repo firebase/genkit --state merged \
125- --search " v ${VERSION} label:python " \
188+ --search " Python SDK ${VERSION} in:title " \
126189 --json number --limit 1 | jq -r ' .[0].number // empty' )
190+
191+ if [ -z " $PR_NUMBER " ]; then
192+ # Try searching by version in body
193+ PR_NUMBER=$( gh pr list --repo firebase/genkit --state merged \
194+ --search " v${VERSION} label:python" \
195+ --json number --limit 1 | jq -r ' .[0].number // empty' )
196+ fi
197+
198+ if [ -z " $PR_NUMBER " ]; then
199+ echo -e " ${RED} Error: Could not find merged PR for version ${VERSION}${NC} "
200+ echo " Specify PR number manually: $0 ${VERSION} <PR_NUMBER>"
201+ echo " Or use --notes or --notes-file for release candidates"
202+ exit 1
203+ fi
127204 fi
128-
129- if [ -z " $PR_NUMBER " ]; then
130- echo -e " ${RED} Error: Could not find merged PR for version ${VERSION}${NC} "
131- echo " Specify PR number manually: $0 ${VERSION} <PR_NUMBER>"
132- exit 1
133- fi
134- fi
135205
136- echo -e " ${GREEN} ✓ Found PR #${PR_NUMBER}${NC} "
206+ echo -e " ${GREEN} ✓ Found PR #${PR_NUMBER}${NC} "
137207
138- # Fetch PR description
139- echo " "
140- echo -e " ${YELLOW} Step 5: Fetching PR description...${NC} "
141- RELEASE_NOTES_FILE=$( mktemp)
142- gh pr view " $PR_NUMBER " --repo firebase/genkit --json body --jq ' .body' > " $RELEASE_NOTES_FILE "
208+ echo " "
209+ echo -e " ${YELLOW} Step 5: Fetching PR description...${NC} "
210+ gh pr view " $PR_NUMBER " --repo firebase/genkit --json body --jq ' .body' > " $RELEASE_NOTES_FILE "
143211
144- if [ ! -s " $RELEASE_NOTES_FILE " ]; then
145- echo -e " ${RED} Error: PR #${PR_NUMBER} has no description${NC} "
146- rm -f " $RELEASE_NOTES_FILE "
147- exit 1
148- fi
212+ if [ ! -s " $RELEASE_NOTES_FILE " ]; then
213+ echo -e " ${RED} Error: PR #${PR_NUMBER} has no description${NC} "
214+ exit 1
215+ fi
149216
150- LINE_COUNT=$( wc -l < " $RELEASE_NOTES_FILE " )
151- echo -e " ${GREEN} ✓ Fetched PR description (${LINE_COUNT} lines)${NC} "
217+ LINE_COUNT=$( wc -l < " $RELEASE_NOTES_FILE " )
218+ echo -e " ${GREEN} ✓ Fetched PR description (${LINE_COUNT} lines)${NC} "
219+ else
220+ echo " "
221+ echo -e " ${YELLOW} Step 4: Using provided release notes...${NC} "
222+ if [ -n " $NOTES_FILE " ]; then
223+ cp " $NOTES_FILE " " $RELEASE_NOTES_FILE "
224+ echo -e " ${GREEN} ✓ Using notes from ${NOTES_FILE}${NC} "
225+ else
226+ echo " $NOTES_STRING " > " $RELEASE_NOTES_FILE "
227+ echo -e " ${GREEN} ✓ Using inline notes${NC} "
228+ fi
229+ fi
152230
153231# Step 6: Check if tag already exists
154232echo " "
155233echo -e " ${YELLOW} Step 6: Checking if tag exists...${NC} "
156234if git tag -l " $TAG_NAME " | grep -q " $TAG_NAME " ; then
157235 echo -e " ${RED} Error: Tag ${TAG_NAME} already exists${NC} "
158236 echo " Delete it first if you need to recreate: git tag -d ${TAG_NAME} && git push origin :refs/tags/${TAG_NAME} "
159- rm -f " $RELEASE_NOTES_FILE "
160237 exit 1
161238fi
162239echo -e " ${GREEN} ✓ Tag ${TAG_NAME} does not exist${NC} "
163240
164241# Step 7: Create tag
165242echo " "
166243echo -e " ${YELLOW} Step 7: Creating annotated tag...${NC} "
167- git tag -a " $TAG_NAME " -m " Genkit Python SDK v${VERSION}
244+ if [ " $USE_PR_NOTES " = true ]; then
245+ TAG_MSG=" Genkit Python SDK v${VERSION}
168246
169247Release highlights:
170248- See py/CHANGELOG.md for full release notes
@@ -173,6 +251,17 @@ Release highlights:
173251Published packages:
174252- genkit (core)
175253- genkit-plugin-* (22 plugins)"
254+ else
255+ TAG_MSG=" Genkit Python SDK v${VERSION}
256+
257+ Release highlights:
258+ - See py/CHANGELOG.md for full release notes
259+
260+ Published packages:
261+ - genkit (core)
262+ - genkit-plugin-* (22 plugins)"
263+ fi
264+ git tag -a " $TAG_NAME " -m " $TAG_MSG "
176265
177266echo -e " ${GREEN} ✓ Created tag: ${TAG_NAME}${NC} "
178267
@@ -185,21 +274,22 @@ echo -e "${GREEN}✓ Pushed tag to origin${NC}"
185274# Step 9: Create GitHub release
186275echo " "
187276echo -e " ${YELLOW} Step 9: Creating GitHub release...${NC} "
188- gh release create " $TAG_NAME " \
189- --title " Genkit Python SDK v${VERSION} " \
190- --notes-file " $RELEASE_NOTES_FILE "
277+ GH_RELEASE_ARGS=(--title " Genkit Python SDK v${VERSION} " --notes-file " $RELEASE_NOTES_FILE " )
278+ if [[ " $VERSION " =~ rc[0-9]* $ ]] || [[ " $VERSION " =~ -r c\. [0-9]+$ ]]; then
279+ GH_RELEASE_ARGS+=(--prerelease)
280+ fi
281+ gh release create " $TAG_NAME " " ${GH_RELEASE_ARGS[@]} "
191282
192283echo -e " ${GREEN} ✓ Created GitHub release${NC} "
193284
194- # Cleanup
195- rm -f " $RELEASE_NOTES_FILE "
196-
197285# Summary
198286echo " "
199287echo -e " ${BLUE} === Release Created Successfully ===${NC} "
200288echo " "
201289echo " Tag: ${TAG_NAME} "
202- echo " Based on PR: #${PR_NUMBER} "
290+ if [ " $USE_PR_NOTES " = true ]; then
291+ echo " Based on PR: #${PR_NUMBER} "
292+ fi
203293echo " Release: https://github.com/firebase/genkit/releases/tag/${TAG_NAME} "
204294echo " "
205295echo -e " ${YELLOW} Next steps:${NC} "
0 commit comments