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: 3 additions & 1 deletion .cursor/rules/diataxis-docs.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,6 @@ This repository's docs follow [Diátaxis](https://diataxis.fr/). Keep them consi

## Published site

Configured by `mkdocs.yml` (Material). GitHub Pages workflow: `.github/workflows/docs.yml`.
Configured by `mkdocs.yml` (Material + mike version picker). Changelog: `docs/release-notes.md`, which keeps an `## Unreleased` section maintained per PR (renamed to the version at release time by `scripts/prepare_release.py`).

GitHub Pages is the `gh-pages` branch (mike). The Deploy docs workflow builds on docs PRs and deploys a versioned site (from the tagged release commit) only after a successful Publish to PyPI run (`workflow_run`), not on every `master` docs push.
62 changes: 62 additions & 0 deletions .github/workflows/changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Changelog

# Every PR must add a user-facing entry under the "## Unreleased" section of
# docs/release-notes.md. Trivial PRs (CI, refactors, typo fixes) can bypass this
# by adding the "skip-changelog" label.

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled]

permissions:
contents: read

concurrency:
group: changelog-${{ github.event.pull_request.number }}
cancel-in-progress: true

jobs:
verify:
runs-on: ubuntu-latest
if: ${{ !contains(github.event.pull_request.labels.*.name, 'skip-changelog') }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}

- name: Require a new Unreleased entry
env:
BASE_REF: ${{ github.base_ref }}
run: |
set -euo pipefail
FILE="docs/release-notes.md"
git fetch origin "$BASE_REF" --depth=1

count_unreleased_bullets() {
# Reads a file on stdin, prints the number of bullet lines that live
# under the first "## Unreleased" heading (until the next "## ").
awk '
/^## / {
in_block = ($0 ~ /^## Unreleased([[:space:]]|$)/) ? 1 : 0
next
}
in_block && /^[[:space:]]*[*-][[:space:]]+/ { n++ }
END { print n + 0 }
'
}

HEAD_BULLETS=$(count_unreleased_bullets < "$FILE")
if git show "origin/${BASE_REF}:${FILE}" > /tmp/base-release-notes.md 2>/dev/null; then
BASE_BULLETS=$(count_unreleased_bullets < /tmp/base-release-notes.md)
else
BASE_BULLETS=0
fi

echo "Unreleased bullets: base=${BASE_BULLETS} head=${HEAD_BULLETS}"

if [ "$HEAD_BULLETS" -le "$BASE_BULLETS" ]; then
echo "::error file=${FILE}::Add a bullet under the '## Unreleased' section in ${FILE}, or label this PR 'skip-changelog'."
exit 1
fi
echo "Changelog entry found."
122 changes: 88 additions & 34 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
name: Deploy docs

# Test on this PR: the build job runs automatically on pull_request.
# Test full Pages deploy before merge: Actions → Deploy docs → Run workflow
# (select this branch). Requires Pages source = GitHub Actions in repo settings.
# PR / path changes: build-only (mkdocs --strict).
# Versioned deploy (mike → gh-pages) runs after a successful "Publish to PyPI"
# workflow, or via workflow_dispatch for manual recovery.
#
# One-time repo setting: Pages source = Deploy from a branch → gh-pages / (root).

on:
push:
branches: [master]
paths:
- "docs/**"
- "docs/assets/**"
- "mkdocs.yml"
- "requirements-docs.txt"
- ".github/workflows/docs.yml"
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- "docs/**"
- "docs/assets/**"
- "mkdocs.yml"
- "requirements-docs.txt"
- ".github/workflows/docs.yml"
workflow_run:
workflows: ["Publish to PyPI"]
types: [completed]
workflow_dispatch:
inputs:
deploy:
description: "Upload artifact and deploy to GitHub Pages"
version:
description: "Docs version to deploy (e.g. 0.7.0, without v prefix)"
required: true
type: string
update_latest:
description: "Also update the latest alias and set it as default"
type: boolean
default: true

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
group: docs-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: false

jobs:
build:
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -60,24 +60,78 @@ jobs:
- name: Build site
run: mkdocs build --strict --clean

- name: Upload Pages artifact
if: >
github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && inputs.deploy)
uses: actions/upload-pages-artifact@v3
with:
path: site

deploy:
if: >
github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && inputs.deploy)
needs: build
(github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success') ||
github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
permissions:
contents: write
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
- name: Checkout the tagged release commit
uses: actions/checkout@v4
with:
# workflow_run: the commit Publish ran on (the tagged release commit).
# workflow_dispatch: the vX.Y.Z tag for the requested version.
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || format('v{0}', inputs.version) }}
fetch-depth: 0

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install docs dependencies
run: pip install -r requirements-docs.txt

- name: Resolve version
id: ver
env:
EVENT_NAME: ${{ github.event_name }}
DISPATCH_VERSION: ${{ inputs.version }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }}
UPDATE_LATEST_INPUT: ${{ inputs.update_latest }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
VERSION="${DISPATCH_VERSION#v}"
UPDATE_LATEST="${UPDATE_LATEST_INPUT:-true}"
else
git fetch --tags origin
VERSION=""
if [[ "${HEAD_BRANCH:-}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
VERSION="${HEAD_BRANCH#v}"
elif [ -n "${HEAD_SHA:-}" ]; then
TAG=$(git tag --points-at "$HEAD_SHA" | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1 || true)
if [ -n "$TAG" ]; then
VERSION="${TAG#v}"
fi
fi
if [ -z "$VERSION" ]; then
echo "Could not resolve release version from workflow_run (branch=$HEAD_BRANCH sha=$HEAD_SHA)"
exit 1
fi
UPDATE_LATEST=true
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "update_latest=$UPDATE_LATEST" >> "$GITHUB_OUTPUT"
echo "Deploying docs version $VERSION (update_latest=$UPDATE_LATEST)"

- name: Configure git for mike
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Deploy with mike
env:
VERSION: ${{ steps.ver.outputs.version }}
UPDATE_LATEST: ${{ steps.ver.outputs.update_latest }}
run: |
set -euo pipefail
if [ "$UPDATE_LATEST" = "true" ]; then
mike deploy --push --update-aliases "$VERSION" latest
mike set-default --push latest
else
mike deploy --push "$VERSION"
fi
85 changes: 62 additions & 23 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
name: Publish to PyPI

# Releasing (prep-commit-then-tag; this pipeline only verifies, never mutates)
# ---------------------------------------------------------------------------
# 1. Land PRs on master. Each PR adds its entry under "## Unreleased" in
# docs/release-notes.md (enforced by the Changelog workflow).
# 2. Cut the release locally:
#
# python scripts/prepare_release.py X.Y.Z
# # review the diff, then:
# git add tapsdk/__version__.py docs/release-notes.md
# git commit -m "Release X.Y.Z"
# git tag -a vX.Y.Z -m "Release X.Y.Z"
# git push origin HEAD vX.Y.Z
#
# This workflow re-runs the reusable test matrix on the tagged commit, verifies
# the tag matches tapsdk.__version__ and that release notes were prepared, then
# builds and uploads to PyPI via Trusted Publishing. Versioned docs deploy
# separately in "Deploy docs" after this workflow succeeds.
#
# Maintainers must configure PyPI Trusted Publishing for project tap-python-sdk,
# repository TapWithUs/tap-python-sdk, and a GitHub `pypi` environment.

on:
push:
tags:
Expand All @@ -11,38 +32,22 @@ permissions:

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Lint with flake8
run: |
pip install flake8
flake8 examples tapsdk tests
- name: Run tests
run: pytest -v
uses: ./.github/workflows/tests.yml

publish:
needs: test
runs-on: ubuntu-latest
environment: pypi
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Validate tag version matches package version
run: |
TAG="${GITHUB_REF_NAME#v}"
Expand All @@ -52,28 +57,62 @@ jobs:
exit 1
fi
echo "Version validated: $PACKAGE_VERSION"

- name: Verify release notes were prepared
env:
VERSION: ${{ github.ref_name }}
run: |
set -euo pipefail
FILE="docs/release-notes.md"
VER="${VERSION#v}"

if ! grep -qE "^## ${VER//./\\.} \(" "$FILE"; then
echo "::error file=${FILE}::No '## ${VER} (<date>)' section found. Run scripts/prepare_release.py ${VER} before tagging."
exit 1
fi

# The Unreleased section must be empty (entries moved into the version).
UNRELEASED_BULLETS=$(awk '
/^## / {
in_block = ($0 ~ /^## Unreleased([[:space:]]|$)/) ? 1 : 0
next
}
in_block && /^[[:space:]]*[*-][[:space:]]+/ { n++ }
END { print n + 0 }
' "$FILE")
if [ "$UNRELEASED_BULLETS" -ne 0 ]; then
echo "::error file=${FILE}::'## Unreleased' still has ${UNRELEASED_BULLETS} entrie(s); run scripts/prepare_release.py to move them into ${VER}."
exit 1
fi
echo "Release notes verified for ${VER}."

- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build

- name: Build package
run: python -m build

- name: Check package metadata
run: |
pip install twine
twine check dist/*

# Import package/version only — avoid TapSDK/bleak, which needs bluetoothctl on Linux.
- name: Smoke test built wheel
run: |
python -m venv /tmp/wheel-venv
/tmp/wheel-venv/bin/pip install --upgrade pip
/tmp/wheel-venv/bin/pip install dist/*.whl
/tmp/wheel-venv/bin/python -c "import tapsdk; from tapsdk.__version__ import __version__; print(__version__)"

- name: Smoke test built sdist
run: |
python -m venv /tmp/sdist-venv
/tmp/sdist-venv/bin/pip install --upgrade pip
/tmp/sdist-venv/bin/pip install dist/*.tar.gz
/tmp/sdist-venv/bin/python -c "import tapsdk; from tapsdk.__version__ import __version__; print(__version__)"

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
24 changes: 2 additions & 22 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,5 @@ on:
branches: [master]

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Lint with flake8
run: |
pip install flake8
flake8 examples tapsdk tests
- name: Run tests
run: pytest -v
test:
uses: ./.github/workflows/tests.yml
Loading
Loading