Skip to content

Commit db55ecc

Browse files
committed
update release calendar logic (selfxyz#1256)
1 parent 756a850 commit db55ecc

File tree

1 file changed

+87
-29
lines changed

1 file changed

+87
-29
lines changed

.github/workflows/release-calendar.yml

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
11
name: Release Calendar
22

3-
# Goal: automatically create release pull requests when the clock hits 17:00 UTC (5:00 PM).
4-
# For reference, 17:00 UTC corresponds to:
5-
# • 10:30 PM IST (India)
6-
# • 10:00 AM PT (San Francisco)
7-
# • 6:00 PM CET / 7:00 PM CEST (Paris)
8-
# • 12:00 PM EST / 1:00 PM EDT (New York)
9-
# • 6:00 PM CET / 7:00 PM CEST (Warsaw)
10-
# Adjust locally if daylight saving time is in effect.
3+
# Creates release PRs on a schedule or manually via workflow_dispatch.
114
#
12-
# Testing: This workflow automatically runs when merged to dev (when the workflow file itself
13-
# is modified), allowing you to test changes immediately. You can also manually trigger it via
14-
# workflow_dispatch and choose which job(s) to run (staging or production).
5+
# HOW IT WORKS:
6+
# 1. Creates snapshot branches (release/staging-YYYY-MM-DD or release/production-YYYY-MM-DD)
7+
# 2. Opens PRs from these branches (NOT directly from dev/staging)
8+
# 3. New commits to dev/staging won't auto-update the PR - you control what's released
9+
#
10+
# SCHEDULE:
11+
# • Friday 17:00 UTC (10am PT): Creates release/staging-* branch from dev → staging PR
12+
# • Sunday 17:00 UTC (10am PT): Creates release/production-* branch from staging → main PR
13+
#
14+
# MANUAL TRIGGER:
15+
# Run via workflow_dispatch from main branch:
16+
# - staging: Creates dev snapshot → staging PR
17+
# - production: Creates staging snapshot → main PR
18+
#
19+
# REQUIREMENTS:
20+
# • Scheduled cron only runs when this file exists on the default branch (main)
21+
# • Manual triggers work from any branch, but should run from main for consistency
1522

1623
on:
1724
workflow_dispatch:
1825
inputs:
1926
job_to_run:
20-
description: "Which job to run (staging or production)"
27+
description: "Which job to run (staging: dev→staging, production: staging→main)"
2128
required: false
2229
type: choice
2330
options:
@@ -101,10 +108,12 @@ jobs:
101108
run: |
102109
set -euo pipefail
103110
PR_DATE=$(date +%Y-%m-%d)
111+
BRANCH_NAME="release/staging-${PR_DATE}"
104112
echo "date=${PR_DATE}" >> "$GITHUB_OUTPUT"
113+
echo "branch_name=${BRANCH_NAME}" >> "$GITHUB_OUTPUT"
105114
106-
echo "Checking for existing pull requests from dev to staging..."
107-
EXISTING_PR=$(gh pr list --base staging --head dev --state open --limit 1 --json number --jq '.[0].number // ""')
115+
echo "Checking for existing pull requests from ${BRANCH_NAME} to staging..."
116+
EXISTING_PR=$(gh pr list --base staging --head "${BRANCH_NAME}" --state open --limit 1 --json number --jq '.[0].number // ""')
108117
echo "existing_pr=${EXISTING_PR}" >> "$GITHUB_OUTPUT"
109118
110119
if [ -n "$EXISTING_PR" ]; then
@@ -135,26 +144,51 @@ jobs:
135144
fi
136145
done
137146
147+
- name: Create release branch from dev
148+
if: ${{ steps.guard_schedule.outputs.continue == 'true' && steps.check_dev_staging.outputs.existing_pr == '' }}
149+
env:
150+
BRANCH_NAME: ${{ steps.check_dev_staging.outputs.branch_name }}
151+
shell: bash
152+
run: |
153+
set -euo pipefail
154+
155+
echo "Creating release branch ${BRANCH_NAME} from dev"
156+
git fetch origin dev
157+
git checkout -b "${BRANCH_NAME}" origin/dev
158+
git push origin "${BRANCH_NAME}"
159+
138160
- name: Create dev to staging release PR
139161
if: ${{ steps.guard_schedule.outputs.continue == 'true' && steps.check_dev_staging.outputs.existing_pr == '' }}
140162
env:
141163
GH_TOKEN: ${{ github.token }}
142164
PR_DATE: ${{ steps.check_dev_staging.outputs.date }}
165+
BRANCH_NAME: ${{ steps.check_dev_staging.outputs.branch_name }}
143166
shell: bash
144167
run: |
145168
set -euo pipefail
146169
147170
python <<'PY'
171+
import os
148172
import pathlib
149173
import textwrap
174+
from datetime import datetime
175+
176+
pr_date = os.environ["PR_DATE"]
177+
branch_name = os.environ["BRANCH_NAME"]
178+
formatted_date = datetime.strptime(pr_date, "%Y-%m-%d").strftime("%B %d, %Y")
150179
151-
pathlib.Path("pr_body.md").write_text(textwrap.dedent("""\
180+
pathlib.Path("pr_body.md").write_text(textwrap.dedent(f"""\
152181
## 🚀 Weekly Release to Staging
153182
154-
This automated PR promotes all changes from `dev` to `staging` for testing.
183+
**Release Date:** {formatted_date}
184+
**Release Branch:** `{branch_name}`
185+
186+
This automated PR promotes a snapshot of `dev` to `staging` for testing.
155187
156188
### What's Included
157-
All commits merged to `dev` since the last staging release.
189+
All commits merged to `dev` up to the branch creation time.
190+
191+
**Note:** This PR uses a dedicated release branch, so new commits to `dev` will NOT automatically appear here.
158192
159193
### Review Checklist
160194
- [ ] All CI checks pass
@@ -166,16 +200,16 @@ jobs:
166200
After merging, the staging environment will be updated. A production release PR will be created on Sunday.
167201
168202
---
169-
*This PR was automatically created by the Release Calendar workflow*
203+
*This PR was automatically created by the Release Calendar workflow on {formatted_date}*
170204
"""))
171205
PY
172206
173207
TITLE="Release to Staging - ${PR_DATE}"
174-
echo "Creating PR with title: ${TITLE}"
208+
echo "Creating PR with title: ${TITLE} from branch ${BRANCH_NAME}"
175209
176210
gh pr create \
177211
--base staging \
178-
--head dev \
212+
--head "${BRANCH_NAME}" \
179213
--title "${TITLE}" \
180214
--label release \
181215
--label automated \
@@ -237,6 +271,11 @@ jobs:
237271
run: |
238272
set -euo pipefail
239273
274+
PR_DATE=$(date +%Y-%m-%d)
275+
BRANCH_NAME="release/production-${PR_DATE}"
276+
echo "date=${PR_DATE}" >> "$GITHUB_OUTPUT"
277+
echo "branch_name=${BRANCH_NAME}" >> "$GITHUB_OUTPUT"
278+
240279
echo "Fetching latest branches..."
241280
git fetch origin main staging
242281
@@ -251,8 +290,8 @@ jobs:
251290
252291
echo "staging_not_ahead=false" >> "$GITHUB_OUTPUT"
253292
254-
echo "Checking for existing pull requests from staging to main..."
255-
EXISTING_PR=$(gh pr list --base main --head staging --state open --limit 1 --json number --jq '.[0].number // ""')
293+
echo "Checking for existing pull requests from ${BRANCH_NAME} to main..."
294+
EXISTING_PR=$(gh pr list --base main --head "${BRANCH_NAME}" --state open --limit 1 --json number --jq '.[0].number // ""')
256295
echo "existing_pr=${EXISTING_PR}" >> "$GITHUB_OUTPUT"
257296
258297
if [ -n "$EXISTING_PR" ]; then
@@ -261,9 +300,6 @@ jobs:
261300
echo "No existing production release PR found. Ready to create a new one."
262301
fi
263302
264-
PR_DATE=$(date +%Y-%m-%d)
265-
echo "date=${PR_DATE}" >> "$GITHUB_OUTPUT"
266-
267303
- name: Log staging up to date
268304
if: ${{ steps.guard_schedule.outputs.continue == 'true' && steps.production_status.outputs.staging_not_ahead == 'true' }}
269305
run: |
@@ -291,11 +327,25 @@ jobs:
291327
fi
292328
done
293329
330+
- name: Create release branch from staging
331+
if: ${{ steps.guard_schedule.outputs.continue == 'true' && steps.production_status.outputs.staging_not_ahead != 'true' && steps.production_status.outputs.existing_pr == '' }}
332+
env:
333+
BRANCH_NAME: ${{ steps.production_status.outputs.branch_name }}
334+
shell: bash
335+
run: |
336+
set -euo pipefail
337+
338+
echo "Creating release branch ${BRANCH_NAME} from staging"
339+
git fetch origin staging
340+
git checkout -b "${BRANCH_NAME}" origin/staging
341+
git push origin "${BRANCH_NAME}"
342+
294343
- name: Create staging to main release PR
295344
if: ${{ steps.guard_schedule.outputs.continue == 'true' && steps.production_status.outputs.staging_not_ahead != 'true' && steps.production_status.outputs.existing_pr == '' }}
296345
env:
297346
GH_TOKEN: ${{ github.token }}
298347
PR_DATE: ${{ steps.production_status.outputs.date }}
348+
BRANCH_NAME: ${{ steps.production_status.outputs.branch_name }}
299349
COMMITS_AHEAD: ${{ steps.production_status.outputs.commits }}
300350
shell: bash
301351
run: |
@@ -305,18 +355,26 @@ jobs:
305355
import os
306356
import pathlib
307357
import textwrap
358+
from datetime import datetime
308359
309360
commits_ahead = os.environ["COMMITS_AHEAD"]
361+
pr_date = os.environ["PR_DATE"]
362+
branch_name = os.environ["BRANCH_NAME"]
363+
formatted_date = datetime.strptime(pr_date, "%Y-%m-%d").strftime("%B %d, %Y")
310364
311365
pathlib.Path("pr_body.md").write_text(textwrap.dedent(f"""\
312366
## 🎯 Production Release
313367
368+
**Release Date:** {formatted_date}
369+
**Release Branch:** `{branch_name}`
370+
**Commits ahead**: {commits_ahead}
371+
314372
This automated PR promotes tested changes from `staging` to `main` for production deployment.
315373
316374
### What's Included
317375
All changes that have been verified in the staging environment.
318376
319-
**Commits ahead**: {commits_ahead}
377+
**Note:** This PR uses a dedicated release branch, so new commits to `staging` will NOT automatically appear here.
320378
321379
### Pre-Deployment Checklist
322380
- [ ] All staging tests passed
@@ -329,16 +387,16 @@ jobs:
329387
Merging this PR will trigger production deployment.
330388
331389
---
332-
*This PR was automatically created by the Release Calendar workflow*
390+
*This PR was automatically created by the Release Calendar workflow on {formatted_date}*
333391
"""))
334392
PY
335393
336394
TITLE="Release to Production - ${PR_DATE}"
337-
echo "Creating PR with title: ${TITLE} and ${COMMITS_AHEAD} commits ahead."
395+
echo "Creating PR with title: ${TITLE} from branch ${BRANCH_NAME} with ${COMMITS_AHEAD} commits ahead."
338396
339397
gh pr create \
340398
--base main \
341-
--head staging \
399+
--head "${BRANCH_NAME}" \
342400
--title "${TITLE}" \
343401
--label release \
344402
--label automated \

0 commit comments

Comments
 (0)