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
9 changes: 9 additions & 0 deletions .github/dependabot.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,12 @@ updates:
- "*"
cooldown:
default-days: 7
- package-ecosystem: "pre-commit"
directory: "/"
labels:
- dependencies
- skip news
schedule:
interval: "weekly"
cooldown:
default-days: 7
38 changes: 38 additions & 0 deletions .github/workflows/codeql.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: 🛡️ CodeQL
on:
push:
branches: [main]
pull_request:
schedule:
- cron: "0 9 * * 2"
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions: {}
jobs:
analyze:
name: 🛡️ Analyze ${{ matrix.language }}
runs-on: ubuntu-26.04
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [actions]
steps:
- name: 📥 Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: 🛡️ Initialize CodeQL
uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
with:
languages: ${{ matrix.language }}
build-mode: none
queries: security-extended
- name: 🛡️ Analyze
uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0
with:
category: "/language:${{ matrix.language }}"
101 changes: 46 additions & 55 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,75 +1,66 @@
name: CI

name: 🧪 CI
on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]
types: [opened, synchronize, reopened]
push:
branches: [main, test-me-*]

permissions:
contents: read

jobs:
self-test-uvx:
name: 🧪 Self-test with uvx
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: self test action (uvx path)
uses: $/
- name: 📥 Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: 🧪 Self test action (uvx path)
uses: $/
self-test-uv-run:
name: 🧪 Self-test with uv run
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: printf '[project]\nname = "test"\nversion = "0.0.0"\nrequires-python = ">=3.13"\n' > pyproject.toml
shell: bash
- name: self test action (uv run path)
uses: $/
- name: 📥 Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: 🐍 Install uv
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
with:
version: latest-known
python-version: "3.13"
enable-cache: false
- name: ⚙️ Write a test pyproject.toml
run: printf '[project]\nname = "test"\nversion = "0.0.0"\nrequires-python = ">=3.13,<3.14"\n' >pyproject.toml
shell: bash
- name: 🧪 Self test action (uv run path)
uses: $/
self-test-external-uv:
name: 🧪 Self-test with external uv
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
python-version: "3.15"
enable-cache: false
- name: self test action (external uv)
uses: $/
with:
uv-install: 'false'
news:
name: News fragment
if: github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'skip news')
runs-on: ubuntu-26.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Require a user-facing change
run: |
found=0
while IFS= read -r path; do
if [[ "$path" =~ ^changelog\.d/[0-9]+\.(feature|bugfix|doc)\.md$ && -s "$path" ]]; then
found=1
fi
done < <(git diff --name-only --diff-filter=AM "origin/$GITHUB_BASE_REF"...HEAD)
if (( found == 0 )); then
echo "::error::add a nonempty changelog.d/<PR>.<type>.md fragment or apply skip news"
exit 1
fi
- name: 📥 Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: 🐍 Install uv
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
with:
version: latest-known
python-version: "3.15"
enable-cache: false
- name: 🧪 Self test action (external uv)
uses: $/
with:
uv-install: 'false'
gate:
name: Required checks pass
name: 🚦 ${{ github.event_name == 'pull_request' && 'Required checks pass' || 'Non-PR checks pass' }}
if: always()
needs: [self-test-uvx, self-test-uv-run, self-test-external-uv, news]
needs: [self-test-uvx, self-test-uv-run, self-test-external-uv]
runs-on: ubuntu-26.04
steps:
- env:
NEEDS_JSON: ${{ toJSON(needs) }}
run: |
jq -e 'all(to_entries[]; .value.result == "success" or (.key == "news" and .value.result == "skipped"))' <<< "$NEEDS_JSON"
- name: 🚦 Require all needed jobs to pass
env:
NEEDS_JSON: ${{ toJSON(needs) }}
run: jq -e 'all(to_entries[]; .value.result == "success")' <<<"$NEEDS_JSON"
34 changes: 34 additions & 0 deletions .github/workflows/news.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: 📝 Changelog
on:
pull_request:
types: [opened, reopened, synchronize, labeled, unlabeled]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
jobs:
news:
name: 📝 News fragment
runs-on: ubuntu-26.04
permissions:
contents: read
steps:
- name: 📥 Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: 🐍 Install uv
uses: astral-sh/setup-uv@07a047e608db84b3d8c06cd9a7c7dcc9c355c18e # v10.3.2
with:
version: latest-known
python-version: "3.15"
enable-cache: false
- name: 📦 Install locked release tools
uses: tox-dev/github-workflows/install-locked-tools@259a3d3fb578d00517ffb73341fdd463259b0b2f # 6.0.1
with:
groups: release-news
venv: ${{ runner.temp }}/release-tools
- name: 🔍 Validate news fragments
uses: tox-dev/github-workflows/validate-news@259a3d3fb578d00517ffb73341fdd463259b0b2f # 6.0.1
94 changes: 20 additions & 74 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
@@ -1,83 +1,29 @@
name: Prepare release

name: 🏷️ Prepare release
on:
workflow_dispatch:
inputs:
version:
description: Version to release, without a v prefix
bump:
description: Version bump type
required: true

type: choice
options:
- auto
- major
- minor
- patch
default: auto
permissions: {}

concurrency:
group: prepare-release
cancel-in-progress: false

jobs:
prepare:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-26.04
environment: release-auth
name: 🏷️ Prepare Towncrier release
uses: tox-dev/github-workflows/.github/workflows/prepare-towncrier-release.yaml@259a3d3fb578d00517ffb73341fdd463259b0b2f # 6.0.1
permissions:
contents: read
steps:
- id: token
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
with:
client-id: ${{ vars.RELEASE_APP_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: ${{ github.event.repository.name }}
permission-contents: write
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
token: ${{ steps.token.outputs.token }}
persist-credentials: true # zizmor: ignore[artipacked] the release job pushes main and its tag
- name: Build changelog and push release
env:
VERSION: ${{ inputs.version }}
GH_TOKEN: ${{ steps.token.outputs.token }}
APP_SLUG: ${{ steps.token.outputs.app-slug }}
run: |
set -euo pipefail
git fetch origin main
git reset --hard origin/main
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::expected a major.minor.patch version"
exit 1
fi
tag="v$VERSION"
latest=$(git tag --list 'v*' | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -n 1)
if [[ "$latest" == "$tag" || "$(printf '%s\n' "$latest" "$tag" | sort -V | tail -n 1)" != "$tag" ]]; then
echo "::error::$tag must be newer than $latest"
exit 1
fi
mapfile -d '' fragments < <(find changelog.d -maxdepth 1 -type f -name '*.md' -print0 | sort -z)
if (( ${#fragments[@]} == 0 )); then
echo "::error::no news fragments to release"
exit 1
fi
for fragment in "${fragments[@]}"; do
if [[ ! "$fragment" =~ ^changelog\.d/[0-9]+\.(feature|bugfix|doc)\.md$ || ! -s "$fragment" ]]; then
echo "::error::invalid or empty news fragment: $fragment"
exit 1
fi
done
{
printf '# Changelog\n\n## %s (%s)\n\n' "$tag" "$(date -u +%F)"
for fragment in "${fragments[@]}"; do
cat "$fragment"
printf '\n\n'
done
tail -n +3 CHANGELOG.md
} > "$RUNNER_TEMP/changelog.md"
mv "$RUNNER_TEMP/changelog.md" CHANGELOG.md
git rm -- "${fragments[@]}"
app_user_id=$(gh api "/users/${APP_SLUG}[bot]" --jq .id)
git config user.name "${APP_SLUG}[bot]"
git config user.email "${app_user_id}+${APP_SLUG}[bot]@users.noreply.github.com"
git add CHANGELOG.md changelog.d
git commit -m "Prepare $tag"
git tag -a "$tag" -m "Release $tag"
git push --atomic origin HEAD:main "refs/tags/$tag"
pull-requests: read
with:
bump: ${{ inputs.bump }}
tag-prefix: v
changelog-prefix: v
annotated: true
secrets:
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
Loading