diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0071ae2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# LF in the repository and in the working tree, on every platform. Without this +# git checks out CRLF on Windows while the mixed-line-ending hook rewrites to +# LF, so the two undo each other on every run and every commit warns about it. +* text=auto eol=lf diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a8e00ed --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,59 @@ +name: Release + +# Pushing a tag like v2.0.0 builds the distributions and publishes them as a +# GitHub Release. The tag is the trigger; nothing is released without one. +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + release: + name: Build and publish + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + with: + enable-cache: true + + # A tag can point at any commit, so the version in the artefact and the + # version in the tag are only the same if something checks. + - name: Check the tag matches __version__ + run: | + tag="${GITHUB_REF_NAME#v}" + version=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' src/loggerhelper/__init__.py) + echo "tag=$tag __version__=$version" + if [ "$tag" != "$version" ]; then + echo "::error::tag ${GITHUB_REF_NAME} does not match __version__ ${version} in src/loggerhelper/__init__.py" + exit 1 + fi + + - name: Sync dependencies + run: uv sync --all-extras --dev + + # A tag can also point at a commit that never passed CI. + - name: Run tests + run: uv run pytest tests/ + + - name: Build the distributions + run: uv build + + - name: Check the wheel imports from a clean environment + run: | + uv run --no-project --isolated --with dist/*.whl \ + python -c "import loggerhelper; print(loggerhelper.__version__)" + + - name: Publish the release + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "$GITHUB_REF_NAME" dist/* \ + --title "$GITHUB_REF_NAME" \ + --generate-notes \ + --verify-tag diff --git a/README.md b/README.md index 5dad172..4a26b31 100644 --- a/README.md +++ b/README.md @@ -630,7 +630,44 @@ has nothing else in it: uv run --no-project --isolated --with dist/loggerhelper-2.0.0-py3-none-any.whl python -c "import loggerhelper; print(loggerhelper.__version__)" ``` -Publish: +### Releasing + +Releases are cut by pushing a tag. `.github/workflows/release.yml` does the rest: +it checks the tag against `__version__`, runs the tests, builds, verifies the +wheel imports from a clean environment, and publishes a GitHub Release with both +distributions attached. + +```bash +# 1. bump __version__ in src/loggerhelper/__init__.py, commit, merge to main +# 2. tag the commit on main and push the tag +git tag -a v2.0.0 -m "v2.0.0" +git push origin v2.0.0 +``` + +The tag must be `v` + exactly what `__version__` says, or the workflow stops +before building — a tag can point at any commit, so nothing else guarantees the +version in the artefact matches the version in the tag. + +The release page then carries: + +| Asset | What it is | +| -------------------------------------- | -------------------------------------- | +| `loggerhelper-2.0.0-py3-none-any.whl` | The wheel — this is what you install | +| `loggerhelper-2.0.0.tar.gz` | The sdist | +| Source code (zip) / (tar.gz) | Added by GitHub automatically; a snapshot of the repo, **not** a built package | + +Only the first two are built artefacts. The two GitHub adds are archives of the +repository at that tag, and installing from them means building from source. + +Install a released wheel straight from GitHub: + +```bash +pip install https://github.com/Marcus778899/LoggerPackage/releases/download/v2.0.0/loggerhelper-2.0.0-py3-none-any.whl +``` + +### Publishing to an index + +Not part of the workflow, since it needs a token: ```bash uv publish @@ -644,10 +681,10 @@ directly — this is what `Database_connector_mcp` does: loggerhelper = { path = "lib/loggerhelper-2.0.0-py3-none-any.whl" } ``` -Before tagging a release, bump `__version__` in `src/loggerhelper/__init__.py`. -That is the only place it is written — `pyproject.toml` declares the version -`dynamic` and reads it from there, so the built artefact and -`loggerhelper.__version__` cannot disagree. +`__version__` in `src/loggerhelper/__init__.py` is the only place the version is +written — `pyproject.toml` declares it `dynamic` and reads it from there, so the +built artefact and `loggerhelper.__version__` cannot disagree, and the release +workflow checks the tag against it. ---