From 6837206732376b55bef6f727cebb9b4b24c2b759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Tue, 27 Oct 2026 11:57:43 -0700 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20feat(notes):=20pass=20one=20art?= =?UTF-8?q?ifact=20link=20per=20line?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 ++-- render-release-notes/action.yml | 23 +++++++++---- tests/conftest.py | 2 +- tests/render_release_notes/test_notes.py | 43 ++++++++++++++++++++++-- 4 files changed, 61 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a6d3607..1e3f7dc 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/render-release-notes/action.yml b/render-release-notes/action.yml index 2f89c4e..1e80d2f 100644 --- a/render-release-notes/action.yml +++ b/render-release-notes/action.yml @@ -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 @@ -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" diff --git a/tests/conftest.py b/tests/conftest.py index 9be76ce..25c5908 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -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 diff --git a/tests/render_release_notes/test_notes.py b/tests/render_release_notes/test_notes.py index 862a797..e3e6ad1 100644 --- a/tests/render_release_notes/test_notes.py +++ b/tests/render_release_notes/test_notes.py @@ -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() @@ -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 From affa1454537bf9a065dc30ffd5af024c2267747f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bern=C3=A1t=20G=C3=A1bor?= Date: Tue, 27 Oct 2026 13:06:38 -0700 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=92=20build(deps):=20lock=20releas?= =?UTF-8?q?e-creator=204.3.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog.d/+artifact-links.feature.md | 3 +++ pylock.toml | 28 +++++++------------------ pyproject.toml | 6 +++--- tests/prepare_towncrier/test_prepare.py | 2 +- 4 files changed, 14 insertions(+), 25 deletions(-) create mode 100644 changelog.d/+artifact-links.feature.md diff --git a/changelog.d/+artifact-links.feature.md b/changelog.d/+artifact-links.feature.md new file mode 100644 index 0000000..70fc35e --- /dev/null +++ b/changelog.d/+artifact-links.feature.md @@ -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. diff --git a/pylock.toml b/pylock.toml index eb00bb1..051523f 100644 --- a/pylock.toml +++ b/pylock.toml @@ -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/" @@ -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 = [ @@ -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" @@ -1472,10 +1462,6 @@ command-line = [ "lock", "--all-groups", "--all-extras", - "--no-cache", - "pyproject.toml", - "--output", - "pylock.toml", ] input-path = "pyproject.toml" mode = "universal" diff --git a/pyproject.toml b/pyproject.toml index 6170c76..631c327 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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", @@ -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 diff --git a/tests/prepare_towncrier/test_prepare.py b/tests/prepare_towncrier/test_prepare.py index 4873d68..2ddb853 100644 --- a/tests/prepare_towncrier/test_prepare.py +++ b/tests/prepare_towncrier/test_prepare.py @@ -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) """, )