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
7 changes: 7 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<!--
SPDX-FileCopyrightText: 2026 copier-template-python-open-source contributors

SPDX-License-Identifier: MIT
-->

@../AGENTS.md
44 changes: 30 additions & 14 deletions .github/workflows/bump-versions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,18 @@ on:
schedule:
- cron: 0 6 * * *

permissions:
contents: read

jobs:
update:
name: Update ${{ matrix.name }}
runs-on: ubuntu-latest
permissions:
# `create-pull-request` pushes and opens the pull request with `CI_PAT`.
contents: read
# Open an issue when the update fails.
issues: write
strategy:
matrix:
include:
Expand All @@ -22,6 +30,8 @@ jobs:
steps:
- name: Check out branch
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up pixi
uses: prefix-dev/setup-pixi@a09b6247153796b190642a2b53fac4241043cf6f # v0.10.0
with:
Expand All @@ -32,31 +42,37 @@ jobs:
GH_TOKEN: ${{ github.token }}
- uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
commit-message: Auto-update ${{ matrix.name }}
title: Auto-update ${{ matrix.name }}
commit-message: "build: auto-update ${{ matrix.name }}"
title: "build: auto-update ${{ matrix.name }}"
body: |
New versions of pinned dependencies in ${{ matrix.name }} were detected.
This PR updates them to the latest version.
branch: update-${{ matrix.name }}
delete-branch: true
# A pull request opened with `GITHUB_TOKEN` starts no workflow, so it
# would get no checks. `CI_PAT` is a fine-grained token limited to
# this repository.
token: ${{ secrets.CI_PAT }}
- name: Create issue on failure
if: failure()
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
NAME: ${{ matrix.name }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
with:
script: |
github.rest.issues.listForRepo({
const { data: open } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
labels: "[bot] autoupdate"
}).then((issues) => {
if (issues.data.length === 0) {
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: "${{ matrix.name }} update failed",
body: "See https://github.com/quantco/copier-template-python-open-source/actions/runs/${{ github.run_id }} for details.",
labels: ["[bot] autoupdate"]
})
}
labels: "[bot] autoupdate",
});
if (open.length === 0) {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `${process.env.NAME} update failed`,
body: `See ${process.env.RUN_URL} for details.`,
labels: ["[bot] autoupdate"],
});
}
69 changes: 40 additions & 29 deletions .github/workflows/ci-copier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
lint:
name: Lint
Expand All @@ -22,6 +25,8 @@ jobs:
steps:
- name: Check out branch
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up pixi
uses: prefix-dev/setup-pixi@a09b6247153796b190642a2b53fac4241043cf6f # v0.10.0
- name: Run linting
Expand All @@ -35,6 +40,8 @@ jobs:
steps:
- name: Check out branch
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Set up pixi
uses: prefix-dev/setup-pixi@a09b6247153796b190642a2b53fac4241043cf6f # v0.10.0
- name: Test
Expand All @@ -47,15 +54,20 @@ jobs:
name: Test CI of generated package (minimal-python = ${{ matrix.minimal-python-version }})
timeout-minutes: 30
runs-on: ubuntu-latest
permissions:
contents: read
# Read and cancel the generated package's runs. Pushing goes through
# `CI_PAT`, not this token.
actions: write
strategy:
matrix:
minimal-python-version: [py312, py314]
steps:
- name: Check out branch
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
fetch-depth: 0
persist-credentials: false
- name: Set up pixi
uses: prefix-dev/setup-pixi@a09b6247153796b190642a2b53fac4241043cf6f # v0.10.0
with:
Expand All @@ -65,42 +77,43 @@ jobs:
run: |
echo "name=ci/$GITHUB_SHA-${{ matrix.minimal-python-version }}" >> $GITHUB_OUTPUT
- name: Test generated package CI
env:
# Reading and cancelling the generated package's runs.
GH_TOKEN: ${{ github.token }}
# A fine-grained token limited to this repository. `GITHUB_TOKEN`
# can neither push files under `.github/workflows/` nor start a
# workflow by pushing; a push made with `CI_PAT` does both.
CI_PAT: ${{ secrets.CI_PAT }}
BRANCH: ${{ steps.branch.outputs.name }}
MINIMAL_PYTHON_VERSION: ${{ matrix.minimal-python-version }}
run: |
# Name of the generated package.
# Authentication for pushing to $REPO.
AUTH='authorization: Bearer ${{ secrets.GITHUB_TOKEN }}'
eval $(ssh-agent)
ssh-add - <<< "${{ secrets.SSH_PRIVATE_KEY }}"
: "${CI_PAT:?the CI_PAT secret is not set}"
AUTH="authorization: Bearer $GH_TOKEN"
# Set up local Git so that copier can run "git commit".
git config --global user.email "landocalrissian@example.com"
git config --global user.name "Lando Calrissian"
# Generate package with default settings + Windows CI.
copier copy \
--data project_slug="package" \
--data project_short_description="Example Package" \
--data github_user="LandoCalrissian" \
--data author_name="Lando Calrissian" \
--data author_email="lando@calrissian.org" \
--data minimal_python_version="${{ matrix.minimal-python-version }}" \
--data minimal_python_version="$MINIMAL_PYTHON_VERSION" \
--defaults \
--trust \
--vcs-ref HEAD \
. out
cd out
# Replace actions trigger with on: [push]
# The generated CI and build run on pull requests and on `main`;
# here they have to run on a push to the `ci/*` branch.
yq eval '.on = ["push"]' -i .github/workflows/ci.yml
yq eval '.on = ["push"]' -i .github/workflows/build.yml
git init
# create pixi.lock
pixi lock --manifest-path pixi.toml --color=always
git add .
git commit -m "Initial commit"
# Push the generated package's HEAD commit to a `ci/*` branch
cid=$(git rev-parse HEAD)
git push -f "${GITHUB_SERVER_URL/https:\/\//git@}:$GITHUB_REPOSITORY" $cid:refs/heads/${STEPS_BRANCH_OUTPUTS_NAME}
# Use the GitHub API to wait for the generated package's CI to complete (success or failure).
# We look for a GitHub Actions run for the HEAD commit ID.
WORKFLOW_URL="$GITHUB_API_URL/repos/${GITHUB_REPOSITORY}/actions/runs?branch=${STEPS_BRANCH_OUTPUTS_NAME}&head_sha=${cid}"
git push -f "https://x-access-token:$CI_PAT@${GITHUB_SERVER_URL#https://}/$GITHUB_REPOSITORY.git" "$cid:refs/heads/$BRANCH"
# Wait for the generated package's CI to complete (success or failure).
WORKFLOW_URL="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/runs?branch=$BRANCH&head_sha=$cid&event=push"
echo "Waiting for inner CI to start"
while (( $(curl -Ls --header "$AUTH" "$WORKFLOW_URL" | jq -r ".workflow_runs | length") < 1 )); do
sleep 10
Expand All @@ -114,23 +127,21 @@ jobs:
echo "CI pipeline failed"
exit 1
fi
env:
STEPS_BRANCH_OUTPUTS_NAME: ${{ steps.branch.outputs.name }}
- name: Clean up CI branch
if: always()
env:
GH_TOKEN: ${{ github.token }}
CI_PAT: ${{ secrets.CI_PAT }}
BRANCH: ${{ steps.branch.outputs.name }}
run: |
set -x
AUTH='authorization: Bearer ${{ secrets.GITHUB_TOKEN }}'
eval $(ssh-agent)
ssh-add - <<< "${{ secrets.SSH_PRIVATE_KEY }}"

git push -d "${GITHUB_SERVER_URL/https:\/\//git@}:$GITHUB_REPOSITORY" refs/heads/ci/$GITHUB_SHA-${{ matrix.minimal-python-version }}

cid=$(git rev-parse HEAD)
for line in $(curl -Ls --header "$AUTH" "$GITHUB_API_URL/repos/${GITHUB_REPOSITORY}/actions/runs?branch=ci/${GITHUB_SHA}-${{ matrix.minimal-python-version }}&head_sha=${cid}" | jq -r ".workflow_runs | .[] | select(.status != \"completed\") | .id")
AUTH="authorization: Bearer $GH_TOKEN"
# Cancel anything still running before the branch disappears.
for run_id in $(curl -Ls --header "$AUTH" "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/runs?branch=$BRANCH" | jq -r ".workflow_runs | .[] | select(.status != \"completed\") | .id")
do
curl -Ls --header "$AUTH" --request POST "$GITHUB_API_URL/repos/${GITHUB_REPOSITORY}/actions/runs/$line/cancel" > /dev/null
curl -Ls --header "$AUTH" --request POST "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/actions/runs/$run_id/cancel" > /dev/null
done
# Delete the branch. Tolerate it never having been created.
git push -d "https://x-access-token:$CI_PAT@${GITHUB_SERVER_URL#https://}/$GITHUB_REPOSITORY.git" "refs/heads/$BRANCH" || true

finalize:
name: Test CI of generated package (all)
Expand Down
2 changes: 1 addition & 1 deletion .prettierrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ bracketSpacing: true
printWidth: 200
endOfLine: "auto"
tabWidth: 2
proseWrap: "never"
proseWrap: "preserve"
19 changes: 19 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!--
SPDX-FileCopyrightText: 2026 copier-template-python-open-source contributors

SPDX-License-Identifier: MIT
-->

# AGENTS.md

Contribution rules for this copier template, for humans and AI agents alike.
`template/AGENTS.md.jinja` holds the rules for generated projects.
A rule there that also fits this repository applies here too.

## Markdown line breaks

Always write one sentence per line in every Markdown file, including the `*.md.jinja` files under `template/`.
Split a very long sentence at a clause boundary, so that each line holds one clause.
Never wrap lines at a fixed width.
A diff then shows the sentence that changed, not a reflowed paragraph.
Code blocks, tables and headings keep their own form.
12 changes: 10 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ Release headings should be of the form:

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

Expand All @@ -33,12 +34,19 @@ In this release, we have aligned to current practice in https://github.com/energ
- mkdocs theme palette, language, & icon.
- extra socials buttons for GH & pypi.
- Mkdocs v2 warning suppressor in pixi.toml.
- Release on merge, ported from https://github.com/energy-models/mathspec:
a PR that puts a version heading on top of `CHANGELOG.md` tags, releases and publishes that version (`release.yml`, `tools/changelog.py`, `RELEASING.md`).
- `Changelog line` and `Conventional commit subject` PR checks.

### Changed

- Accelerate REUSE check in pre-commit.
- template tests to include only a conftest.py file.
- template tests to include only a conftest.py file and an import smoke test, so that a new project's test suite passes.
- AGENTS.md, to include more verbose instructions.
- **Breaking**: the package version comes from the git tag through hatch-vcs, not from a `sed` over `pyproject.toml`.
- **Breaking**: `CHANGELOG.md` uses one `## Upcoming version` heading above `## X.Y.Z (YYYY-MM-DD)` release headings.
CI refuses any other heading.
- `build.yml` no longer publishes on a tag push; `release.yml` calls it.

## v1.0.0 (2026.08.17)

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ To update to a newer template version:
pixi exec --spec copier -- copier update --defaults --trust .
```

Note that copier will show `Conflict` for files that have manual changes. This is normal. As long as there are no merge conflict markers in the files all patches applied cleanly.
Note that copier will show `Conflict` for files that have manual changes.
This is normal.
As long as there are no merge conflict markers in the files all patches applied cleanly.

If you want to change any answer that you gave before, run:

Expand Down
7 changes: 5 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ SPDX-License-Identifier: MIT

# Reporting Security Issues

We take security bugs in our projects seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
We take security bugs in our projects seriously.
We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.

To report a security issue, please use the GitHub Security Advisory ["Report a Vulnerability"](https://github.com/Quantco/copier-template-python-open-source/security/advisories/new) tab.

We will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.
We will send a response indicating the next steps in handling your report.
After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement,
and may ask for additional information or guidance.
10 changes: 7 additions & 3 deletions copier.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ project_slug:
validator: >-
{% if '_' in project_slug or ' ' in project_slug or '.' in project_slug %} The project slug should not contain underscores, spaces or dots. {% endif %}


project_slug_snake_case:
type: str
default: "{{ project_slug.replace('-', '_') }}"
Expand All @@ -40,15 +39,13 @@ author_email:
validator: >-
{% if not (author_email | regex_search('^.*@.*$')) %} Invalid email {% endif %}


repository_owner:
type: str
help: What is the GitHub organisation / user of this project, under which the repository will be hosted?
default: "energy-models"
validator: >-
{% if not (repository_owner | regex_search('^[^\\s]+$')) %} Repository owner cannot contain spaces {% endif %}


github_url:
type: str
default: https://github.com/{{ repository_owner }}/{{ project_slug }}
Expand Down Expand Up @@ -91,6 +88,13 @@ add_autobump_workflow:
help: Do you want to add a GitHub Workflow to automatically bump `pixi.lock`?
default: true

_migrations:
- version: v1.1.0
when: "{{ _stage == 'before' }}"
command:
- "{{ _copier_python }}"
- "{{ _copier_conf.src_path }}/migrations/v1_1_0.py"

_tasks:
- "pixi exec --spec git git init"
- "pixi run reuse download --all"
Expand Down
Loading
Loading