From 4f71054dd1e81a10cb6bf5fb6c78ac944483ecaa Mon Sep 17 00:00:00 2001 From: Adrien Mannocci Date: Thu, 5 Nov 2026 12:08:49 +0100 Subject: [PATCH 1/4] build: declare macOS support and link the changelog in the metadata Signed-off-by: Adrien Mannocci --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1fd05b2..fa6bebd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ keywords = ["subprocess", "process", "pipeline", "asyncio", "shell"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", + "Operating System :: MacOS", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX", "Programming Language :: Python :: 3", @@ -26,6 +27,7 @@ requires-python = ">=3.12" [project.urls] Repository = "https://github.com/techcode-io/proclet" Documentation = "https://github.com/techcode-io/proclet#readme" +Changelog = "https://github.com/techcode-io/proclet/blob/main/CHANGELOG.md" Tracker = "https://github.com/techcode-io/proclet/issues" [dependency-groups] From eb31d6fdf8e38f608341122c051c00e04c1123dc Mon Sep 17 00:00:00 2001 From: Adrien Mannocci Date: Thu, 5 Nov 2026 12:09:14 +0100 Subject: [PATCH 2/4] ci: test on Python 3.14 and 3.15 Signed-off-by: Adrien Mannocci --- .claude/CLAUDE.md | 2 +- .github/workflows/ci.yml | 2 +- CHANGELOG.md | 4 ++++ pyproject.toml | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 9bf30dd..d33ce6c 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -37,7 +37,7 @@ No runtime dependencies - keep it that way. - This is a public repo: describe the library on its own terms. ## CI and release -- `.github/workflows/ci.yml`: push to `main` + PRs, matrix ubuntu/macos/windows x Python 3.12/3.13, +- `.github/workflows/ci.yml`: push to `main` + PRs, matrix ubuntu/macos/windows x Python 3.12 to 3.15, `uv sync --locked`, `uv run poe ci`, `uv build`. Keep `uv.lock` committed and in sync. - Actions are pinned by commit SHA with the version in a trailing comment (`uses: actions/checkout@ # v7.0.1`); bump both together. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b0de78e..4383528 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python: ["3.12", "3.13"] + python: ["3.12", "3.13", "3.14", "3.15"] steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: techcode-io/github-actions/setup-uv@f1054ff1cbac66b5acdc7c4d0d1d38f18a3a6030 # setup-uv/v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b61c30..a330c5f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Support for Python 3.14 and 3.15. + ## [0.1.0] - 2026-11-03 First public release. diff --git a/pyproject.toml b/pyproject.toml index fa6bebd..e13f60d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,8 @@ classifiers = [ "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Topic :: System :: Shells", "Typing :: Typed", ] From 884a692c6b637dc1d2937b9425f2a7d46242dbd2 Mon Sep 17 00:00:00 2001 From: Adrien Mannocci Date: Thu, 5 Nov 2026 12:09:30 +0100 Subject: [PATCH 3/4] docs: replace the contributors badge with a no-dependencies one Signed-off-by: Adrien Mannocci --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b2ec1c9..8a4b20e 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ continuous integration - - contributors + + no runtime dependencies license From 6b00f7526b3dd206da71cb88ea2058ed9de86a0e Mon Sep 17 00:00:00 2001 From: Adrien Mannocci Date: Thu, 5 Nov 2026 12:10:36 +0100 Subject: [PATCH 4/4] ci: create a GitHub release from the changelog when releasing Signed-off-by: Adrien Mannocci --- .claude/CLAUDE.md | 3 +- .github/workflows/release.yml | 13 +++++++ pyproject.toml | 4 +++ scripts/release.py | 68 +++++++++++++++++++++++++++++++---- 4 files changed, 80 insertions(+), 8 deletions(-) diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index d33ce6c..eed8125 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -52,7 +52,8 @@ No runtime dependencies - keep it that way. `workflow_call`, its ubuntu/3.12 job uploads the `dist` artifact), `release` tags the project version and pushes the tag (refuses a dev version), publishes `dist` via PyPI trusted publishing (OIDC, GitHub environment `pypi`, no API token; the job must stay - in `release.yml`, the publisher is bound to that file), then `release:post` opens a PR + in `release.yml`, the publisher is bound to that file), then `release:github` creates + the GitHub release from the version's changelog entries and `release:post` opens a PR from `feat/post-release-vX.Y.Z-dev` setting the next version. Merge it too. - `pyproject.toml`'s `version` is `X.Y.Z.dev0` between releases (`uv version` normalizes `-dev`). diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed4579a..f357d1d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,6 +45,19 @@ jobs: path: dist/ - uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3 + github-release: + needs: publish + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: techcode-io/github-actions/setup-uv@f1054ff1cbac66b5acdc7c4d0d1d38f18a3a6030 # setup-uv/v1 + - run: uv sync --locked + - run: uv run poe release:github + env: + GH_TOKEN: ${{ github.token }} + post-release: needs: publish runs-on: ubuntu-latest diff --git a/pyproject.toml b/pyproject.toml index e13f60d..9ee2ff8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,6 +71,10 @@ script = "scripts.release:run" help = "Create a PR with changes for release." script = "scripts.release:pre" +[tool.poe.tasks."release:github"] +help = "Create the GitHub release of the project version." +script = "scripts.release:github" + [tool.poe.tasks."release:post"] help = "Create a PR preparing the next iteration." script = "scripts.release:post" diff --git a/scripts/release.py b/scripts/release.py index dd3f4e1..7b1afba 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -1,8 +1,9 @@ """Release proclet, one task per step of the release workflows. `pre` opens a pull request stamping the changelog and setting the release version, -`run` tags the version on `main`, and `post` opens a pull request setting the next -iteration version. Publishing to PyPI is left to the `release` workflow. +`run` tags the version on `main`, `github` creates its GitHub release, and `post` opens +a pull request setting the next iteration version. Publishing to PyPI is left to the +`release` workflow. """ import datetime @@ -57,6 +58,22 @@ def _project_version() -> str: return out.getvalue().strip() +def _release_version() -> str: + """Read the project version, which must be a release version. + + Returns: + The version. + Raises: + SystemExit: if the project version isn't a release version, which means the + pull request opened by `pre` isn't merged. + ErrorReturnCode: if uv fails. + """ + version = _project_version() + if not re.fullmatch(r"\d+\.\d+\.\d+", version): + sys.exit(f"`{version}` isn't a release version: run `pre` first") + return version + + def _version_from_env(name: str, pattern: str) -> str: """Read a version from the environment. @@ -131,6 +148,26 @@ def _stamp_changelog(version: str) -> None: CHANGELOG_PATH.write_text(data) +def _release_notes(version: str) -> str: + """Read the changelog entries of `version`. + + Args: + version: the released version. + Returns: + The entries, without the section's heading. + Raises: + SystemExit: if the changelog has no entry for `version`. + """ + section = re.search( + rf"^## \[{re.escape(version)}\][^\n]*\n(.*?)(?=^## \[|^\[)", + CHANGELOG_PATH.read_text(), + re.MULTILINE | re.DOTALL, + ) + if section is None or not section.group(1).strip(): + sys.exit(f"CHANGELOG.md has no entry under `## [{version}]`") + return section.group(1).strip() + "\n" + + def pre() -> None: """Open a pull request preparing the release of `RELEASE_VERSION`. @@ -161,18 +198,35 @@ def run() -> None: """Tag the project version and push the tag to `origin`. Raises: - SystemExit: if the project version isn't a release version, which means the - pull request opened by `pre` isn't merged. + SystemExit: if the project version isn't a release version. ErrorReturnCode: if git or uv fails, including when the tag already exists. """ - release_version = _project_version() - if not re.fullmatch(r"\d+\.\d+\.\d+", release_version): - sys.exit(f"`{release_version}` isn't a release version: run `pre` first") + release_version = _release_version() _git("tag", release_version) _git("push", "origin", release_version) +def github() -> None: + """Create the GitHub release of the project version from its changelog entries. + + Raises: + SystemExit: if the project version isn't a release version or has no + changelog entry. + ErrorReturnCode: if uv or gh fails, including when the tag doesn't exist. + """ + release_version = _release_version() + + Command("gh").args( + "release", + "create", + release_version, + "--verify-tag", + f"--title={release_version}", + "--notes-file=-", + ).cwd(ROOT_PATH).stdin(_release_notes(release_version)).inherit_out().exec() + + def post() -> None: """Open a pull request preparing the iteration of `NEXT_VERSION`.