Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release-doctor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
release_doctor:
name: release doctor
runs-on: ubuntu-latest
if: github.repository == 'fw-ai-external/python-sdk' && (github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next')
if: github.repository == 'fw-ai-external/python-sdk' && (github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'autorelease/') || github.head_ref == 'next')

steps:
- uses: actions/checkout@v6
Expand Down
116 changes: 58 additions & 58 deletions .github/workflows/release-tag.yml
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
name: Release Tag

# Fires when a "release: X.Y.Z" commit lands on main (typically via
# squash-merge of an upstream-prepared release PR; subject has no "v"
# prefix, the tag does). Creates the matching git tag vX.Y.Z and a
# GitHub Release, which is what publish-pypi.yml listens for. The
# GitHub Release MUST be created with FW_AI_BOT_TOKEN (a PAT) rather
# than github.token, because Release events triggered by github.token
# do not cascade into other workflows — using github.token would
# silently break the publish chain. Idempotent — the tag/release won't
# be recreated if they already exist.
# Fires on every push to main. If pyproject.toml carries a version that has
# not yet been tagged, this workflow creates the matching git tag vX.Y.Z and a
# GitHub Release, which is what publish-pypi.yml listens for. pyproject.toml
# is the single source of truth for the released version — no commit-subject
# regex, no manifest, no workflow_dispatch override. The GitHub Release MUST
# be created with FW_AI_BOT_TOKEN (a PAT) rather than github.token, because
# Release events triggered by github.token do not cascade into other
# workflows. Idempotent — the tag/release won't be recreated if they already
# exist.

on:
push:
branches:
- main
workflow_dispatch:
inputs:
version:
description: Version to tag (e.g. 1.2.0-alpha.72); leave blank to read manifest
type: string
required: false

permissions:
contents: write
pull-requests: write

jobs:
tag:
Expand All @@ -32,62 +27,56 @@ jobs:
- uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.FW_AI_BOT_TOKEN || github.token }}
token: ${{ secrets.FW_AI_BOT_TOKEN }}

- name: Determine version
id: version
env:
DISPATCH_VERSION: ${{ github.event.inputs.version }}
run: |
set -euo pipefail

if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && -n "${DISPATCH_VERSION}" ]]; then
version="${DISPATCH_VERSION}"
elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
# Manual recovery dispatch with no version: read the manifest.
version="$(python3 -c 'import json; print(json.load(open(".release-please-manifest.json"))["."])')"
else
# Push to main: the commit subject is the source of truth.
# The optional " (#NNN)" suffix is what GitHub appends on
# squash-merge; capture only the version token.
subject="$(git log -1 --format=%s)"
if [[ ! "${subject}" =~ ^release:\ +([0-9][0-9A-Za-z._-]*)(\ +\(#[0-9]+\))?$ ]]; then
echo "Most recent commit is not a release commit; nothing to tag."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
version="${BASH_REMATCH[1]}"
fi
# Single source of truth: pyproject.toml [project] version on the
# merged commit. No commit-subject parsing, no manifest, no fallback.
version="$(python3 -c '
import sys
try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib
with open("pyproject.toml", "rb") as f:
print(tomllib.load(f)["project"]["version"])
')"

# Belt-and-suspenders: validate the version regardless of source
# (push subject, explicit dispatch input, or manifest fallback).
if [[ ! "${version}" =~ ^[0-9][0-9A-Za-z._-]*$ ]]; then
echo "::error::Invalid release version: ${version}"
echo "::error::Invalid version in pyproject.toml: ${version}"
exit 1
fi

tag="v${version}"
if git rev-parse -q --verify "refs/tags/${tag}" >/dev/null; then
echo "Tag ${tag} already exists; skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi

{
echo "skip=false"
echo "version=${version}"
echo "tag=${tag}"
echo "tag=v${version}"
} >> "$GITHUB_OUTPUT"

- name: Check if tag exists
id: tag
env:
TAG: ${{ steps.version.outputs.tag }}
run: |
set -euo pipefail
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
echo "Tag ${TAG} already exists."
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Require FW_AI_BOT_TOKEN
if: steps.version.outputs.skip != 'true'
env:
BOT_TOKEN: ${{ secrets.FW_AI_BOT_TOKEN }}
run: |
[[ -n "${BOT_TOKEN}" ]] || { echo "::error::FW_AI_BOT_TOKEN is required"; exit 1; }

- name: Create tag and GitHub Release
if: steps.version.outputs.skip != 'true'
if: steps.tag.outputs.exists == 'false'
env:
GH_TOKEN: ${{ secrets.FW_AI_BOT_TOKEN }}
VERSION: ${{ steps.version.outputs.version }}
Expand Down Expand Up @@ -121,22 +110,33 @@ jobs:
${prerelease_flag}

- name: Mark release PR as tagged
if: steps.version.outputs.skip != 'true'
# Runs unconditionally so that recovering from a partial prior failure
# (tag created, label flip failed) just needs a workflow rerun. The
# step is a no-op on commits that aren't release-PR merges (no PR
# found) and on PRs already flipped to 'tagged' (no pending label).
# Loud failure only when this run just created the tag but no PR is
# associated — that indicates a misconfigured release commit.
env:
GH_TOKEN: ${{ secrets.FW_AI_BOT_TOKEN }}
TAG_JUST_CREATED: ${{ steps.tag.outputs.exists == 'false' }}
run: |
set -euo pipefail
# Find the PR that was squash-merged to produce this release commit
# and flip the autorelease labels: pending -> tagged.
# Failure here is informational only — the tag and GitHub Release
# are already created and the next workflow listening on Release
# has fired.
pr_number="$(gh api "repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls" \
--jq '.[0].number // empty' 2>/dev/null || true)"
--jq '.[0].number // empty')"
if [[ -z "${pr_number}" ]]; then
echo "No PR association for ${GITHUB_SHA}; skipping label update."
if [[ "${TAG_JUST_CREATED}" == "true" ]]; then
echo "::error::Tag was created this run but no PR is associated with ${GITHUB_SHA}."
exit 1
fi
echo "No PR associated with ${GITHUB_SHA}; nothing to flip."
exit 0
fi
labels="$(gh pr view "${pr_number}" --repo "${GITHUB_REPOSITORY}" \
--json labels --jq '[.labels[].name] | join(",")')"
if [[ ",${labels}," != *",autorelease: pending,"* ]]; then
echo "PR #${pr_number} does not carry 'autorelease: pending'; nothing to flip."
exit 0
fi
gh pr edit "${pr_number}" --repo "${GITHUB_REPOSITORY}" \
--remove-label "autorelease: pending" \
--add-label "autorelease: tagged" || true
--add-label "autorelease: tagged"
3 changes: 0 additions & 3 deletions .release-please-manifest.json

This file was deleted.

66 changes: 0 additions & 66 deletions release-please-config.json

This file was deleted.