From bf6484dc1fc0cbd1be9978a893b68ffc015cf1d6 Mon Sep 17 00:00:00 2001 From: MrTango Date: Sun, 24 May 2026 05:45:34 -0700 Subject: [PATCH 1/7] Fix non-interactive composite create/setup: overwrite layered files The 'addon' composite applies zope-setup on top of backend_addon, and 'setup' layers zope-setup onto an existing addon. copier raised an interactive-conflict error in --defaults mode. run_create now takes an overwrite flag, set for composite steps after the first and for setup. --- plonecli/cli.py | 6 +++--- plonecli/templates.py | 6 ++++++ tests/test_templates.py | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/plonecli/cli.py b/plonecli/cli.py index 7c20543..8442b31 100644 --- a/plonecli/cli.py +++ b/plonecli/cli.py @@ -287,11 +287,11 @@ def create(context, template, name, data, data_file, defaults, no_git): steps = reg.get_composite_steps(resolved) if steps: echo(f"\nCreating {resolved} project: {name}", fg="green", reverse=True) - for step in steps: + for index, step in enumerate(steps): echo(f"\n Applying template: {step}", fg="green") committed = run_create( step, name, config, data=answers, defaults=defaults, - git_commit=git_commit, + git_commit=git_commit, overwrite=index > 0, ) if committed: echo(f" Committed: {committed}", fg="green") @@ -386,7 +386,7 @@ def setup(context): config = context.obj["config"] echo("\nRunning zope-setup...", fg="green", reverse=True) - run_create("zope-setup", str(project.root_folder), config) + run_create("zope-setup", str(project.root_folder), config, overwrite=True) @cli.command("serve") diff --git a/plonecli/templates.py b/plonecli/templates.py index 71d20eb..372a66d 100644 --- a/plonecli/templates.py +++ b/plonecli/templates.py @@ -137,6 +137,7 @@ def run_create( data: dict | None = None, defaults: bool = False, git_commit: bool = True, + overwrite: bool = False, ) -> str | None: """Run copier to create a new project from a main template. @@ -148,6 +149,10 @@ def run_create( defaults: Use template defaults for unanswered questions instead of prompting (non-interactive mode). git_commit: Initialise git (if needed) and commit the result. + overwrite: Overwrite existing files without prompting. Needed when a + template is layered onto an existing project (composite steps after + the first, ``plonecli setup``); copier otherwise raises an + interactive-conflict error in non-interactive mode. Returns: The commit message if a commit was made, otherwise ``None``. @@ -161,6 +166,7 @@ def run_create( data=data or {}, user_defaults=_build_user_defaults(config), defaults=defaults, + overwrite=overwrite, unsafe=True, ) diff --git a/tests/test_templates.py b/tests/test_templates.py index 2846620..9d7e0c9 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -9,6 +9,7 @@ ensure_templates_cloned, get_template_path, get_templates_info, + run_create, update_templates_clone, ) @@ -130,3 +131,26 @@ def test_get_templates_info_not_cloned(tmp_path): config = PlonecliConfig(templates_dir=str(tmp_path / "nonexistent")) info = get_templates_info(config) assert info == "not cloned" + + +@patch("plonecli.templates.run_copy") +def test_run_create_overwrite_default_false(mock_run_copy, tmp_path): + """run_create does not overwrite by default.""" + (tmp_path / ".git").mkdir() + (tmp_path / "backend_addon").mkdir() + config = PlonecliConfig(templates_dir=str(tmp_path)) + run_create("backend_addon", "out", config, defaults=True, git_commit=False) + assert mock_run_copy.call_args.kwargs["overwrite"] is False + + +@patch("plonecli.templates.run_copy") +def test_run_create_overwrite_forwarded(mock_run_copy, tmp_path): + """overwrite=True is forwarded to copier (layering onto existing files).""" + (tmp_path / ".git").mkdir() + (tmp_path / "zope-setup").mkdir() + config = PlonecliConfig(templates_dir=str(tmp_path)) + run_create( + "zope-setup", "out", config, defaults=True, git_commit=False, + overwrite=True, + ) + assert mock_run_copy.call_args.kwargs["overwrite"] is True From abcc3b7506a545d57543303885039ea33ddf6b51 Mon Sep 17 00:00:00 2001 From: MrTango Date: Sun, 24 May 2026 16:05:34 +0300 Subject: [PATCH 2/7] Add changelog entry for composite create/setup overwrite fix --- CHANGES.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index cb0257c..b872b45 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,7 +3,10 @@ ## 7.0.0b10 (unreleased) -- Nothing changed yet. +- Fix non-interactive composite `create`/`setup`: layered steps now overwrite + files generated by earlier steps instead of raising a copier conflict in + `--defaults` mode. + [MrTango] ## 7.0.0b9 (2026-05-24) From 1465c136c1aaa75d0769996d5b7b19a332a0d2a8 Mon Sep 17 00:00:00 2001 From: MrTango Date: Sun, 24 May 2026 16:21:40 +0300 Subject: [PATCH 3/7] Assert composite create layers pass overwrite flag at cli level --- tests/test_plonecli.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/test_plonecli.py b/tests/test_plonecli.py index 8092b9d..2877057 100644 --- a/tests/test_plonecli.py +++ b/tests/test_plonecli.py @@ -125,6 +125,9 @@ def test_create_composite_template(mock_ensure, mock_run_create, mock_config, mo assert calls[0][0][1] == "my.addon" assert calls[1][0][0] == "zope-setup" assert calls[1][0][1] == "my.addon" + # First layer creates fresh files; later layers overlay and must overwrite. + assert calls[0].kwargs["overwrite"] is False + assert calls[1].kwargs["overwrite"] is True @patch("plonecli.cli.find_project_root", return_value=None) From 73a30d3b754f97a97160f592738d81c43dc15e41 Mon Sep 17 00:00:00 2001 From: MrTango Date: Sun, 24 May 2026 16:35:10 +0300 Subject: [PATCH 4/7] Let integration tests honor PLONECLI_TEMPLATES_DIR The hardcoded /home/node/... checkout paths made the integration tests skip outside the devcontainer. Resolve PLONECLI_TEMPLATES_DIR first so they run against any local templates checkout. --- tests/test_all_templates_data.py | 4 ++++ tests/test_theme_barceloneta_integration.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/tests/test_all_templates_data.py b/tests/test_all_templates_data.py index fe73c67..8300cea 100644 --- a/tests/test_all_templates_data.py +++ b/tests/test_all_templates_data.py @@ -19,6 +19,7 @@ from __future__ import annotations +import os import shutil from pathlib import Path @@ -46,6 +47,9 @@ def _find_templates_dir() -> Path | None: + env_dir = os.environ.get("PLONECLI_TEMPLATES_DIR") + if env_dir and Path(env_dir).exists(): + return Path(env_dir) if DEV_TEMPLATES_DIR.exists(): return DEV_TEMPLATES_DIR if FALLBACK_TEMPLATES_DIR.exists(): diff --git a/tests/test_theme_barceloneta_integration.py b/tests/test_theme_barceloneta_integration.py index 811b307..3088d29 100644 --- a/tests/test_theme_barceloneta_integration.py +++ b/tests/test_theme_barceloneta_integration.py @@ -33,6 +33,9 @@ def _templates_dir() -> Path: + env_dir = os.environ.get("PLONECLI_TEMPLATES_DIR") + if env_dir and Path(env_dir).exists(): + return Path(env_dir) if DEV_TEMPLATES_DIR.exists(): return DEV_TEMPLATES_DIR if FALLBACK_TEMPLATES_DIR.exists(): From 734c035974eb596387237dc78b3cd66321474a4c Mon Sep 17 00:00:00 2001 From: MrTango Date: Sun, 24 May 2026 16:37:01 +0300 Subject: [PATCH 5/7] CI: check out copier-templates and set PLONECLI_TEMPLATES_DIR Integration tests need a templates checkout; provide one and point PLONECLI_TEMPLATES_DIR at it so they run in CI instead of skipping. Exclude the checkout from flake8. --- .github/workflows/python-package.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index ea39f95..26b1d95 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -19,6 +19,11 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Checkout copier-templates + uses: actions/checkout@v2 + with: + repository: plone/copier-templates + path: copier-templates - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: @@ -32,9 +37,11 @@ jobs: - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics + flake8 . --exclude=copier-templates --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics + flake8 . --exclude=copier-templates --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest + env: + PLONECLI_TEMPLATES_DIR: ${{ github.workspace }}/copier-templates run: | pytest From d828cc71946ce7abdcbc522d875023edae9466a4 Mon Sep 17 00:00:00 2001 From: MrTango Date: Sun, 24 May 2026 16:38:08 +0300 Subject: [PATCH 6/7] CI: set up uv so the theme integration test runs instead of skipping --- .github/workflows/python-package.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 26b1d95..c7ea994 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -28,6 +28,8 @@ jobs: uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} + - name: Set up uv + uses: astral-sh/setup-uv@v5 - name: Install dependencies run: | python -m pip install --upgrade pip From 0087d637477c53268cf0d1f72a210f6da5eaffa6 Mon Sep 17 00:00:00 2001 From: MrTango Date: Sun, 24 May 2026 16:48:45 +0300 Subject: [PATCH 7/7] Clear PLONECLI_TEMPLATES_DIR in home-reload config test CI now sets the env var globally, which overrode the saved config value and broke the test. Drop the env override so the test exercises config file loading only. --- tests/test_config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_config.py b/tests/test_config.py index 1001254..7f6c5ac 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -126,6 +126,7 @@ def test_templates_path_reloads_under_different_home(tmp_path, monkeypatch): config_file = config_dir / "config.toml" monkeypatch.setattr("plonecli.config.CONFIG_DIR", config_dir) monkeypatch.setattr("plonecli.config.CONFIG_FILE", config_file) + monkeypatch.delenv("PLONECLI_TEMPLATES_DIR", raising=False) home_a = tmp_path / "home_a" monkeypatch.setenv("HOME", str(home_a))