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
4 changes: 4 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
59 changes: 59 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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
47 changes: 42 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.

---

Expand Down
Loading