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
3 changes: 3 additions & 0 deletions changelog.d/20260509_204000_issue_6_release_metadata.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- GitHub release creation now supports configurable tag prefixes, language-labeled release titles, and automatic PyPI badge insertion when release notes do not already contain a shields.io badge.
52 changes: 43 additions & 9 deletions scripts/create_github_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
Create a GitHub release from CHANGELOG.md content.

Usage:
python scripts/create_github_release.py --version VERSION --repository REPO
python scripts/create_github_release.py --version VERSION --repository REPO \
[--tag-prefix PREFIX] [--language LANGUAGE]

Example:
python scripts/create_github_release.py --version 1.2.3 --repository owner/repo
python scripts/create_github_release.py --version 1.2.3 --repository owner/repo \
--tag-prefix python_v --language Python

Environment variables:
GH_TOKEN or GITHUB_TOKEN: GitHub token for authentication
Expand Down Expand Up @@ -71,14 +73,30 @@ def extract_changelog_entry(changelog_path: Path, version: str) -> str:
return entry if entry else f"Release {version}"


def append_pypi_badge_if_missing(release_notes: str, version: str) -> str:
"""Append a PyPI version badge unless a shields.io badge is already present."""
if "img.shields.io" in release_notes.lower():
return release_notes

badge = f"![PyPI](https://img.shields.io/badge/pypi-{version}-blue.svg)"
return f"{release_notes.rstrip()}\n\n{badge}"


def create_release(
version: str, repository: str, release_notes: str, prerelease: bool = False
version: str,
repository: str,
release_notes: str,
prerelease: bool = False,
tag_prefix: str = "v",
language: str = "Python",
) -> None:
"""Create a GitHub release using gh CLI."""
tag = f"v{version}"
tag = f"{tag_prefix}{version}"
title = f"[{language}] {version}"

print(f"\nCreating GitHub release for {tag}...")
print(f"Repository: {repository}")
print(f"Title: {title}")
print(f"Prerelease: {prerelease}")
print(f"\nRelease notes:\n{release_notes}\n")

Expand All @@ -90,7 +108,7 @@ def create_release(
"--repo",
repository,
"--title",
tag,
title,
"--notes",
release_notes,
]
Expand Down Expand Up @@ -124,6 +142,16 @@ def main() -> int:
action="store_true",
help="Mark as prerelease",
)
parser.add_argument(
"--tag-prefix",
default="v",
help='Tag prefix for the release (default "v")',
)
parser.add_argument(
"--language",
default="Python",
help='Language label for the release title (default "Python")',
)

args = parser.parse_args()

Expand All @@ -150,11 +178,17 @@ def main() -> int:
changelog_path = project_root / "CHANGELOG.md"

try:
# Extract changelog entry
release_notes = extract_changelog_entry(changelog_path, args.version)

# Create release
create_release(args.version, args.repository, release_notes, args.prerelease)
release_notes = append_pypi_badge_if_missing(release_notes, args.version)

create_release(
args.version,
args.repository,
release_notes,
args.prerelease,
args.tag_prefix,
args.language,
)

return 0

Expand Down
69 changes: 69 additions & 0 deletions tests/test_create_github_release.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Tests for scripts/create_github_release.py."""

from __future__ import annotations

import importlib.util
import subprocess
import sys
from pathlib import Path


SCRIPT_PATH = (
Path(__file__).resolve().parent.parent / "scripts" / "create_github_release.py"
)
spec = importlib.util.spec_from_file_location("create_github_release", SCRIPT_PATH)
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module) # type: ignore[union-attr]


def test_create_release_uses_tag_prefix_and_language_title(monkeypatch) -> None:
"""Release creation should separate tag format from display title."""
commands = []

def fake_run_command(cmd, check=True):
commands.append(cmd)
return subprocess.CompletedProcess(cmd, 0, "", "")

monkeypatch.setattr(module, "run_command", fake_run_command)

module.create_release(
version="1.2.3",
repository="owner/repo",
release_notes="Release notes",
prerelease=False,
tag_prefix="python_v",
language="Python",
)

assert commands == [
[
"gh",
"release",
"create",
"python_v1.2.3",
"--repo",
"owner/repo",
"--title",
"[Python] 1.2.3",
"--notes",
"Release notes",
],
]


def test_append_pypi_badge_if_missing_adds_static_version_badge() -> None:
"""Release notes should get a PyPI badge before gh creates the release."""
body = module.append_pypi_badge_if_missing("Release notes", "1.2.3")

assert "Release notes" in body
assert "https://img.shields.io/badge/pypi-1.2.3-blue.svg" in body


def test_append_pypi_badge_if_missing_does_not_duplicate_badge() -> None:
"""Existing shields.io badges should be preserved without duplication."""
existing = (
"Release notes\n\n![PyPI](https://img.shields.io/badge/pypi-1.2.3-blue.svg)"
)

assert module.append_pypi_badge_if_missing(existing, "1.2.3") == existing
Loading