From c5089880d726de801109ca44029ae62628ca5676 Mon Sep 17 00:00:00 2001 From: Alexey Shalaev <75322386+AlexeyShalaev@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:45:41 +0400 Subject: [PATCH] feat: renders record their answers, so copier update can reach them There was no answers file template under template/, so Copier wrote none: a render recorded neither its answers nor the template commit it came from, copier update refused to run, and every template fix had to be copied into each library by hand. template/{{_copier_conf.answers_file}}.jinja makes every render carry _src_path, _commit and the answers. The post-generation message now runs on a copy only, since copier update runs tasks too. CI gets an update job: a render of master, updated to the commit under test, has to come out exactly as a fresh render of it, so a change that does not travel through copier update fails its own pull request. The README says how to update a library and how to backfill the file in one rendered before it existed. Closes #14 --- .github/workflows/ci.yml | 41 +++++++++++++++++++- README.md | 18 +++++++++ copier.yml | 23 ++++++----- template/{{_copier_conf.answers_file}}.jinja | 2 + 4 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 template/{{_copier_conf.answers_file}}.jinja diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8748ac..0aba258 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,10 +67,49 @@ jobs: uv run ruff check tests/unit/test_probe.py rm demo_lib/probe.py tests/unit/test_probe.py + # A library takes template changes with `copier update`, so a change has to + # arrive that way too: a render of master, updated to this commit, has to + # come out exactly as a fresh render of this commit. + update: + name: copier update brings the change into a render of master + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: astral-sh/setup-uv@v7 + + - name: Update a render of master to this commit + run: | + git fetch --no-tags origin master:refs/heads/ci-base + git branch -f ci-head HEAD + if ! git cat-file -e 'ci-base:template/{{_copier_conf.answers_file}}.jinja' 2>/dev/null; then + echo "A render of master has no answers file to update from." + exit 0 + fi + render() { + uvx --from copier==9.19.1 --with jinja2-time==0.2.0 copier copy --quiet --defaults --trust --vcs-ref "$1" \ + --data project_name=demo-lib \ + --data project_slug=demo-lib \ + --data package_name=demo_lib \ + --data project_description="Demo library rendered from the template" \ + --data author_name="Bedrock Python" \ + --data author_email="maintainers@example.com" \ + "$GITHUB_WORKSPACE" "$2" + } + render ci-base "$RUNNER_TEMP/updated" + render ci-head "$RUNNER_TEMP/fresh" + cd "$RUNNER_TEMP/updated" + git init -q -b master + git add -A + git -c user.name=ci -c user.email=ci@example.com commit -q -m "render of master" + uvx --from copier==9.19.1 --with jinja2-time==0.2.0 copier update --quiet --defaults --trust --vcs-ref ci-head + diff -r --exclude=.git "$RUNNER_TEMP/fresh" "$RUNNER_TEMP/updated" + all-checks-passed: name: All checks passed if: always() - needs: [render] + needs: [render, update] runs-on: ubuntu-latest steps: - name: Every job above succeeded diff --git a/README.md b/README.md index 641ad7b..e1264b7 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,24 @@ Then: 4. Set up PyPI Trusted Publisher at pypi.org/manage/account/publishing/ 5. Add `CODECOV_TOKEN` secret in repo Settings → Secrets → Actions +## Pulling template changes into a library + +A render records its answers and the template commit it came from in +`.copier-answers.yml`. Commit that file and leave it to Copier. To bring a library up to +the template's `master`, from the library's clean working tree: + +```bash +uvx --with jinja2-time copier update --trust --defaults +``` + +Copier applies what changed in the template since the recorded commit as a three-way +merge with the library's own edits, leaves conflicts as inline markers, and moves +`_commit` forward. A file the library deleted, such as the checklist, stays deleted. + +A library rendered before the answers file existed needs one written by hand first: the +answers it was rendered with, `_src_path: gh:bedrock-python/python-library-template`, +and as `_commit` the template commit it was rendered from, or the closest one. + ## For AI Agents See [`.claude/LIBRARY_CREATION.md`](.claude/LIBRARY_CREATION.md) for important rules: diff --git a/copier.yml b/copier.yml index 7cc451b..95500bd 100644 --- a/copier.yml +++ b/copier.yml @@ -45,13 +45,16 @@ _jinja_extensions: - jinja2_time.TimeExtension _tasks: - - >- - echo "Template generated. Next steps: - 1. uv sync --group dev - 2. uv run pre-commit install --hook-type commit-msg - 3. Create the GitHub repo and push (branch: master) - 4. After the first CI pass, from the template repo: - python scripts/setup_repo.py {{ github_org }}/{{ project_slug }} - 5. Configure the PyPI Trusted Publisher at pypi.org - 6. Add the CODECOV_TOKEN secret. - See NEW_LIBRARY_CHECKLIST.md for full details." + # On a copy only: `copier update` runs tasks too, and a library taking a + # template update finished these steps long ago. + - command: >- + echo "Template generated. Next steps: + 1. uv sync --group dev + 2. uv run pre-commit install --hook-type commit-msg + 3. Create the GitHub repo and push (branch: master) + 4. After the first CI pass, from the template repo: + python scripts/setup_repo.py {{ github_org }}/{{ project_slug }} + 5. Configure the PyPI Trusted Publisher at pypi.org + 6. Add the CODECOV_TOKEN secret. + See NEW_LIBRARY_CHECKLIST.md for full details." + when: "{{ _copier_operation == 'copy' }}" diff --git a/template/{{_copier_conf.answers_file}}.jinja b/template/{{_copier_conf.answers_file}}.jinja new file mode 100644 index 0000000..69141bb --- /dev/null +++ b/template/{{_copier_conf.answers_file}}.jinja @@ -0,0 +1,2 @@ +# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY +{{ _copier_answers|to_nice_yaml -}}