Skip to content

Commit 2c249e9

Browse files
committed
✨ Template | CI: Add opt-in copier-update workflow
Generated projects have no automated way to stay in sync with upstream template changes, leading to silent drift over time. Add a hidden `auto_update` Copier question (`none`/`github-token`/`pat`) that conditionally includes a `copier-update.yaml` workflow. The question is hidden (`when: false`) to keep the interactive prompt minimal — users opt in via `--data` when they want it. The workflow runs weekly (and on manual dispatch), applies `copier update --defaults --force`, checks for an existing open PR to avoid duplicates, and opens a new PR when changes are detected. The `github-token` mode requires zero setup but won't trigger other workflows due to `GITHUB_TOKEN` limitations; the `pat` mode uses a repository secret so that PRs trigger downstream CI normally.
1 parent 855228b commit 2c249e9

File tree

4 files changed

+131
-0
lines changed

4 files changed

+131
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Template for Python packages based on [`copier`](https://copier.readthedocs.io/e
1717
* Deploy documentation to [GitHub Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site) or [Read the Docs](https://about.readthedocs.com/).
1818
* Run pre-commit checks and tests on every pull request.
1919
* Publish to [PyPI](https://pypi.org/) on `vX.Y.Z` tags via [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) — no API tokens.
20+
* Optional automatic template updates via a bot.
2021

2122
## Usage
2223

copier.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,18 @@ coverage:
4040
- codecov
4141
default: none
4242

43+
# github-token: uses the default GITHUB_TOKEN (zero setup, but PRs won't trigger CI).
44+
# pat: uses a PAT stored as COPIER_UPDATE_TOKEN secret (PRs trigger CI normally).
45+
auto_update:
46+
type: str
47+
help: 'How should automatic template updates be configured?'
48+
choices:
49+
- none
50+
- github-token
51+
- pat
52+
default: none
53+
when: false
54+
4355
_subdirectory: template
4456
_message_after_copy: |
4557
The `{{package_name}}` package has been created successfully!

docs/index.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ A `copier`-based template for Python packages.
1515
* Deploy documentation to [GitHub Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/creating-a-github-pages-site) or [Read the Docs](https://about.readthedocs.com/).
1616
* Run pre-commit checks and tests on every pull request.
1717
* Publish to [PyPI](https://pypi.org/) on `vX.Y.Z` tags via [Trusted Publishing](https://docs.pypi.org/trusted-publishers/) — no API tokens.
18+
* Optional automatic template updates via a bot.
1819

1920
## Usage
2021

@@ -46,3 +47,29 @@ You don't have to publish right away, but if you care about the project name, pu
4647
2. Go to [Read the Docs](https://readthedocs.org/) (RTD) and [import your project](https://docs.readthedocs.com/platform/stable/tutorial/index.html#importing-the-project-to-read-the-docs).
4748
The build will use the included `.readthedocs.yaml` automatically.
4849
3. Set the RTD settings to [build from GitHub pull requests](https://docs.readthedocs.com/platform/stable/tutorial/index.html#triggering-builds-from-pull-requests).
50+
51+
### Coverage with Codecov
52+
53+
If you selected `coverage=codecov` when generating, the CI workflow uploads coverage data to [Codecov](https://about.codecov.io/) after every test run.
54+
To get this working:
55+
56+
1. Sign up at [codecov.io](https://codecov.io/) and connect your GitHub account.
57+
2. Add your repository on the Codecov dashboard — it will start receiving uploads automatically.
58+
59+
No repository secret is needed for public repos.
60+
For private repos, add a `CODECOV_TOKEN` secret to your repository and pass it in the workflow's `codecov-action` step.
61+
62+
### Automatic template updates
63+
64+
The template can optionally add a `copier-update.yaml` workflow that checks for upstream template changes every week, runs `copier update`, and opens a PR with the proposed changes.
65+
This feature is controlled by the hidden `auto_update` question (set via `--data auto_update=<value>` when copying or updating):
66+
67+
- `none` (default) — no workflow is added.
68+
- `github-token` — uses the default `GITHUB_TOKEN`. Zero setup, but PRs created this way [won't trigger other CI workflows](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow#triggering-a-workflow-from-a-workflow).
69+
- `pat` — uses a Personal Access Token stored as a `COPIER_UPDATE_TOKEN` repository secret. PRs will trigger CI normally.
70+
71+
To enable it on an existing project, run:
72+
73+
```
74+
copier update --data auto_update=github-token
75+
```
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
name: Copier Update
2+
3+
on:
4+
schedule:
5+
- cron: '0 5 * * 1'
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: write
10+
pull-requests: write
11+
12+
jobs:
13+
copier-update:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
with:
20+
{%- if auto_update == 'pat' %}
21+
token: {% raw %}${{ secrets.COPIER_UPDATE_TOKEN }}{% endraw %}
22+
{%- endif %}
23+
fetch-depth: 0
24+
25+
- name: Install Python
26+
uses: actions/setup-python@v5
27+
with:
28+
python-version: '3.13'
29+
30+
- name: Install copier
31+
run: pip install copier
32+
33+
- name: Update from template
34+
# --trust is required for non-interactive mode; it allows execution of
35+
# any _tasks defined by the upstream template.
36+
run: copier update --defaults --force --trust
37+
38+
- name: Check for changes
39+
id: check
40+
run: |
41+
if [ -n "$(git status --porcelain)" ]; then
42+
echo "changed=true" >> $GITHUB_OUTPUT
43+
else
44+
echo "changed=false" >> $GITHUB_OUTPUT
45+
fi
46+
47+
- name: Check for existing PR
48+
if: {% raw %}${{ steps.check.outputs.changed == 'true' }}{% endraw %}
49+
50+
id: existing-pr
51+
env:
52+
{%- if auto_update == 'pat' %}
53+
GH_TOKEN: {% raw %}${{ secrets.COPIER_UPDATE_TOKEN }}{% endraw %}
54+
{%- else %}
55+
GH_TOKEN: {% raw %}${{ github.token }}{% endraw %}
56+
{%- endif %}
57+
run: |
58+
EXISTING=$(gh pr list --state open --json headRefName,number --jq '[.[] | select(.headRefName | startswith("copier-update/"))][0].number')
59+
if [ -n "$EXISTING" ]; then
60+
echo "exists=true" >> $GITHUB_OUTPUT
61+
echo "::notice::Update PR #$EXISTING already exists, skipping."
62+
else
63+
echo "exists=false" >> $GITHUB_OUTPUT
64+
fi
65+
66+
- name: Create PR
67+
if: {% raw %}${{ steps.check.outputs.changed == 'true' && steps.existing-pr.outputs.exists == 'false' }}{% endraw %}
68+
69+
env:
70+
{%- if auto_update == 'pat' %}
71+
GH_TOKEN: {% raw %}${{ secrets.COPIER_UPDATE_TOKEN }}{% endraw %}
72+
{%- else %}
73+
GH_TOKEN: {% raw %}${{ github.token }}{% endraw %}
74+
{%- endif %}
75+
run: |
76+
BRANCH="copier-update/{% raw %}${{ github.run_id }}{% endraw %}"
77+
git config user.name "copier-update[bot]"
78+
git config user.email "copier-update[bot]@users.noreply.github.com"
79+
git checkout -b "$BRANCH"
80+
git add .
81+
git commit -m "Update from copier template"
82+
git push origin "$BRANCH"
83+
gh pr create \
84+
--title "Update from copier template" \
85+
--body "Automated update from the upstream copier template.
86+
87+
This PR was created by the \`copier-update\` workflow.
88+
Please review the changes carefully before merging.
89+
90+
> If there are conflicts or unexpected changes, you can run
91+
> \`copier update\` locally to resolve them interactively."

0 commit comments

Comments
 (0)