mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-09 02:50:58 +00:00
* feat(sdk-python): replace verbatim release notes inheritance with --generate-notes The previous implementation fetched the entire body of the previous GitHub release and appended it to the new release notes. Because each release body already contained the body of the one before it, this created a linear chain that grew with every stable release — eventually hitting GitHub's 125 KB release body limit. Replace the body-chaining approach with GitHub's built-in --generate-notes flag, which auto-generates a bounded, PR-based changelog scoped between two tags via --notes-start-tag. The SDK metadata header (package name + version) is preserved via --notes-file, which GitHub prepends above the auto-generated changelog. For the first-ever release (no previous SDK tag), --generate-notes is skipped to avoid pulling in unrelated non-SDK commits, falling back to a static "Initial release" message instead. Closes #3796 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(sdk-python): address review comments on release notes - Rename NOTES_START_TAG_FLAG → NOTES_START_TAG_ARG (contains key-value pair, not just a flag) - Fix misleading "Initial release" message — PREVIOUS_RELEASE_TAG is empty for all nightly/preview releases, not just the first release - Add comments explaining why old error handling is safe to remove 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(sdk-python): validate previous tag exists before using --notes-start-tag If a prior release published to PyPI but failed to create a GitHub release/tag, the tag won't exist in Git. Using --notes-start-tag with a nonexistent tag would cause gh release create to fail after PyPI publish, leaving a partial release state. Add a git rev-parse check before using --notes-start-tag. When the tag is missing, fall back to static notes with a :⚠️: annotation, ensuring the GitHub release is always created. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(sdk-python): clarify else-branch comment covers first stable + preview/nightly The comment previously implied the else-branch was only for preview/nightly, but PREVIOUS_RELEASE_TAG is also empty for the very first stable release (no prior stable version on PyPI). 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * fix(sdk-python): use Bash array for gh release args to fix SC2086 lint ShellCheck SC2086 flags unquoted variables containing spaces (NOTES_START_TAG_ARG holds "--notes-start-tag sdk-python-v0.1.0"). Replace string-based flag variables with a Bash array that is expanded via "${GH_RELEASE_ARGS[@]}" — properly quoted and shellcheck-safe. Also consolidates the prerelease flag into the same array, removing the now-unused PRERELEASE_FLAG variable. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code) * refactor(sdk-python): extract PREVIOUS_TAG_NAME to reduce repetition DRY improvement: sdk-python-${PREVIOUS_RELEASE_TAG} was repeated 3 times. Extract into a local PREVIOUS_TAG_NAME variable, symmetric with the existing TAG_NAME at the top of the script. 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
515 lines
21 KiB
YAML
515 lines
21 KiB
YAML
name: 'Release Python SDK'
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: 'The version to release (e.g., v0.1.0 for stable, v0.1.1-preview.0 for preview).'
|
|
required: false
|
|
type: 'string'
|
|
ref:
|
|
description: 'The protected branch ref to release SDK from. This privileged workflow only permits main.'
|
|
required: true
|
|
type: 'string'
|
|
default: 'main'
|
|
dry_run:
|
|
description: 'Run the release flow without publishing to PyPI or creating persistent release branches/tags.'
|
|
required: true
|
|
type: 'boolean'
|
|
default: true
|
|
create_nightly_release:
|
|
description: 'Auto-apply the nightly release tag. Input version is ignored.'
|
|
required: false
|
|
type: 'boolean'
|
|
default: false
|
|
create_preview_release:
|
|
description: 'Auto-apply the preview release tag. Input version is ignored unless explicitly provided.'
|
|
required: false
|
|
type: 'boolean'
|
|
default: false
|
|
force_skip_tests:
|
|
description: 'Skip Python checks and smoke test. Production releases should keep this disabled.'
|
|
required: false
|
|
type: 'boolean'
|
|
default: false
|
|
|
|
concurrency:
|
|
group: '${{ github.workflow }}-${{ github.event.inputs.create_nightly_release == ''true'' && ''nightly'' || github.event.inputs.create_preview_release == ''true'' && ''preview'' || ''stable'' }}'
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
release-sdk-python:
|
|
runs-on: 'ubuntu-latest'
|
|
environment:
|
|
name: 'production-release'
|
|
url: '${{ github.server_url }}/${{ github.repository }}/releases'
|
|
if: |-
|
|
${{ github.repository == 'QwenLM/qwen-code' }}
|
|
permissions:
|
|
contents: 'write'
|
|
id-token: 'write'
|
|
issues: 'write'
|
|
pull-requests: 'write'
|
|
|
|
steps:
|
|
- name: 'Validate release inputs'
|
|
env:
|
|
CREATE_NIGHTLY_RELEASE: '${{ github.event.inputs.create_nightly_release }}'
|
|
CREATE_PREVIEW_RELEASE: '${{ github.event.inputs.create_preview_release }}'
|
|
DRY_RUN_INPUT: '${{ github.event.inputs.dry_run }}'
|
|
FORCE_SKIP_TESTS: '${{ github.event.inputs.force_skip_tests }}'
|
|
MANUAL_VERSION: '${{ inputs.version }}'
|
|
REQUESTED_REF: '${{ github.event.inputs.ref || github.sha }}'
|
|
WORKFLOW_REF: '${{ github.ref }}'
|
|
run: |
|
|
if [[ "${CREATE_NIGHTLY_RELEASE}" == "true" && "${CREATE_PREVIEW_RELEASE}" == "true" ]]; then
|
|
echo "create_nightly_release and create_preview_release cannot both be true" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ -n "${MANUAL_VERSION}" ]]; then
|
|
if [[ ! "${MANUAL_VERSION}" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-preview\.[0-9]+)?$ ]]; then
|
|
echo "Invalid version format: ${MANUAL_VERSION}" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
case "${WORKFLOW_REF}" in
|
|
refs/heads/main)
|
|
;;
|
|
*)
|
|
echo "This privileged workflow must be launched from the protected main workflow branch." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
case "${REQUESTED_REF}" in
|
|
main|refs/heads/main)
|
|
;;
|
|
*)
|
|
echo "This privileged workflow must use the protected main ref." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
if [[ "${DRY_RUN_INPUT}" != "true" ]]; then
|
|
if [[ "${FORCE_SKIP_TESTS}" == "true" ]]; then
|
|
echo "force_skip_tests cannot be used when dry_run is false" >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
- name: 'Checkout'
|
|
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5
|
|
with:
|
|
ref: '${{ github.event.inputs.ref || github.sha }}'
|
|
fetch-depth: 0
|
|
|
|
- name: 'Set booleans for simplified logic'
|
|
env:
|
|
CREATE_NIGHTLY_RELEASE: '${{ github.event.inputs.create_nightly_release }}'
|
|
CREATE_PREVIEW_RELEASE: '${{ github.event.inputs.create_preview_release }}'
|
|
DRY_RUN_INPUT: '${{ github.event.inputs.dry_run }}'
|
|
id: 'vars'
|
|
run: |
|
|
is_nightly="false"
|
|
if [[ "${CREATE_NIGHTLY_RELEASE}" == "true" ]]; then
|
|
is_nightly="true"
|
|
fi
|
|
echo "is_nightly=${is_nightly}" >> "${GITHUB_OUTPUT}"
|
|
|
|
is_preview="false"
|
|
if [[ "${CREATE_PREVIEW_RELEASE}" == "true" ]]; then
|
|
is_preview="true"
|
|
fi
|
|
echo "is_preview=${is_preview}" >> "${GITHUB_OUTPUT}"
|
|
|
|
is_dry_run="false"
|
|
if [[ "${DRY_RUN_INPUT}" == "true" ]]; then
|
|
is_dry_run="true"
|
|
fi
|
|
echo "is_dry_run=${is_dry_run}" >> "${GITHUB_OUTPUT}"
|
|
|
|
- name: 'Capture checked-out commit'
|
|
id: 'checkout_sha'
|
|
run: |
|
|
echo "sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
|
|
|
|
- name: 'Setup Node.js'
|
|
uses: 'actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020' # ratchet:actions/setup-node@v4
|
|
with:
|
|
node-version-file: '.nvmrc'
|
|
cache: 'npm'
|
|
|
|
- name: 'Get the version'
|
|
id: 'version'
|
|
run: |
|
|
set -euo pipefail
|
|
VERSION_ARGS=()
|
|
if [[ "${IS_NIGHTLY}" == "true" ]]; then
|
|
VERSION_ARGS+=(--type=nightly)
|
|
elif [[ "${IS_PREVIEW}" == "true" ]]; then
|
|
VERSION_ARGS+=(--type=preview)
|
|
if [[ -n "${MANUAL_VERSION}" ]]; then
|
|
VERSION_ARGS+=("--preview_version_override=${MANUAL_VERSION}")
|
|
fi
|
|
else
|
|
VERSION_ARGS+=(--type=stable)
|
|
if [[ -n "${MANUAL_VERSION}" ]]; then
|
|
VERSION_ARGS+=("--stable_version_override=${MANUAL_VERSION}")
|
|
fi
|
|
fi
|
|
|
|
VERSION_JSON=$(node packages/sdk-python/scripts/get-release-version.js "${VERSION_ARGS[@]}")
|
|
|
|
extract_field() {
|
|
local value
|
|
value=$(echo "$VERSION_JSON" | jq -r ".$1")
|
|
if [[ "${value}" == "null" ]]; then
|
|
echo "Failed to extract $1 from version JSON: ${VERSION_JSON}" >&2
|
|
exit 1
|
|
fi
|
|
echo "${value}"
|
|
}
|
|
|
|
RELEASE_TAG=$(extract_field releaseTag)
|
|
RELEASE_VERSION=$(extract_field releaseVersion)
|
|
PACKAGE_VERSION=$(extract_field packageVersion)
|
|
PUBLISH_CHANNEL=$(extract_field publishChannel)
|
|
PREVIOUS_RELEASE_TAG=$(extract_field previousReleaseTag)
|
|
RESUME_EXISTING_RELEASE=$(extract_field resumeExistingRelease)
|
|
|
|
echo "RELEASE_TAG=${RELEASE_TAG}" >> "$GITHUB_OUTPUT"
|
|
echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT"
|
|
echo "PACKAGE_VERSION=${PACKAGE_VERSION}" >> "$GITHUB_OUTPUT"
|
|
echo "PUBLISH_CHANNEL=${PUBLISH_CHANNEL}" >> "$GITHUB_OUTPUT"
|
|
echo "PREVIOUS_RELEASE_TAG=${PREVIOUS_RELEASE_TAG}" >> "$GITHUB_OUTPUT"
|
|
echo "RESUME_EXISTING_RELEASE=${RESUME_EXISTING_RELEASE}" >> "$GITHUB_OUTPUT"
|
|
|
|
echo "========================================"
|
|
echo "Python SDK Release Version Info"
|
|
echo "========================================"
|
|
echo "Release Tag: ${RELEASE_TAG}"
|
|
echo "Release Version: ${RELEASE_VERSION}"
|
|
echo "Package Version: ${PACKAGE_VERSION}"
|
|
echo "Publish Channel: ${PUBLISH_CHANNEL}"
|
|
echo "Previous Release: ${PREVIOUS_RELEASE_TAG}"
|
|
echo "Resume Existing: ${RESUME_EXISTING_RELEASE}"
|
|
echo "========================================"
|
|
env:
|
|
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
|
IS_NIGHTLY: '${{ steps.vars.outputs.is_nightly }}'
|
|
IS_PREVIEW: '${{ steps.vars.outputs.is_preview }}'
|
|
MANUAL_VERSION: '${{ inputs.version }}'
|
|
|
|
- name: 'Setup Python'
|
|
uses: 'actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065' # ratchet:actions/setup-python@v5
|
|
with:
|
|
# Keep in sync with packages/sdk-python/pyproject.toml [project] requires-python.
|
|
python-version: '3.11'
|
|
|
|
- name: 'Install Dependencies'
|
|
run: |
|
|
npm ci
|
|
python -m pip install --upgrade pip
|
|
python -m pip install -e 'packages/sdk-python[dev]' build
|
|
|
|
- name: 'Build qwen CLI bundle'
|
|
if: |-
|
|
${{ github.event.inputs.force_skip_tests != 'true' }}
|
|
run: |
|
|
npm run build
|
|
npm run bundle
|
|
|
|
- name: 'Set Python package version (local only)'
|
|
env:
|
|
PACKAGE_VERSION: '${{ steps.version.outputs.PACKAGE_VERSION }}'
|
|
run: |
|
|
python - <<'PY'
|
|
from pathlib import Path
|
|
import os
|
|
import re
|
|
|
|
pyproject = Path('packages/sdk-python/pyproject.toml')
|
|
content = pyproject.read_text()
|
|
updated, count = re.subn(
|
|
r'^version = "[^"]+"$',
|
|
f'version = "{os.environ["PACKAGE_VERSION"]}"',
|
|
content,
|
|
flags=re.MULTILINE,
|
|
)
|
|
if count != 1:
|
|
raise SystemExit(
|
|
f'pyproject.toml version rewrite matched {count} lines, expected 1'
|
|
)
|
|
pyproject.write_text(updated)
|
|
PY
|
|
|
|
- name: 'Run Python quality gates'
|
|
if: |-
|
|
${{ github.event.inputs.force_skip_tests != 'true' }}
|
|
run: |
|
|
python -m ruff check --config packages/sdk-python/pyproject.toml packages/sdk-python
|
|
python -m ruff format --check --config packages/sdk-python/pyproject.toml packages/sdk-python
|
|
python -m mypy --config-file packages/sdk-python/pyproject.toml packages/sdk-python/src
|
|
python -m pytest -c packages/sdk-python/pyproject.toml packages/sdk-python/tests -q
|
|
|
|
- name: 'Run real smoke test'
|
|
if: |-
|
|
${{ github.event.inputs.force_skip_tests != 'true' }}
|
|
env:
|
|
OPENAI_API_KEY: '${{ secrets.OPENAI_API_KEY }}'
|
|
OPENAI_BASE_URL: '${{ secrets.OPENAI_BASE_URL }}'
|
|
OPENAI_MODEL: '${{ secrets.OPENAI_MODEL }}'
|
|
run: |
|
|
python packages/sdk-python/scripts/smoke_real.py --qwen "${GITHUB_WORKSPACE}/dist/cli.js" --cwd "${GITHUB_WORKSPACE}" --json-only
|
|
|
|
- name: 'Configure Git User'
|
|
run: |
|
|
git config user.name "github-actions[bot]"
|
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
|
|
- name: 'Create and switch to a release branch'
|
|
if: |-
|
|
${{ steps.vars.outputs.is_dry_run == 'false' }}
|
|
id: 'release_branch'
|
|
env:
|
|
RELEASE_TAG: '${{ steps.version.outputs.RELEASE_TAG }}'
|
|
RESUME_EXISTING_RELEASE: '${{ steps.version.outputs.RESUME_EXISTING_RELEASE }}'
|
|
run: |
|
|
set -euo pipefail
|
|
BRANCH_NAME="release/sdk-python/${RELEASE_TAG}"
|
|
VERSIONED_PYPROJECT="$(mktemp)"
|
|
cp packages/sdk-python/pyproject.toml "${VERSIONED_PYPROJECT}"
|
|
git restore --staged --worktree packages/sdk-python/pyproject.toml
|
|
|
|
if git ls-remote --exit-code --heads origin "${BRANCH_NAME}" >/dev/null 2>&1; then
|
|
git fetch origin "${BRANCH_NAME}"
|
|
if git show-ref --verify --quiet "refs/heads/${BRANCH_NAME}"; then
|
|
git switch "${BRANCH_NAME}"
|
|
else
|
|
git switch -c "${BRANCH_NAME}" --track "origin/${BRANCH_NAME}"
|
|
fi
|
|
git reset --hard "origin/${BRANCH_NAME}"
|
|
echo "REMOTE_BRANCH_EXISTS=true" >> "${GITHUB_OUTPUT}"
|
|
else
|
|
if [[ "${RESUME_EXISTING_RELEASE}" == "true" ]]; then
|
|
echo "Published version ${RELEASE_TAG} exists without a persisted release branch ${BRANCH_NAME}. To resolve: (1) re-trigger with a different version, or (2) manually create branch ${BRANCH_NAME} from the original release commit." >&2
|
|
exit 1
|
|
fi
|
|
git switch -c "${BRANCH_NAME}"
|
|
echo "REMOTE_BRANCH_EXISTS=false" >> "${GITHUB_OUTPUT}"
|
|
fi
|
|
|
|
cp "${VERSIONED_PYPROJECT}" packages/sdk-python/pyproject.toml
|
|
rm -f "${VERSIONED_PYPROJECT}"
|
|
echo "BRANCH_NAME=${BRANCH_NAME}" >> "${GITHUB_OUTPUT}"
|
|
|
|
- name: 'Commit and push package version'
|
|
if: |-
|
|
${{ steps.vars.outputs.is_dry_run == 'false' }}
|
|
id: 'persist_source'
|
|
env:
|
|
BRANCH_NAME: '${{ steps.release_branch.outputs.BRANCH_NAME }}'
|
|
CHECKED_OUT_SHA: '${{ steps.checkout_sha.outputs.sha }}'
|
|
REMOTE_BRANCH_EXISTS: '${{ steps.release_branch.outputs.REMOTE_BRANCH_EXISTS }}'
|
|
RELEASE_TAG: '${{ steps.version.outputs.RELEASE_TAG }}'
|
|
run: |
|
|
set -euo pipefail
|
|
git add packages/sdk-python/pyproject.toml
|
|
if git diff --staged --quiet; then
|
|
echo "No version changes to commit"
|
|
if [[ "${REMOTE_BRANCH_EXISTS}" == "true" ]]; then
|
|
echo "HAS_PERSISTED_SOURCE=true" >> "${GITHUB_OUTPUT}"
|
|
echo "RELEASE_TARGET_SHA=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
|
|
else
|
|
echo "HAS_PERSISTED_SOURCE=false" >> "${GITHUB_OUTPUT}"
|
|
echo "RELEASE_TARGET_SHA=${CHECKED_OUT_SHA}" >> "${GITHUB_OUTPUT}"
|
|
fi
|
|
else
|
|
git commit -m "chore(release): sdk-python ${RELEASE_TAG}"
|
|
echo "HAS_PERSISTED_SOURCE=true" >> "${GITHUB_OUTPUT}"
|
|
echo "RELEASE_TARGET_SHA=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
|
|
if [[ "${REMOTE_BRANCH_EXISTS}" == "true" ]]; then
|
|
git push origin "${BRANCH_NAME}"
|
|
else
|
|
git push --set-upstream origin "${BRANCH_NAME}"
|
|
fi
|
|
fi
|
|
|
|
- name: 'Build Python package'
|
|
working-directory: 'packages/sdk-python'
|
|
run: |
|
|
rm -rf dist
|
|
python -m build
|
|
|
|
- name: 'Publish qwen-code-sdk to PyPI'
|
|
if: |-
|
|
${{ steps.vars.outputs.is_dry_run == 'false' }}
|
|
uses: 'pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b' # ratchet:pypa/gh-action-pypi-publish@release/v1
|
|
with:
|
|
packages-dir: 'packages/sdk-python/dist'
|
|
# skip-existing handles resumed releases where some artifacts were already uploaded
|
|
skip-existing: '${{ steps.version.outputs.RESUME_EXISTING_RELEASE }}'
|
|
|
|
- name: 'Show publish artifacts for dry-run'
|
|
if: |-
|
|
${{ steps.vars.outputs.is_dry_run == 'true' }}
|
|
run: |
|
|
ls -la packages/sdk-python/dist
|
|
|
|
- name: 'Create GitHub release and tag'
|
|
if: |-
|
|
${{ steps.vars.outputs.is_dry_run == 'false' }}
|
|
env:
|
|
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
|
RELEASE_TAG: '${{ steps.version.outputs.RELEASE_TAG }}'
|
|
PACKAGE_VERSION: '${{ steps.version.outputs.PACKAGE_VERSION }}'
|
|
PREVIOUS_RELEASE_TAG: '${{ steps.version.outputs.PREVIOUS_RELEASE_TAG }}'
|
|
RELEASE_TARGET_SHA: '${{ steps.persist_source.outputs.RELEASE_TARGET_SHA }}'
|
|
IS_NIGHTLY: '${{ steps.vars.outputs.is_nightly }}'
|
|
IS_PREVIEW: '${{ steps.vars.outputs.is_preview }}'
|
|
run: |
|
|
set -euo pipefail
|
|
TAG_NAME="sdk-python-${RELEASE_TAG}"
|
|
|
|
if gh release view "${TAG_NAME}" --json tagName >/dev/null 2>&1; then
|
|
echo "::warning::GitHub release ${TAG_NAME} already exists; skipping create."
|
|
exit 0
|
|
fi
|
|
|
|
if git rev-parse "${TAG_NAME}" >/dev/null 2>&1; then
|
|
EXISTING_TAG_SHA="$(git rev-list -n 1 "${TAG_NAME}")"
|
|
if [[ "${EXISTING_TAG_SHA}" != "${RELEASE_TARGET_SHA}" ]]; then
|
|
echo "Existing tag ${TAG_NAME} points to ${EXISTING_TAG_SHA}, expected ${RELEASE_TARGET_SHA}." >&2
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
NOTES_FILE=$(mktemp)
|
|
{
|
|
echo "## Published Package"
|
|
echo ""
|
|
echo "- PyPI package: \`qwen-code-sdk\`"
|
|
echo "- Package version: \`${PACKAGE_VERSION}\`"
|
|
echo ""
|
|
echo "---"
|
|
echo ""
|
|
} > "${NOTES_FILE}"
|
|
|
|
GH_RELEASE_ARGS=()
|
|
if [[ -n "${PREVIOUS_RELEASE_TAG}" ]]; then
|
|
PREVIOUS_TAG_NAME="sdk-python-${PREVIOUS_RELEASE_TAG}"
|
|
# Verify the previous tag exists in Git before using --notes-start-tag.
|
|
# If a prior release published to PyPI but failed to create a GitHub
|
|
# release/tag, the tag won't exist — fall back to static notes to
|
|
# avoid failing gh release create after PyPI publish.
|
|
if git rev-parse "${PREVIOUS_TAG_NAME}" >/dev/null 2>&1; then
|
|
GH_RELEASE_ARGS+=(--generate-notes --notes-start-tag "${PREVIOUS_TAG_NAME}")
|
|
else
|
|
echo "::warning::Previous tag ${PREVIOUS_TAG_NAME} not found; skipping --generate-notes."
|
|
echo "See commit history for changes." >> "${NOTES_FILE}"
|
|
fi
|
|
else
|
|
# PREVIOUS_RELEASE_TAG is empty for preview/nightly (not computed)
|
|
# and for the very first stable release (no prior stable on PyPI).
|
|
# Skip --generate-notes to avoid including non-SDK commits.
|
|
echo "See commit history for changes." >> "${NOTES_FILE}"
|
|
fi
|
|
|
|
if [[ "${IS_NIGHTLY}" == "true" || "${IS_PREVIEW}" == "true" ]]; then
|
|
GH_RELEASE_ARGS+=(--prerelease)
|
|
fi
|
|
|
|
gh release create "${TAG_NAME}" \
|
|
--target "${RELEASE_TARGET_SHA}" \
|
|
--title "SDK Python Release ${RELEASE_TAG}" \
|
|
--notes-file "${NOTES_FILE}" \
|
|
"${GH_RELEASE_ARGS[@]}"
|
|
|
|
rm -f "${NOTES_FILE}"
|
|
|
|
- name: 'Delete prerelease release branch'
|
|
if: |-
|
|
${{ steps.vars.outputs.is_dry_run == 'false' && (steps.vars.outputs.is_nightly == 'true' || steps.vars.outputs.is_preview == 'true') }}
|
|
env:
|
|
BRANCH_NAME: '${{ steps.release_branch.outputs.BRANCH_NAME }}'
|
|
run: |
|
|
set -euo pipefail
|
|
if git ls-remote --exit-code --heads origin "${BRANCH_NAME}" >/dev/null 2>&1; then
|
|
if ! git push origin --delete "${BRANCH_NAME}"; then
|
|
echo "::warning::Failed to delete prerelease release branch ${BRANCH_NAME}; release already exists, so remove the branch manually if it still exists."
|
|
fi
|
|
else
|
|
echo "No prerelease release branch to delete for ${BRANCH_NAME}."
|
|
fi
|
|
|
|
- name: 'Create PR to merge release branch into main'
|
|
if: |-
|
|
${{ steps.vars.outputs.is_dry_run == 'false' && steps.vars.outputs.is_nightly == 'false' && steps.vars.outputs.is_preview == 'false' && steps.persist_source.outputs.HAS_PERSISTED_SOURCE == 'true' }}
|
|
id: 'pr'
|
|
env:
|
|
GITHUB_TOKEN: '${{ secrets.CI_BOT_PAT }}'
|
|
RELEASE_BRANCH: '${{ steps.release_branch.outputs.BRANCH_NAME }}'
|
|
RELEASE_TAG: '${{ steps.version.outputs.RELEASE_TAG }}'
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
pr_url="$(gh pr list --head "${RELEASE_BRANCH}" --base main --json url --jq '.[0].url')"
|
|
if [[ -z "${pr_url}" ]]; then
|
|
pr_url="$(gh pr create \
|
|
--base main \
|
|
--head "${RELEASE_BRANCH}" \
|
|
--title "chore(release): sdk-python ${RELEASE_TAG}" \
|
|
--body "Automated release PR for sdk-python ${RELEASE_TAG}.")"
|
|
fi
|
|
|
|
echo "PR_URL=${pr_url}" >> "${GITHUB_OUTPUT}"
|
|
|
|
- name: 'Enable auto-merge for release PR'
|
|
if: |-
|
|
${{ steps.vars.outputs.is_dry_run == 'false' && steps.vars.outputs.is_nightly == 'false' && steps.vars.outputs.is_preview == 'false' && steps.persist_source.outputs.HAS_PERSISTED_SOURCE == 'true' }}
|
|
env:
|
|
GITHUB_TOKEN: '${{ secrets.CI_BOT_PAT }}'
|
|
PR_URL: '${{ steps.pr.outputs.PR_URL }}'
|
|
run: |
|
|
set -euo pipefail
|
|
gh pr merge "${PR_URL}" --merge --auto --delete-branch
|
|
|
|
- name: 'Create issue on failure'
|
|
if: |-
|
|
${{ failure() && github.event.inputs.dry_run != 'true' }}
|
|
env:
|
|
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}'
|
|
RELEASE_TAG: "${{ steps.version.outputs.RELEASE_TAG || 'N/A' }}"
|
|
PACKAGE_VERSION: "${{ steps.version.outputs.PACKAGE_VERSION || 'N/A' }}"
|
|
PUBLISH_CHANNEL: "${{ steps.version.outputs.PUBLISH_CHANNEL || 'N/A' }}"
|
|
RESUME_EXISTING_RELEASE: "${{ steps.version.outputs.RESUME_EXISTING_RELEASE || 'N/A' }}"
|
|
BRANCH_NAME: "${{ steps.release_branch.outputs.BRANCH_NAME || 'N/A' }}"
|
|
HAS_PERSISTED_SOURCE: "${{ steps.persist_source.outputs.HAS_PERSISTED_SOURCE || 'N/A' }}"
|
|
RELEASE_TARGET_SHA: "${{ steps.persist_source.outputs.RELEASE_TARGET_SHA || 'N/A' }}"
|
|
PR_URL: "${{ steps.pr.outputs.PR_URL || 'N/A' }}"
|
|
DETAILS_URL: '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}'
|
|
run: |
|
|
set -euo pipefail
|
|
BODY=$(cat <<BODY
|
|
The Python SDK release workflow failed.
|
|
|
|
| Field | Value |
|
|
|-------|-------|
|
|
| Release Tag | \`${RELEASE_TAG}\` |
|
|
| Package Version | \`${PACKAGE_VERSION}\` |
|
|
| Publish Channel | \`${PUBLISH_CHANNEL}\` |
|
|
| Resume Existing | \`${RESUME_EXISTING_RELEASE}\` |
|
|
| Release Branch | \`${BRANCH_NAME}\` |
|
|
| Persisted Source | \`${HAS_PERSISTED_SOURCE}\` |
|
|
| Target SHA | \`${RELEASE_TARGET_SHA}\` |
|
|
| PR URL | ${PR_URL} |
|
|
|
|
See the full run for details: ${DETAILS_URL}
|
|
BODY
|
|
)
|
|
gh issue create \
|
|
--repo "${GITHUB_REPOSITORY}" \
|
|
--title "Python SDK release failed for ${RELEASE_TAG} on $(date +'%Y-%m-%d')" \
|
|
--body "${BODY}"
|