From 12c4348549f0c2397eb698eb6f86068b36661d04 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 9 Nov 2026 18:49:24 +0100 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=94=A7=20MAINTAIN:=20Build=20and=20ch?= =?UTF-8?q?eck=20the=20distributions=20on=20every=20PR?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `build` job that runs `python -m build` and `twine check --strict`, checks on a tag push that the tag matches the built version, and uploads the distributions as the `dist` artifact. `allgood` now needs it, so a broken build is caught before a tag. The workflow also gets a read-only default `permissions:` block. --- .github/workflows/tests.yml | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 588482f..990cea4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,6 +10,9 @@ on: - 'v*' pull_request: +permissions: + contents: read + jobs: pre-commit: @@ -55,11 +58,48 @@ jobs: files: ./coverage.xml fail_ci_if_error: true + build: + + name: Build distributions + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-python@v7 + with: + python-version: "3.13" + # `python -m build` builds with the project's own [build-system] pin, + # so the release build cannot drift from it + # (`pip install flit~=3.4` resolves flit 3.12 + flit_core 4.2, and `flit build` crashes) + - run: python -m pip install build twine + - run: python -m build + - run: python -m twine check --strict dist/* + - name: Check the tag matches the built version + if: startsWith(github.ref, 'refs/tags/v') + run: | + python - <<'EOF' + import os, pathlib, sys + tag = os.environ["GITHUB_REF_NAME"].removeprefix("v") + dists = list(pathlib.Path("dist").glob("*.whl")) + list(pathlib.Path("dist").glob("*.tar.gz")) + versions = {p.name.removesuffix(".tar.gz").split("-")[1] for p in dists} + if versions != {tag}: + sys.exit(f"tag v{tag} != built version(s) {sorted(versions)}") + print(f"tag v{tag} matches the built version") + EOF + - uses: actions/upload-artifact@v7 + with: + name: dist + path: dist/ + retention-days: 7 + allgood: runs-on: ubuntu-latest needs: - pre-commit - tests + - build steps: - run: echo "Great success!" From ab87096e65b05be8f030e9af7deac9510f7cc967 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 9 Nov 2026 18:49:36 +0100 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=90=9B=20FIX:=20Publish=20the=20check?= =?UTF-8?q?ed=20build=20artifact=20instead=20of=20running=20flit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pip install flit~=3.4` now resolves flit 3.12.0 with flit_core 4.2.0, and `flit build` then crashes with `TypeError: SdistBuilder.build() got an unexpected keyword argument 'gen_setup_py'`, so the next tag would fail before uploading anything. The `publish` job now downloads the `dist` artifact from the `build` job and uploads it with trusted publishing. It needs `pre-commit`, `tests` and `build`, runs only for `v*` tag pushes, and its only permission is `id-token: write`. --- .github/workflows/tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 990cea4..1b9fde6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -106,19 +106,19 @@ jobs: publish: name: Publish to PyPI - needs: [pre-commit, tests] - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') + needs: [pre-commit, tests, build] + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-latest permissions: - id-token: write + id-token: write # the only permission this job has (trusted publishing) environment: name: pypi url: https://pypi.org/p/mdit-py-plugins steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + # publish exactly the distributions the `build` job built and checked + - uses: actions/download-artifact@v8 with: - python-version: "3.10" - - run: pip install flit~=3.4 - - run: flit build + name: dist + path: dist/ + # attestations are generated and uploaded by default - uses: pypa/gh-action-pypi-publish@release/v1 From 3e13512590a8af57b347d7398a0f7d1877265e72 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 9 Nov 2026 19:02:55 +0100 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=94=A7=20MAINTAIN:=20Require=20one=20?= =?UTF-8?q?wheel=20and=20one=20sdist=20in=20the=20tag=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tag check compared a set of versions, so a dist/ holding only a wheel or only an sdist would still pass. --- .github/workflows/tests.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1b9fde6..daa662e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -82,8 +82,11 @@ jobs: python - <<'EOF' import os, pathlib, sys tag = os.environ["GITHUB_REF_NAME"].removeprefix("v") - dists = list(pathlib.Path("dist").glob("*.whl")) + list(pathlib.Path("dist").glob("*.tar.gz")) - versions = {p.name.removesuffix(".tar.gz").split("-")[1] for p in dists} + wheels = list(pathlib.Path("dist").glob("*.whl")) + sdists = list(pathlib.Path("dist").glob("*.tar.gz")) + if len(wheels) != 1 or len(sdists) != 1: + sys.exit(f"expected one wheel and one sdist, got {len(wheels)} and {len(sdists)}") + versions = {p.name.removesuffix(".tar.gz").split("-")[1] for p in wheels + sdists} if versions != {tag}: sys.exit(f"tag v{tag} != built version(s) {sorted(versions)}") print(f"tag v{tag} matches the built version")