From 8c22bad0e0486060d8e674131ac258e078b345f1 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Wed, 8 Jul 2026 18:54:50 +0000 Subject: [PATCH 01/16] move coveragerc --- .coveragerc => .config/.coveragerc | 0 extensions/context.py | 5 +++-- pytest.ini | 2 +- template/.config/.coveragerc | 1 + template/.coveragerc | 1 - template/extensions/context.py.jinja-base | 1 + .../{% if template_uses_python %}.coveragerc{% endif %} | 1 + .../{% if template_uses_python %}.coveragerc{% endif %} | 1 - 8 files changed, 7 insertions(+), 5 deletions(-) rename .coveragerc => .config/.coveragerc (100%) create mode 120000 template/.config/.coveragerc delete mode 120000 template/.coveragerc create mode 120000 template/template/.config/{% if template_uses_python %}.coveragerc{% endif %} delete mode 120000 template/template/{% if template_uses_python %}.coveragerc{% endif %} diff --git a/.coveragerc b/.config/.coveragerc similarity index 100% rename from .coveragerc rename to .config/.coveragerc diff --git a/extensions/context.py b/extensions/context.py index 046f8d865..7765738cd 100644 --- a/extensions/context.py +++ b/extensions/context.py @@ -45,9 +45,10 @@ def hook( # noqa: PLR0915 # yes, this is a lot of statements, but it's all just context["strawberry_graphql_version"] = ( "==0.298.0" # problems with strawberry pydantic in later versions...lost the context for exactly why...it was figured out a while ago, but has to do with being able to override resolvers and have the schema still keep the desired nullability ) - context["fastapi_version"] = ">=0.138.2" + context["fastapi_version"] = ">=0.139.0" context["fastapi_offline_version"] = ">=1.7.7" - context["uvicorn_version"] = ">=0.49.0" + context["starlette_version"] = ">=1.3.1" + context["uvicorn_version"] = ">=0.51.0" context["lab_auto_pulumi_version"] = ">=0.2.3" context["ariadne_codegen_version"] = ">=0.18.0" context["pytest_mock_version"] = ">=3.15.1" diff --git a/pytest.ini b/pytest.ini index 74b83a9f3..c6e82920e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,7 +1,7 @@ [pytest] # Settings managed by the base template norecursedirs = node_modules .precommit_cache .npm_cache .pipenv_cache venv .venv .history -addopts = --cov=src --cov-report html --cov-report term-missing:skip-covered --cov-config=./.coveragerc +addopts = --cov=src --cov-report html --cov-report term-missing:skip-covered --cov-config=./.config/.coveragerc log_cli = 1 log_cli_level = INFO diff --git a/template/.config/.coveragerc b/template/.config/.coveragerc new file mode 120000 index 000000000..15224beee --- /dev/null +++ b/template/.config/.coveragerc @@ -0,0 +1 @@ +../../.config/.coveragerc \ No newline at end of file diff --git a/template/.coveragerc b/template/.coveragerc deleted file mode 120000 index 50785142b..000000000 --- a/template/.coveragerc +++ /dev/null @@ -1 +0,0 @@ -../.coveragerc \ No newline at end of file diff --git a/template/extensions/context.py.jinja-base b/template/extensions/context.py.jinja-base index 109983ea2..f319432ad 100644 --- a/template/extensions/context.py.jinja-base +++ b/template/extensions/context.py.jinja-base @@ -38,6 +38,7 @@ class ContextUpdater(ContextHook): context["strawberry_graphql_version"] = "{{ strawberry_graphql_version }}" context["fastapi_version"] = "{{ fastapi_version }}" context["fastapi_offline_version"] = "{{ fastapi_offline_version }}" + context["starlette_version"] = "{{ starlette_version }}" context["uvicorn_version"] = "{{ uvicorn_version }}" context["lab_auto_pulumi_version"] = "{{ lab_auto_pulumi_version }}" context["ariadne_codegen_version"] = "{{ ariadne_codegen_version }}" diff --git a/template/template/.config/{% if template_uses_python %}.coveragerc{% endif %} b/template/template/.config/{% if template_uses_python %}.coveragerc{% endif %} new file mode 120000 index 000000000..068ac56bb --- /dev/null +++ b/template/template/.config/{% if template_uses_python %}.coveragerc{% endif %} @@ -0,0 +1 @@ +../../../.config/.coveragerc \ No newline at end of file diff --git a/template/template/{% if template_uses_python %}.coveragerc{% endif %} b/template/template/{% if template_uses_python %}.coveragerc{% endif %} deleted file mode 120000 index ae7ed41e9..000000000 --- a/template/template/{% if template_uses_python %}.coveragerc{% endif %} +++ /dev/null @@ -1 +0,0 @@ -../../.coveragerc \ No newline at end of file From 72f67fb1465425bcf19496a0044ecc3ebb06e25e Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Wed, 8 Jul 2026 19:12:30 +0000 Subject: [PATCH 02/16] ci --- .github/workflows/ci.yaml | 1 + template/.github/workflows/ci.yaml.jinja-base | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 0f5adf6ff..b6b50eea2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -167,6 +167,7 @@ jobs: rm -rf .github # apparently this folder doesn't get removed with the previous command for some reason rm -rf .devcontainer # apparently this folder doesn't get removed with the previous command for some reason rm -rf .claude # apparently this folder doesn't get removed with the previous command for some reason + rm -rf .config # apparently this folder doesn't get removed with the previous command for some reason ls -la - name: Move the instantiated template into the repo root run: | diff --git a/template/.github/workflows/ci.yaml.jinja-base b/template/.github/workflows/ci.yaml.jinja-base index 2cf08a798..2548683c1 100644 --- a/template/.github/workflows/ci.yaml.jinja-base +++ b/template/.github/workflows/ci.yaml.jinja-base @@ -145,6 +145,7 @@ jobs: rm -rf .github # apparently this folder doesn't get removed with the previous command for some reason rm -rf .devcontainer # apparently this folder doesn't get removed with the previous command for some reason rm -rf .claude # apparently this folder doesn't get removed with the previous command for some reason + rm -rf .config # apparently this folder doesn't get removed with the previous command for some reason ls -la - name: Move the instantiated template into the repo root From 94b41196ad8261735effdd37220c46d2aa12b04d Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Wed, 8 Jul 2026 19:59:22 +0000 Subject: [PATCH 03/16] simplifier script --- .../pyrefly.mutmut.toml | 0 .../openapi/openapi_schema_simplifier.py | 20 +++++++++++++++++-- .../openapi/test_openapi_schema_simplifier.py | 15 ++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) rename pyrefly.mutmut.toml => .config/pyrefly.mutmut.toml (100%) diff --git a/pyrefly.mutmut.toml b/.config/pyrefly.mutmut.toml similarity index 100% rename from pyrefly.mutmut.toml rename to .config/pyrefly.mutmut.toml diff --git a/src/copier_base_template/openapi/openapi_schema_simplifier.py b/src/copier_base_template/openapi/openapi_schema_simplifier.py index fdecd1bb2..bcca2f04c 100644 --- a/src/copier_base_template/openapi/openapi_schema_simplifier.py +++ b/src/copier_base_template/openapi/openapi_schema_simplifier.py @@ -3,6 +3,20 @@ # This is unit tested in the https://github.com/LabAutomationAndScreening/copier-base-template repo. It is excluded from coverage checks in other repos, so be cautious about making changes downstream as opposed to the copier base template...really it's for parsing OpenAPI schema...so I can't think of a reason you'd need custom changes that are repo-specific that shouldn't be backported to the template +def _merge_typed_members(typed_dicts: list[dict[str, JsonValue]]) -> dict[str, JsonValue] | None: + merged: dict[str, JsonValue] = {} + for typed_member in typed_dicts: + for key, value in typed_member.items(): + if key == "type": + continue + if key not in merged: + merged[key] = value + continue + if merged[key] != value: + return None # branches disagree on this key; a single merged schema would silently drop one value + return merged + + def _collapse_anyof(schema: dict[str, JsonValue]) -> None: any_of = schema.get("anyOf") if not isinstance(any_of, list): @@ -24,9 +38,11 @@ def _collapse_anyof(schema: dict[str, JsonValue]) -> None: return # enum/const apply to every type, so merging them would wrongly reject null and the sibling types typed_dicts.append(member) member_types.append(member_type) + merged = _merge_typed_members(typed_dicts) + if merged is None: + return # branches carry conflicting per-key values; collapsing would be lossy, so leave the union as-is del schema["anyOf"] - for typed_member in typed_dicts: - schema.update({key: value for key, value in typed_member.items() if key != "type"}) + schema.update(merged) schema["type"] = [*member_types, "null"] diff --git a/tests/unit/openapi/test_openapi_schema_simplifier.py b/tests/unit/openapi/test_openapi_schema_simplifier.py index 589666002..72e9432a4 100644 --- a/tests/unit/openapi/test_openapi_schema_simplifier.py +++ b/tests/unit/openapi/test_openapi_schema_simplifier.py @@ -105,6 +105,21 @@ def test_Given_typed_member_has_a_branch_constraint__When_collapsed__Then_anyof_ assert "anyOf" in field assert "type" not in field + def test_Given_branches_disagree_on_a_key__When_collapsed__Then_anyof_left_untouched(self): + field: dict[str, JsonValue] = { + "anyOf": [ + {"type": "string", "format": "date-time"}, + {"type": "string", "format": "date"}, + {"type": "null"}, + ] + } + document: dict[str, JsonValue] = {"value": field} + + openapi_schema_simplifier.collapse_nullable_anyof(document) + + assert "anyOf" in field + assert "type" not in field + class TestKiotaGeneration: def test_Given_nullable_integer__When_collapsed_and_generated__Then_no_member1_wrapper(self, tmp_path: Path): From 85a30194d8b8eb07d664029b18d4adc560c0e702 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Wed, 8 Jul 2026 20:02:03 +0000 Subject: [PATCH 04/16] Try mutmut pyrefly --- .config/pyrefly.mutmut.toml | 2 +- pyproject.toml | 2 +- .../{% if template_uses_python %}pyrefly.mutmut.toml{% endif %} | 1 + .../{% if template_uses_python %}pyrefly.mutmut.toml{% endif %} | 1 - 4 files changed, 3 insertions(+), 3 deletions(-) create mode 120000 template/template/.config/{% if template_uses_python %}pyrefly.mutmut.toml{% endif %} delete mode 120000 template/template/{% if template_uses_python %}pyrefly.mutmut.toml{% endif %} diff --git a/.config/pyrefly.mutmut.toml b/.config/pyrefly.mutmut.toml index 698d43b3c..be0760306 100644 --- a/.config/pyrefly.mutmut.toml +++ b/.config/pyrefly.mutmut.toml @@ -4,7 +4,7 @@ use-ignore-files = false disable-project-excludes-heuristics = true python-platform = "linux" -project-includes = ["mutants/src/**"] +project-includes = ["../mutants/src/**"] project-excludes = [ "**/generated/**", ] diff --git a/pyproject.toml b/pyproject.toml index bfce5ee3d..ed83e4b78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ do_not_mutate = [ "*/generated/*" , # do not mutate generated code, this is cr # mutants tree and excludes do_not_mutate files, so every reported type error falls inside a mutant # function and mutmut can attribute it. Without it, pyrefly checks the real source from the mutants/ # cwd and reports errors on non-mutant lines, crashing with "Could not find mutant for type error". -type_check_command = ['uv', 'run', 'pyrefly', 'check', '--output-format=json', '-c', '../pyrefly.mutmut.toml'] +type_check_command = ['uv', 'run', 'pyrefly', 'check', '--output-format=json', '-c', '../.config/pyrefly.mutmut.toml'] do_not_mutate_patterns = [ # disable mutations of all logger.info/debug/... statements 'logger\.\w+', diff --git a/template/template/.config/{% if template_uses_python %}pyrefly.mutmut.toml{% endif %} b/template/template/.config/{% if template_uses_python %}pyrefly.mutmut.toml{% endif %} new file mode 120000 index 000000000..e5b8bb01a --- /dev/null +++ b/template/template/.config/{% if template_uses_python %}pyrefly.mutmut.toml{% endif %} @@ -0,0 +1 @@ +../../../.config/pyrefly.mutmut.toml \ No newline at end of file diff --git a/template/template/{% if template_uses_python %}pyrefly.mutmut.toml{% endif %} b/template/template/{% if template_uses_python %}pyrefly.mutmut.toml{% endif %} deleted file mode 120000 index 69658ec0f..000000000 --- a/template/template/{% if template_uses_python %}pyrefly.mutmut.toml{% endif %} +++ /dev/null @@ -1 +0,0 @@ -../../pyrefly.mutmut.toml \ No newline at end of file From 3b72b0d164bd37712d3b49428d03d01105302272 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Wed, 8 Jul 2026 20:18:53 +0000 Subject: [PATCH 05/16] pylintrc --- pylintrc.toml => .config/pylintrc.toml | 0 .devcontainer/devcontainer.json | 2 +- .pre-commit-config.yaml | 2 +- template/.config/pylintrc.toml | 1 + template/pylintrc.toml | 1 - .../{% if template_uses_python %}pylintrc.toml{% endif %} | 1 + .../{% if template_uses_python %}pylintrc.toml{% endif %} | 1 - 7 files changed, 4 insertions(+), 4 deletions(-) rename pylintrc.toml => .config/pylintrc.toml (100%) create mode 120000 template/.config/pylintrc.toml delete mode 120000 template/pylintrc.toml create mode 120000 template/template/.config/{% if template_uses_python %}pylintrc.toml{% endif %} delete mode 120000 template/template/{% if template_uses_python %}pylintrc.toml{% endif %} diff --git a/pylintrc.toml b/.config/pylintrc.toml similarity index 100% rename from pylintrc.toml rename to .config/pylintrc.toml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index ca94793fd..8952eb41b 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -68,5 +68,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" - // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): f239a981 # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 14d7ac54 # spellchecker:disable-line } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index da97ac53f..40d3765bd 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -272,7 +272,7 @@ repos: template/.*| )$ args: - - --rcfile=./pylintrc.toml + - --rcfile=./.config/pylintrc.toml verbose: true - repo: local diff --git a/template/.config/pylintrc.toml b/template/.config/pylintrc.toml new file mode 120000 index 000000000..5265fd8eb --- /dev/null +++ b/template/.config/pylintrc.toml @@ -0,0 +1 @@ +../../.config/pylintrc.toml \ No newline at end of file diff --git a/template/pylintrc.toml b/template/pylintrc.toml deleted file mode 120000 index b19da0e8f..000000000 --- a/template/pylintrc.toml +++ /dev/null @@ -1 +0,0 @@ -../pylintrc.toml \ No newline at end of file diff --git a/template/template/.config/{% if template_uses_python %}pylintrc.toml{% endif %} b/template/template/.config/{% if template_uses_python %}pylintrc.toml{% endif %} new file mode 120000 index 000000000..743d8fb64 --- /dev/null +++ b/template/template/.config/{% if template_uses_python %}pylintrc.toml{% endif %} @@ -0,0 +1 @@ +../../../.config/pylintrc.toml \ No newline at end of file diff --git a/template/template/{% if template_uses_python %}pylintrc.toml{% endif %} b/template/template/{% if template_uses_python %}pylintrc.toml{% endif %} deleted file mode 120000 index 0d434ddfd..000000000 --- a/template/template/{% if template_uses_python %}pylintrc.toml{% endif %} +++ /dev/null @@ -1 +0,0 @@ -../../pylintrc.toml \ No newline at end of file From b2ff1ff62b7f52368f1faa145b2b06c0c4e789b5 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Wed, 8 Jul 2026 20:40:24 +0000 Subject: [PATCH 06/16] ruff --- ruff-non-src.toml => .config/ruff-non-src.toml | 0 ruff.toml => .config/ruff.toml | 0 .devcontainer/devcontainer.json | 4 ++-- .pre-commit-config.yaml | 4 ++-- copier.yaml | 6 +++--- template/.config/ruff-non-src.toml | 1 + template/.config/ruff.toml | 1 + template/.devcontainer/devcontainer.json.jinja-base | 2 +- template/copier.yml.jinja-base | 6 +++--- template/ruff-non-src.toml | 1 - template/ruff.toml | 1 - ...% if template_uses_python %}ruff-non-src.toml{% endif %} | 1 + .../{% if template_uses_python %}ruff.toml{% endif %} | 1 + ...% if template_uses_python %}ruff-non-src.toml{% endif %} | 1 - .../{% if template_uses_python %}ruff.toml{% endif %} | 1 - 15 files changed, 15 insertions(+), 15 deletions(-) rename ruff-non-src.toml => .config/ruff-non-src.toml (100%) rename ruff.toml => .config/ruff.toml (100%) create mode 120000 template/.config/ruff-non-src.toml create mode 120000 template/.config/ruff.toml delete mode 120000 template/ruff-non-src.toml delete mode 120000 template/ruff.toml create mode 120000 template/template/.config/{% if template_uses_python %}ruff-non-src.toml{% endif %} create mode 120000 template/template/.config/{% if template_uses_python %}ruff.toml{% endif %} delete mode 120000 template/template/{% if template_uses_python %}ruff-non-src.toml{% endif %} delete mode 120000 template/template/{% if template_uses_python %}ruff.toml{% endif %} diff --git a/ruff-non-src.toml b/.config/ruff-non-src.toml similarity index 100% rename from ruff-non-src.toml rename to .config/ruff-non-src.toml diff --git a/ruff.toml b/.config/ruff.toml similarity index 100% rename from ruff.toml rename to .config/ruff.toml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8952eb41b..c7445f9df 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -57,7 +57,7 @@ "python.terminal.activateEnvironment": false, // uv manages the environment "ruff.nativeServer": "on", // TODO: see if there's a way to specify different configurations for different folders - "ruff.configuration": "/workspaces/copier-base-template/ruff-non-src.toml", // use the non-src configuration since it's less restrictive and won't show false positives and underline things + "ruff.configuration": "/workspaces/copier-base-template/.config/ruff-non-src.toml", // use the non-src configuration since it's less restrictive and won't show false positives and underline things "[jsonc][json]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true @@ -68,5 +68,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" - // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 14d7ac54 # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 3e9bb1fa # spellchecker:disable-line } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 40d3765bd..4c508a657 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -234,7 +234,7 @@ repos: hooks: - id: ruff name: ruff-src - args: [--fix, --config=./ruff.toml] + args: [--fix, --config=./.config/ruff.toml] files: src/.+\.py$ exclude: | (?x)^( @@ -244,7 +244,7 @@ repos: )$ - id: ruff name: ruff-non-src # tests, scripts and other non-source code - args: [--fix, --config=./ruff-non-src.toml] + args: [--fix, --config=./.config/ruff-non-src.toml] files: ^(?!src/)(?!.+/src/).+\.py$ exclude: | (?x)^( diff --git a/copier.yaml b/copier.yaml index 8be0dca31..67e4b993c 100644 --- a/copier.yaml +++ b/copier.yaml @@ -115,13 +115,13 @@ _tasks: fi done - command: | - if [ -f ruff.toml ]; then + if [ -f .config/ruff.toml ]; then echo "Updating ruff target-version from python_version..." py_major_minor="$(printf '%s' '{{ python_version }}' | cut -d. -f1,2)" py_tag="py$(printf '%s' "$py_major_minor" | tr -d '.')" - sed -i -E 's/^target-version = "py[0-9]+"/target-version = "'"$py_tag"'"/' ruff.toml + sed -i -E 's/^target-version = "py[0-9]+"/target-version = "'"$py_tag"'"/' .config/ruff.toml else - echo "ruff.toml not found; skipping Ruff target-version update." + echo ".config/ruff.toml not found; skipping Ruff target-version update." fi - command: | python3 '{{ _copier_conf.src_path }}/src/copier_base_template/copier_tasks/copier_provenance.py' \ diff --git a/template/.config/ruff-non-src.toml b/template/.config/ruff-non-src.toml new file mode 120000 index 000000000..1f0fcd63a --- /dev/null +++ b/template/.config/ruff-non-src.toml @@ -0,0 +1 @@ +../../.config/ruff-non-src.toml \ No newline at end of file diff --git a/template/.config/ruff.toml b/template/.config/ruff.toml new file mode 120000 index 000000000..279de52b1 --- /dev/null +++ b/template/.config/ruff.toml @@ -0,0 +1 @@ +../../.config/ruff.toml \ No newline at end of file diff --git a/template/.devcontainer/devcontainer.json.jinja-base b/template/.devcontainer/devcontainer.json.jinja-base index 5df31b919..987ef2a00 100644 --- a/template/.devcontainer/devcontainer.json.jinja-base +++ b/template/.devcontainer/devcontainer.json.jinja-base @@ -72,7 +72,7 @@ "python.terminal.activateEnvironment": false, // uv manages the environment "ruff.nativeServer": "on", // TODO: see if there's a way to specify different configurations for different folders - "ruff.configuration": "/workspaces/{% endraw %}{{ repo_name }}{% raw %}/ruff-non-src.toml", // use the non-src configuration since it's less restrictive and won't show false positives and underline things{% endraw %}{% endif %}{% raw %} + "ruff.configuration": "/workspaces/{% endraw %}{{ repo_name }}{% raw %}/.config/ruff-non-src.toml", // use the non-src configuration since it's less restrictive and won't show false positives and underline things{% endraw %}{% endif %}{% raw %} "[jsonc][json][javascript][typescript][graphql][css][scss][html][vue]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true diff --git a/template/copier.yml.jinja-base b/template/copier.yml.jinja-base index e95d435e1..0e32002d4 100644 --- a/template/copier.yml.jinja-base +++ b/template/copier.yml.jinja-base @@ -199,13 +199,13 @@ _tasks: python3 '{{ _copier_conf.src_path }}/src/copier_tasks/ensure_pnpm_minimum_release_age_exclude.py' --patterns "$PNPM_PATTERNS" when: "{{ pnpm_minimum_release_age_exclude }}"{% endraw %}{% endif %}{% raw %} - command: | - if [ -f ruff.toml ]; then + if [ -f .config/ruff.toml ]; then echo "Updating ruff target-version from python_version..." py_major_minor="$(printf '%s' '{{ python_version }}' | cut -d. -f1,2)" py_tag="py$(printf '%s' "$py_major_minor" | tr -d '.')" - sed -i -E 's/^target-version = "py[0-9]+"/target-version = "'"$py_tag"'"/' ruff.toml + sed -i -E 's/^target-version = "py[0-9]+"/target-version = "'"$py_tag"'"/' .config/ruff.toml else - echo "ruff.toml not found; skipping Ruff target-version update." + echo ".config/ruff.toml not found; skipping Ruff target-version update." fi - command: | python3 '{{ _copier_conf.src_path }}/src/copier_tasks/copier_provenance.py' \ diff --git a/template/ruff-non-src.toml b/template/ruff-non-src.toml deleted file mode 120000 index df741ebb4..000000000 --- a/template/ruff-non-src.toml +++ /dev/null @@ -1 +0,0 @@ -../ruff-non-src.toml \ No newline at end of file diff --git a/template/ruff.toml b/template/ruff.toml deleted file mode 120000 index 851b505b5..000000000 --- a/template/ruff.toml +++ /dev/null @@ -1 +0,0 @@ -../ruff.toml \ No newline at end of file diff --git a/template/template/.config/{% if template_uses_python %}ruff-non-src.toml{% endif %} b/template/template/.config/{% if template_uses_python %}ruff-non-src.toml{% endif %} new file mode 120000 index 000000000..b2a8aefe1 --- /dev/null +++ b/template/template/.config/{% if template_uses_python %}ruff-non-src.toml{% endif %} @@ -0,0 +1 @@ +../../../.config/ruff-non-src.toml \ No newline at end of file diff --git a/template/template/.config/{% if template_uses_python %}ruff.toml{% endif %} b/template/template/.config/{% if template_uses_python %}ruff.toml{% endif %} new file mode 120000 index 000000000..8d22cd896 --- /dev/null +++ b/template/template/.config/{% if template_uses_python %}ruff.toml{% endif %} @@ -0,0 +1 @@ +../../../.config/ruff.toml \ No newline at end of file diff --git a/template/template/{% if template_uses_python %}ruff-non-src.toml{% endif %} b/template/template/{% if template_uses_python %}ruff-non-src.toml{% endif %} deleted file mode 120000 index f8a8f3e15..000000000 --- a/template/template/{% if template_uses_python %}ruff-non-src.toml{% endif %} +++ /dev/null @@ -1 +0,0 @@ -../../ruff-non-src.toml \ No newline at end of file diff --git a/template/template/{% if template_uses_python %}ruff.toml{% endif %} b/template/template/{% if template_uses_python %}ruff.toml{% endif %} deleted file mode 120000 index e768b4aa2..000000000 --- a/template/template/{% if template_uses_python %}ruff.toml{% endif %} +++ /dev/null @@ -1 +0,0 @@ -../../ruff.toml \ No newline at end of file From 12e91574cee890deca3b2efce99974d4cb0498cb Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Thu, 9 Jul 2026 10:10:58 +0000 Subject: [PATCH 07/16] move contributing --- CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md | 0 CONTRIBUTING.md => .github/CONTRIBUTING.md | 4 ++-- template/.github/CODE_OF_CONDUCT.md | 1 + template/.github/CONTRIBUTING.md | 1 + template/CODE_OF_CONDUCT.md | 1 - template/CONTRIBUTING.md | 1 - template/template/.github/CONTRIBUTING.md | 1 + ...is_open_source %}CODE_OF_CONDUCT.md{% endif %}{% endraw %} | 1 + template/template/CONTRIBUTING.md | 1 - ...is_open_source %}CODE_OF_CONDUCT.md{% endif %}{% endraw %} | 1 - 10 files changed, 6 insertions(+), 6 deletions(-) rename CODE_OF_CONDUCT.md => .github/CODE_OF_CONDUCT.md (100%) rename CONTRIBUTING.md => .github/CONTRIBUTING.md (96%) create mode 120000 template/.github/CODE_OF_CONDUCT.md create mode 120000 template/.github/CONTRIBUTING.md delete mode 120000 template/CODE_OF_CONDUCT.md delete mode 120000 template/CONTRIBUTING.md create mode 120000 template/template/.github/CONTRIBUTING.md create mode 120000 template/template/.github/{% raw %}{% if is_open_source %}CODE_OF_CONDUCT.md{% endif %}{% endraw %} delete mode 120000 template/template/CONTRIBUTING.md delete mode 120000 template/template/{% raw %}{% if is_open_source %}CODE_OF_CONDUCT.md{% endif %}{% endraw %} diff --git a/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to .github/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/.github/CONTRIBUTING.md similarity index 96% rename from CONTRIBUTING.md rename to .github/CONTRIBUTING.md index 8272cfaba..7f0008176 100644 --- a/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -27,7 +27,7 @@ By participating in this project, you agree to abide by our [Code of Conduct](./ 1. **Fork the repository** on GitHub. 2. **Set up the Dev Container (Recommended Method)** - - Create a GitHub Codespace (`Code`->`Codespaces` in the GitHub web console of your cloned repository) OR click on the "Open in Devcontainer" link in the [Readme](./README.md) + - Create a GitHub Codespace (`Code`->`Codespaces` in the GitHub web console of your cloned repository) OR click on the "Open in Devcontainer" link in the [Readme](../README.md) 3. **Make Your Changes** - Create a branch for your feature or bug fix: @@ -107,7 +107,7 @@ We love hearing new ideas! If you have an idea for a feature: ## 📜 License -By contributing, you agree that your contributions will be licensed under the same license as the project: [LICENSE](./LICENSE). +By contributing, you agree that your contributions will be licensed under the same license as the project: [LICENSE](../LICENSE). --- diff --git a/template/.github/CODE_OF_CONDUCT.md b/template/.github/CODE_OF_CONDUCT.md new file mode 120000 index 000000000..7d5eea0c6 --- /dev/null +++ b/template/.github/CODE_OF_CONDUCT.md @@ -0,0 +1 @@ +../../.github/CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/template/.github/CONTRIBUTING.md b/template/.github/CONTRIBUTING.md new file mode 120000 index 000000000..95181235e --- /dev/null +++ b/template/.github/CONTRIBUTING.md @@ -0,0 +1 @@ +../../.github/CONTRIBUTING.md \ No newline at end of file diff --git a/template/CODE_OF_CONDUCT.md b/template/CODE_OF_CONDUCT.md deleted file mode 120000 index 0400d5746..000000000 --- a/template/CODE_OF_CONDUCT.md +++ /dev/null @@ -1 +0,0 @@ -../CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/template/CONTRIBUTING.md b/template/CONTRIBUTING.md deleted file mode 120000 index 44fcc6343..000000000 --- a/template/CONTRIBUTING.md +++ /dev/null @@ -1 +0,0 @@ -../CONTRIBUTING.md \ No newline at end of file diff --git a/template/template/.github/CONTRIBUTING.md b/template/template/.github/CONTRIBUTING.md new file mode 120000 index 000000000..724d1770d --- /dev/null +++ b/template/template/.github/CONTRIBUTING.md @@ -0,0 +1 @@ +../../../.github/CONTRIBUTING.md \ No newline at end of file diff --git a/template/template/.github/{% raw %}{% if is_open_source %}CODE_OF_CONDUCT.md{% endif %}{% endraw %} b/template/template/.github/{% raw %}{% if is_open_source %}CODE_OF_CONDUCT.md{% endif %}{% endraw %} new file mode 120000 index 000000000..61f306b84 --- /dev/null +++ b/template/template/.github/{% raw %}{% if is_open_source %}CODE_OF_CONDUCT.md{% endif %}{% endraw %} @@ -0,0 +1 @@ +../../../.github/CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/template/template/CONTRIBUTING.md b/template/template/CONTRIBUTING.md deleted file mode 120000 index f939e75f2..000000000 --- a/template/template/CONTRIBUTING.md +++ /dev/null @@ -1 +0,0 @@ -../../CONTRIBUTING.md \ No newline at end of file diff --git a/template/template/{% raw %}{% if is_open_source %}CODE_OF_CONDUCT.md{% endif %}{% endraw %} b/template/template/{% raw %}{% if is_open_source %}CODE_OF_CONDUCT.md{% endif %}{% endraw %} deleted file mode 120000 index a3613c99f..000000000 --- a/template/template/{% raw %}{% if is_open_source %}CODE_OF_CONDUCT.md{% endif %}{% endraw %} +++ /dev/null @@ -1 +0,0 @@ -../../CODE_OF_CONDUCT.md \ No newline at end of file From 5310725c0cd307f42a1f57ff4575f828b2ad4bdf Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Thu, 9 Jul 2026 10:45:02 +0000 Subject: [PATCH 08/16] format --- .devcontainer/devcontainer.json | 2 +- .pre-commit-config.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index c7445f9df..77ae4f833 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -68,5 +68,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" - // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 3e9bb1fa # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): ca58bf9d # spellchecker:disable-line } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4c508a657..de9a534e8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -253,6 +253,7 @@ repos: template/.*| )$ - id: ruff-format + args: [--config=./.config/ruff.toml] exclude: | (?x)^( .*/generated/graphql/.*| From 8e3fe1da802090dacd2c0b6ed3e72e74510233e5 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Thu, 9 Jul 2026 11:03:05 +0000 Subject: [PATCH 09/16] pyrefly --- .config/pyrefly.mutmut.toml | 3 ++- pyrefly.toml => .config/pyrefly.toml | 17 +++++++++-------- .devcontainer/devcontainer.json | 9 +++++---- .pre-commit-config.yaml | 2 +- template/.config/pyrefly.toml | 1 + .../.devcontainer/devcontainer.json.jinja-base | 9 +++++---- template/pyrefly.toml | 1 - ...mplate_uses_python %}pyrefly.toml{% endif %} | 1 + ...mplate_uses_python %}pyrefly.toml{% endif %} | 1 - 9 files changed, 24 insertions(+), 20 deletions(-) rename pyrefly.toml => .config/pyrefly.toml (73%) create mode 120000 template/.config/pyrefly.toml delete mode 120000 template/pyrefly.toml create mode 120000 template/template/.config/{% if template_uses_python %}pyrefly.toml{% endif %} delete mode 120000 template/template/{% if template_uses_python %}pyrefly.toml{% endif %} diff --git a/.config/pyrefly.mutmut.toml b/.config/pyrefly.mutmut.toml index be0760306..977e2d28e 100644 --- a/.config/pyrefly.mutmut.toml +++ b/.config/pyrefly.mutmut.toml @@ -6,5 +6,6 @@ disable-project-excludes-heuristics = true python-platform = "linux" project-includes = ["../mutants/src/**"] project-excludes = [ - "**/generated/**", + # paths are relative to this config file (in .config/), so prefix with ../ to anchor at the repo root + "../**/generated/**", ] diff --git a/pyrefly.toml b/.config/pyrefly.toml similarity index 73% rename from pyrefly.toml rename to .config/pyrefly.toml index a170a4d79..93e412a0b 100644 --- a/pyrefly.toml +++ b/.config/pyrefly.toml @@ -1,19 +1,20 @@ project-excludes = [ + # paths are relative to this config file (in .config/), so prefix with ../ to anchor at the repo root # excludes managed by this repository - "**/template", + "../**/template", # excludes managed by template - "**/vendor_files", - "**/generated", - "**/copier_template_resources", - "**/.claude/plugins", # similar to vendor_files, but these are gitignored and can contain arbitrary code, so exclude from type checking but not mutation testing + "../**/vendor_files", + "../**/generated", + "../**/copier_template_resources", + "../**/.claude/plugins", # similar to vendor_files, but these are gitignored and can contain arbitrary code, so exclude from type checking but not mutation testing ] # without this explicit "project-includes" and "search-path", pyrefly struggles to understand the tests folder project-includes = [ - "src", - "tests" + "../src", + "../tests" ] -search-path = ["."] +search-path = [".."] disable-project-excludes-heuristics = true # with this being enabled by default, pyrefly things the src is excluded because it's an editable local install python-platform = "linux" infer-with-first-use = false # more similar to pyright. must define the empty list type when instantiated diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 77ae4f833..3756d9fe8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -28,13 +28,13 @@ "-AmazonWebServices.aws-toolkit-vscode", // the AWS CLI feature installs this automatically, but it's causing problems in VS Code // basic tooling // "eamodio.gitlens@15.5.1", - "coderabbit.coderabbit-vscode@0.20.4", + "coderabbit.coderabbit-vscode@0.20.7", "ms-vscode.live-server@0.5.2025051301", "MS-vsliveshare.vsliveshare@1.0.5905", - "anthropic.claude-code@2.1.197", + "anthropic.claude-code@2.1.205", // Python "ms-python.python@2026.5.2026052901", - "meta.pyrefly@1.1.1", + "meta.pyrefly@1.1.9001", "ms-vscode-remote.remote-containers@0.414.0", "charliermarsh.ruff@2026.56.0", // Misc file formats @@ -58,6 +58,7 @@ "ruff.nativeServer": "on", // TODO: see if there's a way to specify different configurations for different folders "ruff.configuration": "/workspaces/copier-base-template/.config/ruff-non-src.toml", // use the non-src configuration since it's less restrictive and won't show false positives and underline things + "python.pyrefly.configPath": "/workspaces/copier-base-template/.config/pyrefly.toml", "[jsonc][json]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true @@ -68,5 +69,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" - // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): ca58bf9d # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 360bc892 # spellchecker:disable-line } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index de9a534e8..6c4f7fb51 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -300,7 +300,7 @@ repos: hooks: - id: pyrefly name: pyrefly - entry: bash -c "uv run pyrefly check" + entry: bash -c "uv run pyrefly check -c ./.config/pyrefly.toml" files: '.+\.py$' exclude: | (?x)^( diff --git a/template/.config/pyrefly.toml b/template/.config/pyrefly.toml new file mode 120000 index 000000000..3791d0a23 --- /dev/null +++ b/template/.config/pyrefly.toml @@ -0,0 +1 @@ +../../.config/pyrefly.toml \ No newline at end of file diff --git a/template/.devcontainer/devcontainer.json.jinja-base b/template/.devcontainer/devcontainer.json.jinja-base index 987ef2a00..8b9e96ebb 100644 --- a/template/.devcontainer/devcontainer.json.jinja-base +++ b/template/.devcontainer/devcontainer.json.jinja-base @@ -34,14 +34,14 @@ "-AmazonWebServices.aws-toolkit-vscode", // the AWS CLI feature installs this automatically, but it's causing problems in VS Code{% endraw %}{% endif %}{% raw %} // basic tooling // "eamodio.gitlens@15.5.1", - "coderabbit.coderabbit-vscode@0.20.4", + "coderabbit.coderabbit-vscode@0.20.7", "ms-vscode.live-server@0.5.2025051301", "MS-vsliveshare.vsliveshare@1.0.5905",{% endraw %}{% if install_claude_cli %}{% raw %} - "anthropic.claude-code@2.1.197",{% endraw %}{% endif %}{% raw %}{% endraw %}{% if (is_child_of_copier_base_template is defined and is_child_of_copier_base_template is sameas(true)) or (is_child_of_copier_base_template is not defined and template_uses_python is defined and template_uses_python is sameas(true)) %}{% raw %} + "anthropic.claude-code@2.1.205",{% endraw %}{% endif %}{% raw %}{% endraw %}{% if (is_child_of_copier_base_template is defined and is_child_of_copier_base_template is sameas(true)) or (is_child_of_copier_base_template is not defined and template_uses_python is defined and template_uses_python is sameas(true)) %}{% raw %} // Python "ms-python.python@2026.5.2026052901", - "meta.pyrefly@1.1.1", + "meta.pyrefly@1.1.9001", "ms-vscode-remote.remote-containers@0.414.0", "charliermarsh.ruff@2026.56.0",{% endraw %}{% endif %}{% raw %} {% endraw %}{% if is_child_of_copier_base_template is not defined and template_uses_vuejs is defined and template_uses_vuejs is sameas(true) %}{% raw %} @@ -72,7 +72,8 @@ "python.terminal.activateEnvironment": false, // uv manages the environment "ruff.nativeServer": "on", // TODO: see if there's a way to specify different configurations for different folders - "ruff.configuration": "/workspaces/{% endraw %}{{ repo_name }}{% raw %}/.config/ruff-non-src.toml", // use the non-src configuration since it's less restrictive and won't show false positives and underline things{% endraw %}{% endif %}{% raw %} + "ruff.configuration": "/workspaces/{% endraw %}{{ repo_name }}{% raw %}/.config/ruff-non-src.toml", // use the non-src configuration since it's less restrictive and won't show false positives and underline things + "python.pyrefly.configPath": "/workspaces/{% endraw %}{{ repo_name }}{% raw %}/.config/pyrefly.toml",{% endraw %}{% endif %}{% raw %} "[jsonc][json][javascript][typescript][graphql][css][scss][html][vue]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true diff --git a/template/pyrefly.toml b/template/pyrefly.toml deleted file mode 120000 index c99e7d412..000000000 --- a/template/pyrefly.toml +++ /dev/null @@ -1 +0,0 @@ -../pyrefly.toml \ No newline at end of file diff --git a/template/template/.config/{% if template_uses_python %}pyrefly.toml{% endif %} b/template/template/.config/{% if template_uses_python %}pyrefly.toml{% endif %} new file mode 120000 index 000000000..afe96477d --- /dev/null +++ b/template/template/.config/{% if template_uses_python %}pyrefly.toml{% endif %} @@ -0,0 +1 @@ +../../../.config/pyrefly.toml \ No newline at end of file diff --git a/template/template/{% if template_uses_python %}pyrefly.toml{% endif %} b/template/template/{% if template_uses_python %}pyrefly.toml{% endif %} deleted file mode 120000 index 0653122e2..000000000 --- a/template/template/{% if template_uses_python %}pyrefly.toml{% endif %} +++ /dev/null @@ -1 +0,0 @@ -../../pyrefly.toml \ No newline at end of file From 35a4d7eff1f76585b577a42f9858fb6d46a277a7 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Thu, 9 Jul 2026 12:09:10 +0000 Subject: [PATCH 10/16] typos --- _typos.toml => .config/_typos.toml | 0 .devcontainer/devcontainer.json | 2 +- .pre-commit-config.yaml | 1 + extensions/context.py | 2 +- pyproject.toml | 2 +- template/.config/_typos.toml | 1 + template/_typos.toml | 1 - template/template/.config/_typos.toml | 1 + template/template/_typos.toml | 1 - uv.lock | 8 ++++---- 10 files changed, 10 insertions(+), 9 deletions(-) rename _typos.toml => .config/_typos.toml (100%) create mode 120000 template/.config/_typos.toml delete mode 120000 template/_typos.toml create mode 120000 template/template/.config/_typos.toml delete mode 120000 template/template/_typos.toml diff --git a/_typos.toml b/.config/_typos.toml similarity index 100% rename from _typos.toml rename to .config/_typos.toml diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3756d9fe8..21c162faa 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -69,5 +69,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" - // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 360bc892 # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 9a17d832 # spellchecker:disable-line } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6c4f7fb51..1e50699df 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,6 +45,7 @@ repos: rev: 359573a3ff38f71c93e160f2ace96d118f6ff42e # frozen: v1 hooks: - id: typos + args: [--config, .config/_typos.toml] exclude: | (?x)^( .*\.min.js| diff --git a/extensions/context.py b/extensions/context.py index 7765738cd..76367b75c 100644 --- a/extensions/context.py +++ b/extensions/context.py @@ -63,7 +63,7 @@ def hook( # noqa: PLR0915 # yes, this is a lot of statements, but it's all just context["pytest_asyncio_version"] = ">=1.4.0" context["pytest_timeout_version"] = ">=2.4.0" context["pytest_reserial_version"] = ">=0.6.1" - context["python_faker_version"] = ">=40.23.0" + context["python_faker_version"] = ">=40.28.1" ####### context["nuxt_ui_version"] = "^4.8.1" context["nuxt_version"] = "^4.4.6" diff --git a/pyproject.toml b/pyproject.toml index ed83e4b78..249bb70b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dependencies = [ "pytest>=9.1.1", "pytest-cov>=7.1.0", "pytest-randomly>=4.1.0", - "faker>=40.23.0", + "faker>=40.28.1", "pyright[nodejs]>=1.1.411", "pyrefly>=1.1.1", "mutmut>=3.6.0", diff --git a/template/.config/_typos.toml b/template/.config/_typos.toml new file mode 120000 index 000000000..8ce792db1 --- /dev/null +++ b/template/.config/_typos.toml @@ -0,0 +1 @@ +../../.config/_typos.toml \ No newline at end of file diff --git a/template/_typos.toml b/template/_typos.toml deleted file mode 120000 index 2264b5db8..000000000 --- a/template/_typos.toml +++ /dev/null @@ -1 +0,0 @@ -../_typos.toml \ No newline at end of file diff --git a/template/template/.config/_typos.toml b/template/template/.config/_typos.toml new file mode 120000 index 000000000..d5b8d0dca --- /dev/null +++ b/template/template/.config/_typos.toml @@ -0,0 +1 @@ +../../../.config/_typos.toml \ No newline at end of file diff --git a/template/template/_typos.toml b/template/template/_typos.toml deleted file mode 120000 index 15e4306a8..000000000 --- a/template/template/_typos.toml +++ /dev/null @@ -1 +0,0 @@ -../../_typos.toml \ No newline at end of file diff --git a/uv.lock b/uv.lock index 1722db75e..355a6a101 100644 --- a/uv.lock +++ b/uv.lock @@ -82,7 +82,7 @@ dependencies = [ requires-dist = [ { name = "copier", specifier = "==9.16.0" }, { name = "copier-template-extensions", specifier = "==0.3.3" }, - { name = "faker", specifier = ">=40.23.0" }, + { name = "faker", specifier = ">=40.28.1" }, { name = "mutmut", specifier = ">=3.6.0" }, { name = "pyrefly", specifier = ">=1.1.1" }, { name = "pyright", extras = ["nodejs"], specifier = ">=1.1.411" }, @@ -182,14 +182,14 @@ wheels = [ [[package]] name = "faker" -version = "40.23.0" +version = "40.28.1" source = { registry = "https://pypi.org/simple/" } dependencies = [ { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/d6/fc071e5754815d9058e12ab549cc88e90f8f4ecf4dc33b6b750cdf4b622d/faker-40.23.0.tar.gz", hash = "sha256:f135e563f1f95f19346bb680bc2e43570bc43b7893e566023746f51f32c69dfc", size = 1972975, upload-time = "2026-06-10T20:53:21.611Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/2d/3ead106cef42eab305e15f9c089dcc68a40387d5ce04af18585af4ebbb44/faker-40.28.1.tar.gz", hash = "sha256:2a63fb51abab8790636d4030a094cf942404cbccc9c288d1cad70e2e3e9ecd58", size = 2022717, upload-time = "2026-07-01T22:23:43.763Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/5f/824e6fb3e9d63408151dc9173994fa65bde620a67dde3a59354f5aecd497/faker-40.23.0-py3-none-any.whl", hash = "sha256:775922453e54afa42eaf60eac478fa3a969357f224d09a8022b93e3ad88f18ae", size = 2013046, upload-time = "2026-06-10T20:53:19.226Z" }, + { url = "https://files.pythonhosted.org/packages/92/a6/6111b9f13c1564b0e2f5dbeeb611fd00dc731e6add2336f62b798598c73d/faker-40.28.1-py3-none-any.whl", hash = "sha256:e8d3f5c469100a553d246dce7937c291308068a5ec6c9c3a228d7878b50720be", size = 2061052, upload-time = "2026-07-01T22:23:41.946Z" }, ] [[package]] From 3aceda4d2dd797090b7f0e3330b54d46f7e3c275 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Thu, 9 Jul 2026 12:30:50 +0000 Subject: [PATCH 11/16] move claude --- .claude/CLAUDE.md | 1 + .devcontainer/devcontainer.json | 2 +- .pre-commit-config.yaml | 2 +- CLAUDE.md | 1 - template/template/.claude/CLAUDE.md | 1 + template/template/CLAUDE.md | 1 - template/{% if install_claude_cli %}CLAUDE.md{% endif %} | 1 - 7 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 .claude/CLAUDE.md delete mode 100644 CLAUDE.md create mode 120000 template/template/.claude/CLAUDE.md delete mode 120000 template/template/CLAUDE.md delete mode 120000 template/{% if install_claude_cli %}CLAUDE.md{% endif %} diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 000000000..dba71e970 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1 @@ +@../AGENTS.md diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 21c162faa..7f1aa505d 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -69,5 +69,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" - // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 9a17d832 # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): d7e8e3d5 # spellchecker:disable-line } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1e50699df..915869774 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: rev: 359573a3ff38f71c93e160f2ace96d118f6ff42e # frozen: v1 hooks: - id: typos - args: [--config, .config/_typos.toml] + args: [--write-changes, --force-exclude, --config, .config/_typos.toml] exclude: | (?x)^( .*\.min.js| diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 43c994c2d..000000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -@AGENTS.md diff --git a/template/template/.claude/CLAUDE.md b/template/template/.claude/CLAUDE.md new file mode 120000 index 000000000..86a7b102c --- /dev/null +++ b/template/template/.claude/CLAUDE.md @@ -0,0 +1 @@ +../../../.claude/CLAUDE.md \ No newline at end of file diff --git a/template/template/CLAUDE.md b/template/template/CLAUDE.md deleted file mode 120000 index 6100270f8..000000000 --- a/template/template/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -../../CLAUDE.md \ No newline at end of file diff --git a/template/{% if install_claude_cli %}CLAUDE.md{% endif %} b/template/{% if install_claude_cli %}CLAUDE.md{% endif %} deleted file mode 120000 index 949a29f15..000000000 --- a/template/{% if install_claude_cli %}CLAUDE.md{% endif %} +++ /dev/null @@ -1 +0,0 @@ -../CLAUDE.md \ No newline at end of file From 7c44af1eaa32e9f13594f97d69e7786b96b9e661 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Thu, 9 Jul 2026 12:54:01 +0000 Subject: [PATCH 12/16] prettier --- .prettierrc => .config/.prettierrc | 0 .devcontainer/devcontainer.json | 3 ++- .pre-commit-config.yaml | 1 + template/.config/.prettierrc | 1 + template/.devcontainer/devcontainer.json.jinja-base | 1 + template/.prettierrc | 1 - template/template/.config/.prettierrc | 1 + template/template/.prettierrc | 1 - 8 files changed, 6 insertions(+), 3 deletions(-) rename .prettierrc => .config/.prettierrc (100%) create mode 120000 template/.config/.prettierrc delete mode 120000 template/.prettierrc create mode 120000 template/template/.config/.prettierrc delete mode 120000 template/template/.prettierrc diff --git a/.prettierrc b/.config/.prettierrc similarity index 100% rename from .prettierrc rename to .config/.prettierrc diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7f1aa505d..6292d5052 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -59,6 +59,7 @@ // TODO: see if there's a way to specify different configurations for different folders "ruff.configuration": "/workspaces/copier-base-template/.config/ruff-non-src.toml", // use the non-src configuration since it's less restrictive and won't show false positives and underline things "python.pyrefly.configPath": "/workspaces/copier-base-template/.config/pyrefly.toml", + "prettier.configPath": ".config/.prettierrc", "[jsonc][json]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true @@ -69,5 +70,5 @@ "initializeCommand": "sh .devcontainer/initialize-command.sh", "onCreateCommand": "sh .devcontainer/on-create-command.sh", "postStartCommand": "sh .devcontainer/post-start-command.sh" - // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): d7e8e3d5 # spellchecker:disable-line + // Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 007af1ff # spellchecker:disable-line } diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 915869774..2c95472b0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -109,6 +109,7 @@ repos: rev: 515f543f5718ebfd6ce22e16708bb32c68ff96e1 # frozen: v3.8.3 hooks: - id: prettier + args: [--config, .config/.prettierrc] # TODO: get template YAML and MD files more in line with prettier expectations so we can start using prettier on those too exclude: | (?x)^( diff --git a/template/.config/.prettierrc b/template/.config/.prettierrc new file mode 120000 index 000000000..b7bd64859 --- /dev/null +++ b/template/.config/.prettierrc @@ -0,0 +1 @@ +../../.config/.prettierrc \ No newline at end of file diff --git a/template/.devcontainer/devcontainer.json.jinja-base b/template/.devcontainer/devcontainer.json.jinja-base index 8b9e96ebb..731d23455 100644 --- a/template/.devcontainer/devcontainer.json.jinja-base +++ b/template/.devcontainer/devcontainer.json.jinja-base @@ -74,6 +74,7 @@ // TODO: see if there's a way to specify different configurations for different folders "ruff.configuration": "/workspaces/{% endraw %}{{ repo_name }}{% raw %}/.config/ruff-non-src.toml", // use the non-src configuration since it's less restrictive and won't show false positives and underline things "python.pyrefly.configPath": "/workspaces/{% endraw %}{{ repo_name }}{% raw %}/.config/pyrefly.toml",{% endraw %}{% endif %}{% raw %} + "prettier.configPath": ".config/.prettierrc", "[jsonc][json][javascript][typescript][graphql][css][scss][html][vue]": { "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true diff --git a/template/.prettierrc b/template/.prettierrc deleted file mode 120000 index 5b5fb8c8b..000000000 --- a/template/.prettierrc +++ /dev/null @@ -1 +0,0 @@ -../.prettierrc \ No newline at end of file diff --git a/template/template/.config/.prettierrc b/template/template/.config/.prettierrc new file mode 120000 index 000000000..b7bd64859 --- /dev/null +++ b/template/template/.config/.prettierrc @@ -0,0 +1 @@ +../../.config/.prettierrc \ No newline at end of file diff --git a/template/template/.prettierrc b/template/template/.prettierrc deleted file mode 120000 index 5b5fb8c8b..000000000 --- a/template/template/.prettierrc +++ /dev/null @@ -1 +0,0 @@ -../.prettierrc \ No newline at end of file From b5573351a7e2ec02dda769bca31b0802e5a4c5b0 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Thu, 9 Jul 2026 13:33:05 +0000 Subject: [PATCH 13/16] answers file --- .github/workflows/ci.yaml | 2 +- copier.yaml | 6 +++-- .../copier_tasks/copier_provenance.py | 5 +++- template/copier.yml.jinja-base | 2 ++ .../copier_tasks/test_copier_provenance.py | 25 ++++++++++++++++++- 5 files changed, 35 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b6b50eea2..442495503 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -153,7 +153,7 @@ jobs: - name: Confirm instantiated template has copier answers file run: | - if [ ! -f "./new-template/.copier-answers.yml" ]; then + if [ ! -f "./new-template/.config/.copier-answers.yml" ]; then echo "Copier answers file not found in instantiated template location" exit 1 fi diff --git a/copier.yaml b/copier.yaml index 67e4b993c..fa54e30e1 100644 --- a/copier.yaml +++ b/copier.yaml @@ -85,8 +85,8 @@ template_publishes_releases: copier_answers_directory: type: str - help: What directory within the instantiated template contains the .copier-answers.yml file? Use '.' for the root, or a subdirectory path (e.g. 'frontend'). - default: "." + help: What directory within the instantiated template contains the .copier-answers.yml file? Use '.' for the root, or a subdirectory path (e.g. '.frontend'). + default: ".config" _tasks: - command: | @@ -131,6 +131,8 @@ _tasks: _min_copier_version: "9.8" +_answers_file: "{{ copier_answers_directory }}/.copier-answers.yml" + _subdirectory: template _templates_suffix: .jinja-base diff --git a/src/copier_base_template/copier_tasks/copier_provenance.py b/src/copier_base_template/copier_tasks/copier_provenance.py index 0bbc1c8ca..39780e260 100644 --- a/src/copier_base_template/copier_tasks/copier_provenance.py +++ b/src/copier_base_template/copier_tasks/copier_provenance.py @@ -225,7 +225,10 @@ def apply_file_markers( def _read_parent_src(src_template_directory: Path) -> str | None: - answers_path = src_template_directory.parent / ".copier-answers.yml" + template_root = src_template_directory.parent + answers_path = template_root / ".config" / ".copier-answers.yml" + if not answers_path.exists(): + answers_path = template_root / ".copier-answers.yml" if not answers_path.exists(): return None text = answers_path.read_text(encoding="utf-8") diff --git a/template/copier.yml.jinja-base b/template/copier.yml.jinja-base index 0e32002d4..c134fcfc6 100644 --- a/template/copier.yml.jinja-base +++ b/template/copier.yml.jinja-base @@ -216,6 +216,8 @@ _tasks: # Additional Settings _min_copier_version: "9.8" +_answers_file: "{% endraw %}{{ copier_answers_directory }}{% raw %}/.copier-answers.yml" + _subdirectory: template _templates_suffix: .jinja diff --git a/tests/unit/copier_tasks/test_copier_provenance.py b/tests/unit/copier_tasks/test_copier_provenance.py index 274196b19..a41358bda 100644 --- a/tests/unit/copier_tasks/test_copier_provenance.py +++ b/tests/unit/copier_tasks/test_copier_provenance.py @@ -559,7 +559,30 @@ def test_manifest_structure_is_valid(self, tmp_path: Path) -> None: assert isinstance(entry["managed_files"], list) assert all(isinstance(f, str) for f in entry["managed_files"]) - def test_manifest_parent_src_discovered_from_copier_answers(self, tmp_path: Path) -> None: + def test_manifest_parent_src_discovered_from_config_copier_answers(self, tmp_path: Path) -> None: + template_dir = tmp_path / "template" + template_dir.mkdir() + config_dir = tmp_path / ".config" + config_dir.mkdir() + _ = (config_dir / ".copier-answers.yml").write_text( + "_src_path: https://github.com/org/parent-template\n", + encoding="utf-8", + ) + + dst_dir = tmp_path / "destination" + dst_dir.mkdir() + + _ = _run_script( + src_template_dir=template_dir, + dst_dir=dst_dir, + template_src="https://github.com/org/child-template", + ) + + manifest = json.loads((dst_dir / ".copier-managed-files.json").read_text(encoding="utf-8")) + entry = manifest["templates"][0] + assert entry["parent_src"] == "https://github.com/org/parent-template" + + def test_manifest_parent_src_falls_back_to_root_copier_answers(self, tmp_path: Path) -> None: template_dir = tmp_path / "template" template_dir.mkdir() _ = (tmp_path / ".copier-answers.yml").write_text( From 55119319b4f2d261fdb9c8b6243289022905f2f3 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Thu, 9 Jul 2026 15:34:26 +0000 Subject: [PATCH 14/16] move config --- copier.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/copier.yaml b/copier.yaml index fa54e30e1..2eddb8c32 100644 --- a/copier.yaml +++ b/copier.yaml @@ -85,7 +85,7 @@ template_publishes_releases: copier_answers_directory: type: str - help: What directory within the instantiated template contains the .copier-answers.yml file? Use '.' for the root, or a subdirectory path (e.g. '.frontend'). + help: What directory within the instantiated template contains the .copier-answers.yml file? Use '.' for the root, or a subdirectory path (e.g. 'frontend'). default: ".config" _tasks: @@ -131,7 +131,7 @@ _tasks: _min_copier_version: "9.8" -_answers_file: "{{ copier_answers_directory }}/.copier-answers.yml" +_answers_file: ".config/.copier-answers.yml" _subdirectory: template From 46f55805e643ce7a71912fb132c63c5583931dfe Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Thu, 9 Jul 2026 16:50:09 +0000 Subject: [PATCH 15/16] template --- template/README.md.jinja-base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/README.md.jinja-base b/template/README.md.jinja-base index 359a10ede..ebd889029 100644 --- a/template/README.md.jinja-base +++ b/template/README.md.jinja-base @@ -23,4 +23,4 @@ To create a new repository using this template: ## Updating from the template This repository uses a copier template. To pull in the latest updates from the template, use the command: -`copier update --trust --conflict rej --defaults`{% endraw %} +`copier update --answers-file .config/.copier-answers.yml --trust --conflict rej --defaults`{% endraw %} From ddb4a815b3ded22f717375d490ca9e0494bdfab5 Mon Sep 17 00:00:00 2001 From: Eli Fine Date: Thu, 9 Jul 2026 17:24:31 +0000 Subject: [PATCH 16/16] move managed --- .coderabbit.yaml | 4 +- AGENTS.md | 2 +- .../copier_tasks/copier_provenance.py | 21 +++++++--- .../copier_tasks/test_copier_provenance.py | 38 +++++++++---------- 4 files changed, 38 insertions(+), 27 deletions(-) diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 29e580a93..083c17fdf 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -9,10 +9,10 @@ reviews: instructions: "Never express concerns about potential ruff rule violations, even those rules mentioned in other agent instructions; a pre-commit hook already runs a ruff check. Do not warn about unnecessary super().__init__() calls; pyright prefers those to be present. Do not warn about missing type hints; a pre-commit hook already checks for that." - path: "**/.copier-answers.yml" instructions: "Do not comment about the `_commit` value needing to be a clean release tag. A CI job will fail if that is not the case." - - path: ".copier-managed-files.json" + - path: ".config/.copier-managed-files.json" instructions: "This file is auto-generated by copier on every `copier copy` or `copier update`. Do not edit manually and do not request changes to its content." instructions: | - If any changed files are listed in .copier-managed-files.json, add a single summary comment + If any changed files are listed in .config/.copier-managed-files.json, add a single summary comment (not one per file) noting which files are template-managed and that the changes may warrant backporting. The manifest has a "templates" array — each entry has "src" (template URL), optional "parent_src" (its parent template, forming a chain), and "managed_files". If a file diff --git a/AGENTS.md b/AGENTS.md index b3157a918..86b00d393 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -170,7 +170,7 @@ For more details, see README.md and docs/QUICKSTART.md. # Copier-Managed Files -Files generated from upstream copier templates are listed in `.copier-managed-files.json` (if present). +Files generated from upstream copier templates are listed in `.config/.copier-managed-files.json` (if present). The manifest has a `templates` array — one entry per template in the chain. Each entry has `src` (the template URL), `managed_files` (files it placed), and optionally `parent_src` (the template that generated it, forming the chain). diff --git a/src/copier_base_template/copier_tasks/copier_provenance.py b/src/copier_base_template/copier_tasks/copier_provenance.py index 39780e260..6847d2ec1 100644 --- a/src/copier_base_template/copier_tasks/copier_provenance.py +++ b/src/copier_base_template/copier_tasks/copier_provenance.py @@ -49,9 +49,19 @@ class CommentFormat: ".node-version": CommentFormat("none", "none"), } +_MANIFEST_RELPATH = Path(".config") / ".copier-managed-files.json" + + +def _find_manifest(base_directory: Path) -> Path: + config_manifest = base_directory / _MANIFEST_RELPATH + if config_manifest.exists(): + return config_manifest + return base_directory / ".copier-managed-files.json" + + _HEADER_BASE = """\ ============== WARNING ============================================================================== -File is managed by a copier template. See .copier-managed-files.json for details. +File is managed by a copier template. See .config/.copier-managed-files.json for details. You are welcome to make changes to this file in your repo if they are custom to your project, but if the change should be shared with other projects, please backport it to the template repo. @@ -65,7 +75,7 @@ def _build_header(template_src: str) -> str: lines: list[str] = list(_HEADER_BASE.split("\n")) # Replace the generic "File is managed" line with two lines: URL line + "See ..." line. lines[1] = f"File is managed by copier template: {template_src}" - lines.insert(2, "See .copier-managed-files.json for details.") + lines.insert(2, "See .config/.copier-managed-files.json for details.") return "\n".join(lines) @@ -243,7 +253,8 @@ def update_manifest( managed_files: list[str], parent_src: str | None = None, ) -> None: - manifest_path = dst_directory / ".copier-managed-files.json" + manifest_path = dst_directory / _MANIFEST_RELPATH + manifest_path.parent.mkdir(parents=True, exist_ok=True) existing: dict[str, Any] = {} if manifest_path.exists(): @@ -272,13 +283,13 @@ def main() -> None: args = parser.parse_args() # header_src drives what URL appears in file headers (empty → generic "managed by a copier template" text). - # manifest_src is the key written to .copier-managed-files.json and is always non-empty. + # manifest_src is the key written to .config/.copier-managed-files.json and is always non-empty. header_src = args.template_src manifest_src = args.template_src or str(args.src_template_dir) ancestor_managed_by_src: dict[str, set[str]] = {} ancestor_parent_by_src: dict[str, str] = {} - ancestor_manifest_path = args.src_template_dir.parent / ".copier-managed-files.json" + ancestor_manifest_path = _find_manifest(args.src_template_dir.parent) if ancestor_manifest_path.exists(): data: dict[str, Any] = json.loads(ancestor_manifest_path.read_text(encoding="utf-8")) # The ancestor manifest may contain paths with a "template/" prefix (from self-stamp diff --git a/tests/unit/copier_tasks/test_copier_provenance.py b/tests/unit/copier_tasks/test_copier_provenance.py index a41358bda..d183fef53 100644 --- a/tests/unit/copier_tasks/test_copier_provenance.py +++ b/tests/unit/copier_tasks/test_copier_provenance.py @@ -12,7 +12,7 @@ expected_hash_comment = """\ # ============== WARNING ============================================================================== -# File is managed by a copier template. See .copier-managed-files.json for details. +# File is managed by a copier template. See .config/.copier-managed-files.json for details. # # You are welcome to make changes to this file in your repo if they are custom to your project, # but if the change should be shared with other projects, please backport it to the template repo. @@ -20,7 +20,7 @@ expected_batch_comment = """\ REM ============== WARNING ============================================================================== -REM File is managed by a copier template. See .copier-managed-files.json for details. +REM File is managed by a copier template. See .config/.copier-managed-files.json for details. REM REM You are welcome to make changes to this file in your repo if they are custom to your project, REM but if the change should be shared with other projects, please backport it to the template repo. @@ -29,7 +29,7 @@ expected_block_comment = """\ /* * ============== WARNING ============================================================================== - * File is managed by a copier template. See .copier-managed-files.json for details. + * File is managed by a copier template. See .config/.copier-managed-files.json for details. * * You are welcome to make changes to this file in your repo if they are custom to your project, * but if the change should be shared with other projects, please backport it to the template repo. @@ -39,7 +39,7 @@ expected_jinja_comment = """\ {# ============== WARNING ============================================================================== - File is managed by a copier template. See .copier-managed-files.json for details. + File is managed by a copier template. See .config/.copier-managed-files.json for details. You are welcome to make changes to this file in your repo if they are custom to your project, but if the change should be shared with other projects, please backport it to the template repo. @@ -49,7 +49,7 @@ expected_markdown_comment = """\