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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -456,9 +456,10 @@ creates the Release; this Action only writes a local Markdown file.
- `tag` **(required)**: Release version, such as `1.2.3`. The checkout must contain its changelog section.
- `changelog` **(required)**: Tracked Markdown or reStructuredText changelog in the format that `release-creator notes`
parses, with exactly one section for the tag.
- `artifact-url`: Ecosystem release URL, such as a PyPI version page. Omit it for a GitHub-only project.
- `artifact-label`: Link text for `artifact-url`; default `PyPI release`. Use `Zipapp` or another product name for a
different artifact.
- `artifact-url`: Ecosystem release URL, such as a PyPI version page. Omit it for a GitHub-only project. A project that
publishes several packages puts one URL per line; the links share one line of the body, separated by commas.
- `artifact-label`: Link text for each `artifact-url` line, in the same order; default `PyPI release`. Use `Zipapp` or
another product name for a different artifact. `release-creator` adds an emoji for the link's ecosystem.

The Action reads `GITHUB_REPOSITORY` for PR and comparison links. A repository with separate release namespaces, such as
toml-fmt, calls `release-creator notes --package` directly for its package changelog.
Expand Down
3 changes: 3 additions & 0 deletions changelog.d/+artifact-links.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
`render-release-notes` takes one `artifact-url` per line, with matching `artifact-label` lines, so a project that
publishes several packages lists them all on one row of the GitHub Release. Release bodies now come from
`release-creator` 4.3.0, which marks each link with its ecosystem emoji.
28 changes: 7 additions & 21 deletions pylock.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ sha256 = "cb7654125e3dcb594269a6feb56ac693f1f13b7c654493a93eba72dc22d18034"
[[packages]]
name = "pyyaml"
version = "6.0.3"
marker = "\"release-prepare\" in dependency_groups or \"test\" in dependency_groups"
marker = "\"test\" in dependency_groups"
requires-python = ">=3.8"
index = "https://pypi.org/simple/"

Expand All @@ -1045,7 +1045,7 @@ sha256 = "d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"

[[packages]]
name = "release-creator"
version = "4.1.0"
version = "4.3.0"
marker = "\"release-prepare\" in dependency_groups or \"test\" in dependency_groups"
requires-python = ">=3.15"
dependencies = [
Expand All @@ -1054,28 +1054,18 @@ dependencies = [
{ name = "mdformat" },
{ name = "mdformat-gfm" },
{ name = "packaging" },
{ name = "pyyaml" },
{ name = "towncrier" },
]
index = "https://pypi.org/simple/"

[packages.sdist]
name = "release_creator-4.1.0.tar.gz"
upload-time = 2026-10-26 18:24:45.805437+00:00
url = "https://files.pythonhosted.org/packages/7f/fd/05d7f3c604476a238d67d5aaa2a4085312e7078c193dbd7ad99ad48d560d/release_creator-4.1.0.tar.gz"
size = 186016

[packages.sdist.hashes]
sha256 = "c783037f5ca2efc16fa7cccfb1c2abf0a442f116a63ca82bd7bfb7e2a8343a15"

[[packages.wheels]]
name = "release_creator-4.1.0-py3-none-any.whl"
upload-time = 2026-10-26 18:24:44.353933+00:00
url = "https://files.pythonhosted.org/packages/30/d0/38480e99025c021d07e0710af02547562dfadbd798bef26dc01869a3f81e/release_creator-4.1.0-py3-none-any.whl"
size = 52922
name = "release_creator-4.3.0-py3-none-any.whl"
upload-time = 2026-10-27 20:03:24.840699+00:00
url = "https://files.pythonhosted.org/packages/c4/35/cb402853dd142441cbfca66439332ff190edb76dc2111bae3bf52fa67ed8/release_creator-4.3.0-py3-none-any.whl"
size = 53455

[packages.wheels.hashes]
sha256 = "67deae16d11ac261a1512f18a22c0cf13418f43afc3e56cb74e58ad4422062e3"
sha256 = "8d5a60d4b90fb772c73f106cce25c4dec9a658ef483b4223a5af4a9f4a6d5429"

[[packages]]
name = "setuptools"
Expand Down Expand Up @@ -1472,10 +1462,6 @@ command-line = [
"lock",
"--all-groups",
"--all-extras",
"--no-cache",
"pyproject.toml",
"--output",
"pylock.toml",
]
input-path = "pyproject.toml"
mode = "universal"
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test = [
"pytest-mock>=3.16",
"pytest-xdist>=3",
"pyyaml>=6",
"release-creator>=4.1",
"release-creator>=4.3",
"tomlkit>=0.13",
"tox>=4.64.7",
"tox-uv>=1.36.1",
Expand All @@ -36,9 +36,9 @@ uploaded-prior-to = "P7D"
python = ">=3.15,<3.16"
platforms = ["linux_x86_64"]

# Temporary: release-creator 4.1.0 is younger than seven days. Delete after 2026-11-02.
# Temporary: release-creator 4.3.0 is younger than seven days. Delete after 2026-11-03.
[tool.nab.packages.release-creator]
uploaded-prior-to = 2026-10-26T18:24:46Z
uploaded-prior-to = 2026-10-27T20:03:25Z

[tool.ruff]
line-length = 120
Expand Down
23 changes: 16 additions & 7 deletions render-release-notes/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ inputs:
description: Tracked changelog path
required: true
artifact-url:
description: Published ecosystem release URL
description: Published ecosystem release URL, one per line for several packages
default: ""
artifact-label:
description: Link text for the published artifact
default: PyPI release
description: Link text for each artifact-url line, in the same order; default PyPI release
default: ""
previous:
description: Previous tag, or auto to calculate it from Git history
default: auto
Expand Down Expand Up @@ -55,10 +55,19 @@ runs:
if [[ -n "$PREVIOUS" ]]; then
args+=(--previous "$PREVIOUS")
fi
if [[ -n "$ARTIFACT_URL" ]]; then
args+=(--artifact-url "$ARTIFACT_URL" --artifact-label "$ARTIFACT_LABEL")
echo "Artifact link: $ARTIFACT_LABEL -> $ARTIFACT_URL"
else
mapfile -t urls < <(grep -v '^[[:space:]]*$' <<<"$ARTIFACT_URL" || true)
mapfile -t labels < <(grep -v '^[[:space:]]*$' <<<"$ARTIFACT_LABEL" || true)
if ((${#labels[@]} && ${#labels[@]} != ${#urls[@]})); then
echo "::error::artifact-label has ${#labels[@]} lines for ${#urls[@]} artifact-url lines"
exit 1
fi
for index in "${!urls[@]}"; do
args+=(--artifact-url "${urls[index]}")
if ((${#labels[@]})); then
args+=(--artifact-label "${labels[index]}")
fi
done
if ((${#urls[@]} == 0)); then
echo 'Artifact link skipped: artifact-url input is empty'
fi
release-creator notes "${args[@]}" >"$RUNNER_TEMP/release-notes.md"
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def action(
"BRANCH": "main",
"ANNOTATED": "false",
"ARTIFACT_URL": "",
"ARTIFACT_LABEL": "PyPI release",
"ARTIFACT_LABEL": "",
"LOCK": "pylock.toml",
"MODE": "complete",
} | extra_env
Expand Down
2 changes: 1 addition & 1 deletion tests/prepare_towncrier/test_prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def test_prepare_renders_github_notes(

- Add a new feature ([#2](https://github.com/tox-dev/github-workflows/pull/2)) by [@gaborbernat](https://github.com/gaborbernat).

[Full Changelog](https://github.com/tox-dev/github-workflows/compare/0.1.0...0.2.0)
[πŸ“œ Full Changelog](https://github.com/tox-dev/github-workflows/compare/0.1.0...0.2.0)
""",
)

Expand Down
43 changes: 40 additions & 3 deletions tests/render_release_notes/test_notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ def test_render_release_notes_uses_tagged_changelog(

- Add a new feature ([#2](https://github.com/tox-dev/fixture/pull/2)) by [@gaborbernat](https://github.com/gaborbernat).

[PyPI release](https://pypi.org/project/fixture/0.2.0/)
[🐍 PyPI release](https://pypi.org/project/fixture/0.2.0/)

[Full Changelog](https://github.com/tox-dev/fixture/compare/0.1.0...0.2.0)
[πŸ“œ Full Changelog](https://github.com/tox-dev/fixture/compare/0.1.0...0.2.0)
""",
)
assert "previous=0.1.0" in (tmp_path / "output").read_text()
Expand Down Expand Up @@ -122,6 +122,43 @@ def test_render_release_notes_custom_artifact_label(
)
assert result.returncode == 0, result.stderr
assert (
"[Zipapp](https://github.com/tox-dev/fixture/releases/download/0.2.0/fixture.pyz)"
"[πŸ“¦ Zipapp](https://github.com/tox-dev/fixture/releases/download/0.2.0/fixture.pyz)"
in (tmp_path / "release-notes.md").read_text()
)


def test_render_release_notes_lists_every_artifact_line(
tagged_repository: Path, tmp_path: Path, action_scripts: Path
) -> None:
result = action(
tagged_repository,
tmp_path,
action_scripts,
"render-release-notes",
version="0.2.0",
TAG="0.2.0",
ARTIFACT_URL="https://pypi.org/project/fixture/0.2.0/\nhttps://pypi.org/project/fixture-bare/0.2.0/\n",
ARTIFACT_LABEL="PyPI: fixture\nPyPI: fixture-bare\n",
)
assert result.returncode == 0, result.stderr
assert (
"[🐍 PyPI: fixture](https://pypi.org/project/fixture/0.2.0/), "
"[🐍 PyPI: fixture-bare](https://pypi.org/project/fixture-bare/0.2.0/)\n"
) in (tmp_path / "release-notes.md").read_text()


def test_render_release_notes_rejects_unpaired_labels(
tagged_repository: Path, tmp_path: Path, action_scripts: Path
) -> None:
result = action(
tagged_repository,
tmp_path,
action_scripts,
"render-release-notes",
version="0.2.0",
TAG="0.2.0",
ARTIFACT_URL="https://pypi.org/project/fixture/0.2.0/\nhttps://pypi.org/project/fixture-bare/0.2.0/",
ARTIFACT_LABEL="PyPI: fixture",
)
assert result.returncode != 0
assert "artifact-label has 1 lines for 2 artifact-url lines" in result.stdout