Skip to content

Commit 8dd2dcf

Browse files
author
David Elner
committed
Added: JavaScript release workflow and shared actions
1 parent f362d6d commit 8dd2dcf

20 files changed

Lines changed: 1363 additions & 8 deletions

File tree

.github/workflows/release-py.yml

Lines changed: 212 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,57 @@
1-
# Placeholder so the release-py.yml workflow_dispatch trigger is registered on the
2-
# default branch. This lets the full workflow be dispatched from its feature branch
3-
# ("Use workflow from: <branch>") before merge. The functional workflow — which
4-
# publishes the bt-publishing-test dummy PyPI package end-to-end — replaces this
5-
# stub when the Python release PR lands.
1+
#
2+
# Releases bt-publishing-test — a dummy PyPI package used to validate Braintrust
3+
# release workflow tooling end-to-end. This does NOT release any real Braintrust
4+
# Python SDK.
5+
#
6+
# This workflow serves two purposes:
7+
# 1. End-to-end testing of the composite actions in actions/release/
8+
# 2. Reference implementation of the Python-specific release template
9+
#
10+
# Generic steps are provided by composite actions in actions/release/.
11+
# Only the sections marked with inline comments need to change for other packages.
12+
#
13+
# ─────────────────────────────────────────────────────────────────────────────
14+
# SETUP REQUIREMENTS
15+
# ─────────────────────────────────────────────────────────────────────────────
16+
#
17+
# GitHub Environments (repo Settings → Environments):
18+
# publish
19+
# - Required reviewers: sdk-eng team or named maintainers
20+
# - Prevent self-review: enabled
21+
# - Deployment branches: main only
22+
# publish-dry-run
23+
# - Required reviewers: at least yourself (to test the approval gate)
24+
# - Allow administrators to bypass: enabled (for testing flexibility)
25+
# - Deployment branches: no restriction
26+
#
27+
# Repository Variables (Settings → Secrets and variables → Variables):
28+
# SLACK_SDK_RELEASE_CHANNEL Slack channel ID for release notifications
29+
#
30+
# Repository Secrets (Settings → Secrets and variables → Secrets):
31+
# SLACK_BOT_TOKEN Org-level secret — Brainbot Slack app token
32+
# Must be invited to SLACK_SDK_RELEASE_CHANNEL
33+
#
34+
# PyPI Trusted Publishing (OIDC — no token needed):
35+
# Configure for your package at pypi.org → project → Settings → Publishing,
36+
# or pre-register a "pending publisher" before the first publish:
37+
# Owner: braintrustdata (or your org)
38+
# Repository: sdk-actions (or your SDK repo)
39+
# Workflow: release-py.yml (or your workflow filename)
40+
# Environment: publish
41+
# The publish job needs id-token: write for OIDC + PEP 740 attestations.
42+
#
43+
# ─────────────────────────────────────────────────────────────────────────────
44+
645
name: Release Python (bt-publishing-test)
746

847
on:
48+
# Auto dry-run on PRs that touch the generated actions or the workflows, to
49+
# smoke-test the actions end-to-end. (A template edited without regenerating
50+
# is caught separately by the CI check-generated job, which runs on every PR.)
51+
pull_request:
52+
paths:
53+
- 'actions/**'
54+
- '.github/workflows/**'
955
workflow_dispatch:
1056
inputs:
1157
_instructions:
@@ -21,17 +67,175 @@ on:
2167
description: "Anchor for release notes: a tag name or commit SHA. If SHA, notes will be empty."
2268
type: string
2369
required: false
70+
# bt-publishing-test: fixed SHA anchor prevents indefinite changelog accumulation.
71+
# Update this when you want to reset the notes baseline.
2472
default: '58a397193105516446c3042361913655bcece509'
2573
dry_run:
2674
description: "Dry run: Build without tagging or publishing"
2775
type: boolean
2876
default: false
2977

3078
jobs:
31-
placeholder:
79+
bump:
3280
runs-on: ubuntu-24.04
3381
timeout-minutes: 5
3482
permissions: {}
83+
outputs:
84+
version: ${{ steps.bump.outputs.version }}
85+
steps:
86+
# bt-publishing-test: derives next version by querying PyPI and incrementing the patch.
87+
# Falls back to 0.0.1 on first publish (project not found). Remove this job
88+
# in real SDK workflows; version comes from the version bump PR.
89+
- name: Bump version
90+
id: bump
91+
run: |
92+
# Project may not exist yet (first release / new name); default to 0.0.0
93+
# so the first publish is 0.0.1.
94+
CURRENT=$(curl -sf https://pypi.org/pypi/bt-publishing-test/json 2>/dev/null | jq -r '.info.version // empty' || true)
95+
CURRENT=${CURRENT:-0.0.0}
96+
IFS='.' read -r major minor patch <<< "$CURRENT"
97+
echo "version=$major.$minor.$((patch + 1))" >> $GITHUB_OUTPUT
98+
99+
validate:
100+
needs: bump
101+
runs-on: ubuntu-24.04
102+
timeout-minutes: 10
103+
permissions:
104+
contents: read
105+
outputs:
106+
release_tag: ${{ steps.validate-release.outputs.release_tag }}
107+
prev_release: ${{ steps.validate-release.outputs.prev_release }}
108+
branch: ${{ steps.validate-release.outputs.branch }}
109+
on_release_branch: ${{ steps.validate-release.outputs.on_release_branch }}
110+
commit_message: ${{ steps.validate-release.outputs.commit_message }}
35111
steps:
36-
- name: Placeholder
37-
run: echo "Placeholder — registers workflow_dispatch. The functional release-py.yml lands in the Python release PR."
112+
# bt-publishing-test: checkout required to resolve local ./actions/... references.
113+
# Real SDK workflows use external braintrustdata/sdk-actions/...@sha references
114+
# and do not need this step.
115+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
116+
117+
- name: Validate release
118+
id: validate-release
119+
uses: ./actions/release/lang/py/validate
120+
with:
121+
# bt-publishing-test: version passed directly from bump job — no version_file read needed.
122+
# In a real Python project, drop `version`; it is read from version_file.
123+
version: ${{ needs.bump.outputs.version }}
124+
sha: ${{ inputs.sha || github.event.pull_request.head.sha }}
125+
dry_run: ${{ inputs.dry_run || github.event_name == 'pull_request' }}
126+
working_directory: test/release/py
127+
python_version: test/release/py/.python-version # exercises version-file sourcing (vs explicit version)
128+
package_name: bt-publishing-test # exercises the PyPI-availability fail-fast check
129+
130+
prepare:
131+
needs: validate
132+
runs-on: ubuntu-24.04
133+
timeout-minutes: 5
134+
permissions:
135+
contents: write # required for releases/generate-notes API
136+
outputs:
137+
pr_list: ${{ steps.prepare.outputs.pr_list }}
138+
notes: ${{ steps.prepare.outputs.notes }}
139+
steps:
140+
# bt-publishing-test: checkout required because actions are referenced locally (./actions/...).
141+
# Real SDK workflows reference actions externally (braintrustdata/sdk-actions/...@sha)
142+
# and do not need this step.
143+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
144+
145+
- name: Prepare release
146+
id: prepare
147+
uses: ./actions/release/prepare
148+
with:
149+
release_tag: ${{ needs.validate.outputs.release_tag }}
150+
sha: ${{ inputs.sha || github.event.pull_request.head.sha }}
151+
# On PR auto dry-runs, anchor notes to the head SHA: prepare treats a
152+
# full SHA as "notes unavailable" (no tag range), so the changelog
153+
# doesn't accumulate in the step summary on every PR.
154+
prev_release: ${{ inputs.prev_release || github.event.pull_request.head.sha || needs.validate.outputs.prev_release }}
155+
156+
notify-pending:
157+
needs: [validate, prepare]
158+
runs-on: ubuntu-24.04
159+
timeout-minutes: 5
160+
permissions: {}
161+
steps:
162+
# bt-publishing-test: checkout required for local action resolution (see prepare job comment)
163+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
164+
165+
- name: Notify release pending
166+
uses: ./actions/release/notify-pending
167+
with:
168+
sha: ${{ inputs.sha || github.event.pull_request.head.sha }}
169+
release_tag: ${{ needs.validate.outputs.release_tag }}
170+
prev_release: ${{ needs.validate.outputs.prev_release }}
171+
branch: ${{ needs.validate.outputs.branch }}
172+
on_release_branch: ${{ needs.validate.outputs.on_release_branch }}
173+
commit_message: ${{ needs.validate.outputs.commit_message }}
174+
pr_list: ${{ needs.prepare.outputs.pr_list }}
175+
notes: ${{ needs.prepare.outputs.notes }}
176+
dry_run: ${{ inputs.dry_run || github.event_name == 'pull_request' }}
177+
# Suppress Slack on PR auto dry-runs (still exercises message-building; just no real post).
178+
# Gate on != pull_request: GHA's `A && '' || B` returns B (the '' is falsy), so suppression
179+
# must be the trailing '' branch, not the "true" value.
180+
slack_token: ${{ github.event_name != 'pull_request' && secrets.SLACK_BOT_TOKEN || '' }}
181+
slack_channel: ${{ github.event_name != 'pull_request' && vars.SLACK_SDK_RELEASE_CHANNEL || '' }}
182+
# slack_mention: '@sdk-eng'
183+
label: 'bt-publishing-test' # distinguishes this from other packages in this repo
184+
emoji: ':python:'
185+
186+
publish:
187+
needs: [bump, validate, prepare, notify-pending]
188+
runs-on: ubuntu-24.04
189+
timeout-minutes: 15
190+
# No environment on PR dry-runs (avoids the approval gate AND the accumulating
191+
# deployment records); the gated environments apply only to manual dispatch.
192+
# Note the structure: empty must be the trailing `|| ''` branch, since GHA's
193+
# `cond && '' || X` would fall through to X (the '' is falsy).
194+
environment: >-
195+
${{ github.event_name != 'pull_request'
196+
&& (inputs.dry_run && 'publish-dry-run' || 'publish')
197+
|| '' }}
198+
permissions:
199+
contents: write
200+
id-token: write # OIDC trusted publishing + PEP 740 attestations
201+
steps:
202+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
203+
with:
204+
ref: ${{ inputs.sha || github.event.pull_request.head.sha }}
205+
fetch-depth: 0
206+
207+
# bt-publishing-test: write the run-number version into version.py. The publish job
208+
# checks out fresh, so the file still reflects the placeholder version.
209+
# Real SDK workflows omit this — the version is committed in the bump PR.
210+
- name: Apply version to version.py
211+
env:
212+
VERSION: ${{ needs.bump.outputs.version }}
213+
run: |
214+
printf 'VERSION = "%s"\n' "$VERSION" > test/release/py/src/bt_publishing_test/version.py
215+
216+
- name: Publish
217+
uses: ./actions/release/lang/py/publish
218+
with:
219+
sha: ${{ inputs.sha || github.event.pull_request.head.sha }}
220+
checkout: 'false' # bt-publishing-test: skip internal checkout — version patching above must survive into the build
221+
working_directory: test/release/py
222+
python_version: test/release/py/.python-version
223+
dry_run: ${{ inputs.dry_run || github.event_name == 'pull_request' }}
224+
release_tag: ${{ needs.validate.outputs.release_tag }}
225+
github_release: 'false' # bt-publishing-test: omit GitHub release to avoid tag/release pollution from repeated test runs
226+
version: ${{ needs.bump.outputs.version }}
227+
package_name: bt-publishing-test
228+
label: 'bt-publishing-test' # distinguishes this from other packages in this repo
229+
notes: ${{ needs.prepare.outputs.notes }}
230+
prev_release: ${{ needs.validate.outputs.prev_release }}
231+
branch: ${{ needs.validate.outputs.branch }}
232+
on_release_branch: ${{ needs.validate.outputs.on_release_branch }}
233+
pr_list: ${{ needs.prepare.outputs.pr_list }}
234+
# Suppress Slack on PR auto dry-runs (still exercises message-building; just no real post).
235+
# Gate on != pull_request: GHA's `A && '' || B` returns B (the '' is falsy), so suppression
236+
# must be the trailing '' branch, not the "true" value.
237+
slack_token: ${{ github.event_name != 'pull_request' && secrets.SLACK_BOT_TOKEN || '' }}
238+
slack_channel: ${{ github.event_name != 'pull_request' && vars.SLACK_SDK_RELEASE_CHANNEL || '' }}
239+
# slack_mention: '@sdk-eng'
240+
# failure_slack_mention: '@sdk-eng'
241+
emoji: ':python:'

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ Shared GitHub actions and workflows for Braintrust SDK repositories.
1111
1. **Copy the canonical template** for your language into your repo's
1212
`.github/workflows/`:
1313
- [`release-js.yml`](.github/workflows/release-js.yml)
14+
- [`release-py.yml`](.github/workflows/release-py.yml)
1415
- [`release-ruby.yml`](.github/workflows/release-ruby.yml)
1516
2. **Adapt the marked sections** — version source, gem name, working directory,
1617
and so on. The template's comments flag exactly what changes per repo;
@@ -35,6 +36,8 @@ pulls in everything it needs.
3536
| `release/lang/ruby/validate` | Check out the SHA, set up Ruby, read the version, validate the release (tag/branch/metadata), lint + build |
3637
| `release/lang/js/publish` | Publish the package to npm (OIDC trusted publishing), create the GitHub release, and notify |
3738
| `release/lang/js/validate` | Check out the SHA, set up Node + package manager, read the version, validate the release (channel/tag/branch/metadata), build |
39+
| `release/lang/py/publish` | Publish the package to PyPI (OIDC trusted publishing + PEP 740 attestations), create the GitHub release, and notify |
40+
| `release/lang/py/validate` | Check out the SHA, set up uv + Python, read the version, validate the release (tag/branch/metadata, PyPI availability), build |
3841
| `release/notify-pending` | Post the pre-approval job summary and Slack notification |
3942
| `release/prepare` | Fetch the PR list and release notes |
4043

0 commit comments

Comments
 (0)