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
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ first time is by hand; `copier update` takes over from then on.
```

GitHub Pages then rebuilds the site after every push to the branch.
7. On GitHub: Settings > Actions > General > Workflow permissions: tick "Allow
GitHub Actions to create and approve pull requests", so the monthly Upgrade
lock workflow can open its pull request.

## What you get

Expand All @@ -108,7 +111,7 @@ first time is by hand; `copier update` takes over from then on.
| pre-commit | pre-commit-hooks, rST checks, validate-pyproject, [zizmor](https://docs.zizmor.sh); `uv lock`, ruff and pyrefly run from `.venv`, so `uv.lock` pins their versions |
| Changelog | Keep a Changelog, hand-edited; `just changelog` drafts entries from Conventional Commits ([git-cliff](https://git-cliff.org)) |
| CI | Tests (the chosen operating systems × all supported Pythons), Code Quality, Documentation, Publish (tag push → PyPI trusted publishing with attestations → GitHub Release from CHANGELOG → Zenodo) |
| Upkeep | Dependabot for actions, pre-commit hooks and `uv.lock` (monthly, grouped, 7-day cooldown; never raises minimum versions) |
| Upkeep | Dependabot for actions, pre-commit hooks and `uv.lock` (monthly, grouped, 7-day cooldown; never raises minimum versions); a monthly workflow runs `uv lock --upgrade` and opens a pull request when the lock changes, so the newest release for each supported Python gets tested |
| Project files | AGENTS.md, CONTRIBUTING.md, CHANGELOG.md, CITATION.cff, `.zenodo.json`, issue forms, PR template |

Generated projects score clean on the Scientific Python
Expand Down
5 changes: 4 additions & 1 deletion copier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ _message_after_copy: |
- Settings > Pages > Source: Deploy from a branch, branch gh-pages, folder
/ (root). The Documentation workflow creates the branch on its first run
on main, so this comes after that run.
- Settings > Secrets and variables > Actions: add CODECOV_TOKEN.{% if publish_to_pypi %}
- Settings > Secrets and variables > Actions: add CODECOV_TOKEN.
- Settings > Actions > General > Workflow permissions: tick "Allow GitHub
Actions to create and approve pull requests", so the monthly Upgrade lock
workflow can open its pull request.{% if publish_to_pypi %}
- On PyPI, add a trusted publisher: workflow publish.yml, environment pypi.
Optionally, Settings > Environments > pypi > Required reviewers: you, so
each upload waits for your approval.{% endif %}
Expand Down
93 changes: 93 additions & 0 deletions template/.github/workflows/lock-upgrade.yml.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
name: Upgrade lock

# Once a month, re-lock every dependency to its newest release and open one pull
# request when uv.lock changes (`uv lock --upgrade`). Dependabot's uv updates do
# not do this on their own: a package whose newest release needs a newer Python
# than the oldest one this project supports stays at its old version, so CI would
# test a release that users on a newer Python no longer get. `uv lock --upgrade`
# locks the newest release for each Python (a fork in uv.lock).
#
# Setup, once: Settings > Actions > General > Workflow permissions > tick "Allow
# GitHub Actions to create and approve pull requests".
#
# A pull request opened with the workflow's own token does not start the other
# workflows, so this one starts Tests, Code Quality and Documentation on the
# branch itself (workflow_dispatch is the one event the token may trigger); their
# results show on the pull request.
on:
schedule:
- cron: "0 6 1 * *" # 06:00 UTC on the 1st of each month
workflow_dispatch:

permissions:
contents: read

concurrency:
group: lock-upgrade
cancel-in-progress: false

env:
BRANCH: chore/lock-upgrade

jobs:
upgrade:
runs-on: ubuntu-latest
permissions:
contents: write # push the branch
pull-requests: write # open or update the pull request
actions: write # start the other workflows on the branch

steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
fetch-depth: 0 # setuptools-scm reads the version from tags

- uses: astral-sh/setup-uv@v10.2.0
with:
version: "{{ uv_version }}"
python-version: "{{ python_max }}"

- name: Upgrade uv.lock
id: upgrade
run: |
set -euo pipefail
uv lock --upgrade 2>&1 | tee "$RUNNER_TEMP/lock.log"
if git diff --quiet -- uv.lock; then
echo "uv.lock is already up to date."
exit 0
fi
{
echo "Monthly \`uv lock --upgrade\`: the newest release of every dependency, for each supported Python."
echo
echo '```'
grep -E '^(Added|Removed|Updated) ' "$RUNNER_TEMP/lock.log" || true
echo '```'
echo
echo "Tests, Code Quality and Documentation were started on this branch by the workflow run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
echo "Merge it when they pass."
} > "$RUNNER_TEMP/body.md"
echo "changed=true" >> "$GITHUB_OUTPUT"

- name: Push the branch, open or update the pull request, start the checks
if: {% raw %}steps.upgrade.outputs.changed == 'true'{% endraw %}
env:
GH_TOKEN: {% raw %}${{ github.token }}{% endraw %}
run: |
set -euo pipefail
gh auth setup-git
git checkout --quiet -b "$BRANCH"
git add uv.lock
git -c user.name="github-actions[bot]" -c user.email="41898282+github-actions[bot]@users.noreply.github.com" \
commit --quiet -m "chore(deps): upgrade uv.lock"
git push --quiet --force origin "$BRANCH" # the branch belongs to this workflow
number="$(gh pr list --head "$BRANCH" --state open --json number --jq '.[0].number // empty')"
if [ -n "$number" ]; then
gh pr edit "$number" --body-file "$RUNNER_TEMP/body.md"
else
gh pr create --base main --head "$BRANCH" --title "chore(deps): upgrade uv.lock" \
--body-file "$RUNNER_TEMP/body.md"
fi
for workflow in tests.yml quality.yml docs.yml; do
gh workflow run "$workflow" --ref "$BRANCH"
done
1 change: 1 addition & 0 deletions template/.github/workflows/quality.yml.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read
Expand Down
10 changes: 10 additions & 0 deletions template/CONTRIBUTING.md.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ Dependabot opens one pull request a month per kind of pin: GitHub Actions,
pre-commit hooks, and `uv.lock` (the dev tools; it never raises the minimum
versions in `pyproject.toml`). Merge it when CI passes.

Dependabot leaves a package at its old version when the newest release needs a
newer Python than the oldest one this project supports. So on the 1st of each
month the Upgrade lock workflow runs `uv lock --upgrade` (`uv.lock` then holds
the newest release for each Python) and, when the lock changes, opens the pull
request `chore/lock-upgrade` (or updates it). GitHub does not start workflows on
a pull request that its own token opened, so the workflow starts Tests, Code
Quality and Documentation on the branch itself; their results show on the pull
request. Merge it when they pass. You can also run it by hand: Actions > Upgrade
lock > Run workflow.

## Template

The tooling (CI workflows, pre-commit, justfile, packaging configuration) comes
Expand Down
Loading