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
27 changes: 27 additions & 0 deletions .github/scripts/select-release-by-tag.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""Select exactly one release from paginated GitHub API output, including drafts."""

from __future__ import annotations

import argparse
import json
import sys


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("tag")
args = parser.parse_args()
pages = json.load(sys.stdin)
if not isinstance(pages, list) or not all(isinstance(page, list) for page in pages):
parser.error("expected paginated release arrays")
releases = [release for page in pages for release in page]
if not all(isinstance(release, dict) for release in releases):
parser.error("expected release objects")
matches = [release for release in releases if release.get("tag_name") == args.tag]
if len(matches) != 1:
parser.error(f"expected exactly one release for tag {args.tag}")
json.dump(matches[0], sys.stdout)


if __name__ == "__main__":
main()
8 changes: 7 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ jobs:
environment: pypi
runs-on: ubuntu-latest
steps:
- name: Check out trusted release policy
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Find matching release candidate
id: release
shell: bash
Expand All @@ -36,7 +40,9 @@ jobs:
echo "invalid release tag: ${RELEASE_TAG}" >&2
exit 1
fi
if [[ "$(gh api "repos/${GITHUB_REPOSITORY}/releases/tags/${RELEASE_TAG}" --jq '.draft')" != true ]]; then
release=$(gh api --paginate --slurp "repos/${GITHUB_REPOSITORY}/releases?per_page=100" |
python .github/scripts/select-release-by-tag.py "$RELEASE_TAG")
if [[ "$(jq -r '.draft' <<<"$release")" != true ]]; then
echo "GitHub release ${RELEASE_TAG} must still be a draft" >&2
exit 1
fi
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/test_release_selector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from __future__ import annotations

import json
import subprocess
import sys
from pathlib import Path

import pytest

SCRIPT = Path(__file__).parents[2] / ".github/scripts/select-release-by-tag.py"


def select(pages: object) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, str(SCRIPT), "v0.7.0"],
input=json.dumps(pages),
text=True,
capture_output=True,
check=False,
)


def test_draft_on_later_page_is_selected() -> None:
draft = {"id": 42, "tag_name": "v0.7.0", "draft": True}
result = select([[{"tag_name": "v0.6.0"}], [draft]])
assert result.returncode == 0, result.stderr
assert json.loads(result.stdout) == draft


@pytest.mark.parametrize(
"pages",
[
[],
[[]],
[[{"tag_name": "v0.6.0"}]],
[[{"tag_name": "v0.7.0"}], [{"tag_name": "v0.7.0"}]],
{},
[{"tag_name": "v0.7.0"}],
[[None]],
],
)
def test_missing_duplicate_or_invalid_release_is_rejected(pages: object) -> None:
result = select(pages)
assert result.returncode != 0
assert result.stdout == ""
Loading