Skip to content

Release Please

Release Please #1345

name: Release Please
on:
workflow_run: # skylos: ignore[SKY-D290] guarded same-repo main pushes; SHA verified before checkout
workflows:
# Workflow-name filters are globs; '+' must match literally.
- 'Tests \+ Coverage'
- Analyzer Speed Check
- Corpus Guard
- Quality Benchmark
- Skylos Advisory Scan
branches: [main]
types: [completed]
permissions:
contents: read
jobs:
release-please:
name: Release Please
if: >-
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.head_repository.full_name == github.repository &&
github.event.workflow_run.head_sha == github.sha &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
timeout-minutes: 10
concurrency:
group: release-please-main
cancel-in-progress: false
queue: max
permissions:
contents: write
checks: read
issues: write
pull-requests: write
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
- name: Require current main commit
id: main_commit
env:
GH_TOKEN: ${{ github.token }}
TESTED_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail
main_sha="$(gh api "/repos/${GITHUB_REPOSITORY}/git/ref/heads/main" --jq '.object.sha')"
if [[ "$TESTED_SHA" != "$main_sha" ]]; then
echo "Skipping stale CI result: tested $TESTED_SHA, current main $main_sha"
exit 0
fi
echo "current=true" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
if: steps.main_commit.outputs.current == 'true'
with:
ref: ${{ github.event.workflow_run.head_sha }}
persist-credentials: false
- name: Require successful release checks
id: checks
if: steps.main_commit.outputs.current == 'true'
env:
GH_TOKEN: ${{ github.token }}
TESTED_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
set -euo pipefail
for attempt in 1 2 3; do
main_sha="$(gh api "/repos/${GITHUB_REPOSITORY}/git/ref/heads/main" --jq '.object.sha')"
if [[ "$TESTED_SHA" != "$main_sha" ]]; then
echo "Skipping stale CI result: tested $TESTED_SHA, current main $main_sha"
exit 0
fi
gh api --paginate --slurp \
-H "Accept: application/vnd.github+json" \
"/repos/${GITHUB_REPOSITORY}/commits/${TESTED_SHA}/check-runs?per_page=100&filter=all" |
jq '{check_runs: [.[].check_runs[]]}' > "$RUNNER_TEMP/skylos-release-checks.json"
status=0
python tools/release/check_release_checks.py "$RUNNER_TEMP/skylos-release-checks.json" || status=$?
if [ "$status" -eq 2 ] && [ "$attempt" -lt 3 ]; then
sleep 5
continue
fi
if [ "$status" -eq 1 ] || [ "$status" -eq 2 ]; then
echo "Release deferred; required CI completion will trigger another check."
exit 0
fi
if [ "$status" -ne 0 ]; then
exit "$status"
fi
main_sha="$(gh api "/repos/${GITHUB_REPOSITORY}/git/ref/heads/main" --jq '.object.sha')"
if [[ "$TESTED_SHA" != "$main_sha" ]]; then
echo "Skipping stale CI result: tested $TESTED_SHA, current main $main_sha"
exit 0
fi
echo "ready=true" >> "$GITHUB_OUTPUT"
exit 0
done
- uses: googleapis/release-please-action@16a9c90856f42705d54a6fda1823352bdc62cf38 # v4.4.0 (pin SHA to avoid supply chain attacks))
id: release
if: steps.checks.outputs.ready == 'true'
with:
# Prefer a dedicated token/App so release PRs can satisfy required PR checks.
token: ${{ secrets.RELEASE_PLEASE_TOKEN != '' && secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
target-branch: main
config-file: tools/release/release-please-config.json
manifest-file: tools/release/.release-please-manifest.json
publish-release:
name: Publish release artifacts
needs: release-please
if: ${{ needs.release-please.outputs.release_created == 'true' }}
uses: ./.github/workflows/publish.yml
permissions:
contents: read
checks: read
packages: write
# Granted only so publish.yml validates: its own PyPI job requests it
# but is skipped when called from here.
id-token: write
with:
ref: ${{ needs.release-please.outputs.tag_name }}
# PyPI Trusted Publishing (OIDC) for releases. PyPI matches the token to this
# top-level workflow file and the "pypi" environment; there is no stored
# upload token. The dist artifact comes from publish.yml's build job in this
# same run.
pypi-publish:
name: Publish to PyPI
needs: publish-release
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
id-token: write
environment:
name: pypi
url: https://pypi.org/project/skylos/
steps:
- name: Download dist artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
skip-existing: true