Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
changelog:
exclude:
authors:
- dependabot
- dependabot[bot]
8 changes: 2 additions & 6 deletions .github/workflows/release-testpypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up uv
uses: astral-sh/setup-uv@v5
Expand All @@ -18,12 +20,6 @@ jobs:
- name: Set up Python
run: uv python install 3.12

- name: Show package version
run: |
VERSION="$(uv run python -c 'import tomllib; print(tomllib.loads(open("pyproject.toml","rb").read().decode())["project"]["version"])')"
echo "Publishing version: ${VERSION}"
echo "::notice title=TestPyPI version::${VERSION}"

- name: Build
run: uv build

Expand Down
26 changes: 15 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,21 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Validate release tag
run: |
tag="${GITHUB_REF_NAME}"
if ! printf '%s' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "::error::Tag $tag is not in vMAJOR.MINOR.PATCH form"
exit 1
fi
newest="$(git tag --list 'v*.*.*' --sort=-v:refname | head -n1)"
if [ "$tag" != "$newest" ]; then
echo "::error::Tag $tag is not the newest release tag ($newest); refusing to publish an out-of-order version"
exit 1
fi

- name: Set up uv
uses: astral-sh/setup-uv@v5
Expand All @@ -20,17 +35,6 @@ jobs:
- name: Set up Python
run: uv python install 3.12

- name: Verify tag matches package version
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
PKG_VERSION="$(uv run python -c 'import tomllib,sys; print(tomllib.loads(open("pyproject.toml","rb").read().decode())["project"]["version"])')"
echo "Tag version: ${TAG_VERSION}"
echo "Package version: ${PKG_VERSION}"
if [ "${TAG_VERSION}" != "${PKG_VERSION}" ]; then
echo "::error::Tag ${GITHUB_REF_NAME} does not match pyproject.toml version ${PKG_VERSION}"
exit 1
fi

- name: Build
run: uv build

Expand Down
82 changes: 0 additions & 82 deletions CHANGELOG.md

This file was deleted.

49 changes: 34 additions & 15 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ File an issue on the
[GitHub issue tracker](https://github.com/datamasque/datamasque-cli/issues).
Please include:

- the version of `datamasque-cli` you're using (`dm --version` or `pip show datamasque-cli`);
- the version of `datamasque-cli` you're using (`dm version` or `pip show datamasque-cli`);
- the Python version and operating system;
- the command you ran (with credentials and other sensitive arguments redacted);
- the full output, including any traceback.
Expand Down Expand Up @@ -48,8 +48,8 @@ so the `dm` entry point on the venv reflects your working tree —
no reinstall after each edit.

```console
uv run dm --version # one-shot, no venv activation needed
source .venv/bin/activate && dm --version # or activate once per shell
uv run dm version # one-shot, no venv activation needed
source .venv/bin/activate && dm version # or activate once per shell
```

Point it at a DataMasque instance.
Expand Down Expand Up @@ -217,22 +217,41 @@ and so on.

## Releasing

Releases are published automatically by CI when a version tag is pushed.
A release is a Git tag.
The version comes from the tag rather than a file,
so there is nothing to bump by hand and nothing is committed to `main`.
The notes are generated by GitHub from the pull requests merged since the last
release, so there is no changelog to maintain either.

Tags are semver, `v`-prefixed: `vMAJOR.MINOR.PATCH`, for example `v1.4.1`.

To cut a release, pick the change level:

```console
make release-patch # 0.1.0 → 0.1.1 — bug fixes
make release-minor # 0.1.0 → 0.2.0 — new features
make release-major # 0.1.0 → 1.0.0 — breaking changes
make release-patch # bug fixes only
make release-minor # backwards-compatible features
make release-major # breaking changes
```

Each target runs `make check`,
bumps the version in `pyproject.toml`,
refreshes `uv.lock`,
commits, tags, and pushes.
CI handles the publish to PyPI.

To smoke-test a release against TestPyPI without tagging,
trigger the `Release (TestPyPI)` workflow manually from the GitHub Actions tab.
Each reads the latest tag,
works out the next version,
asks you to confirm,
and creates the release.
Creating the tag is what triggers CI to build and publish to PyPI,
so give your pull requests clear titles
for the generated notes to read well.

The same thing by hand is `gh release create v1.4.1 --generate-notes`,
or the GitHub Releases UI.
A published version is immutable:
PyPI will not let you reuse a number,
so a wrong tag means moving on to the next one.

To exercise a build without releasing,
run the `Release (TestPyPI)` workflow from the **Actions** tab.
Between tags the version is a `.devN` pre-release,
which is also how an unreleased change can be published
for others to install and test.

## Toolchain

Expand Down
34 changes: 6 additions & 28 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,11 @@ format-check:
build:
uv build

# Bump version, commit, tag, push — CI publishes automatically.
# Usage: make release-patch (0.1.0 → 0.1.1)
# make release-minor (0.1.0 → 0.2.0)
# make release-major (0.1.0 → 1.0.0)
release-patch: check
$(eval VERSION := $(shell python3 scripts/bump_version.py patch))
uv lock
git add pyproject.toml uv.lock
git commit -m "Release v$(VERSION)"
git tag "v$(VERSION)"
git push && git push --tags
@echo "Released v$(VERSION) — CI will publish to PyPI (https://pypi.org/p/datamasque-cli)"
release-patch:
@sh scripts/release.sh patch

release-minor: check
$(eval VERSION := $(shell python3 scripts/bump_version.py minor))
uv lock
git add pyproject.toml uv.lock
git commit -m "Release v$(VERSION)"
git tag "v$(VERSION)"
git push && git push --tags
@echo "Released v$(VERSION) — CI will publish to PyPI (https://pypi.org/p/datamasque-cli)"
release-minor:
@sh scripts/release.sh minor

release-major: check
$(eval VERSION := $(shell python3 scripts/bump_version.py major))
uv lock
git add pyproject.toml uv.lock
git commit -m "Release v$(VERSION)"
git tag "v$(VERSION)"
git push && git push --tags
@echo "Released v$(VERSION) — CI will publish to PyPI (https://pypi.org/p/datamasque-cli)"
release-major:
@sh scripts/release.sh major
10 changes: 8 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "datamasque-cli"
version = "1.4.0"
dynamic = ["version"]
description = "Official command-line interface for the DataMasque data-masking platform."
authors = [
{ name = "DataMasque Ltd" },
Expand Down Expand Up @@ -127,8 +127,14 @@ markers = [
addopts = "-m 'not integration'"

[build-system]
requires = ["hatchling"]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "vcs"

[tool.hatch.version.raw-options]
local_scheme = "no-local-version"

[tool.hatch.build.targets.wheel]
packages = ["src/datamasque_cli"]
40 changes: 0 additions & 40 deletions scripts/bump_version.py

This file was deleted.

27 changes: 27 additions & 0 deletions scripts/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env sh
# Cut a release by creating a tag only:
# the version is derived from the tag, so nothing is committed to main.
set -eu

level=${1:-patch}
git fetch --tags --quiet
latest=$(git describe --tags --abbrev=0 --match 'v*.*.*')

IFS=. read -r major minor patch <<EOF
${latest#v}
EOF

case "$level" in
patch) patch=$((patch + 1)) ;;
minor) minor=$((minor + 1)); patch=0 ;;
major) major=$((major + 1)); minor=0; patch=0 ;;
*) echo "usage: release.sh patch|minor|major" >&2; exit 1 ;;
esac

next="v$major.$minor.$patch"
printf 'Release %s (from %s)? This publishes to PyPI and cannot be undone. [y/N] ' "$next" "$latest"
read -r reply
case "$reply" in
[yY]*) gh release create "$next" --generate-notes --target main ;;
*) echo "Aborted." >&2; exit 1 ;;
esac
1 change: 0 additions & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading