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
1 change: 1 addition & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@../AGENTS.md
4 changes: 2 additions & 2 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
5 changes: 3 additions & 2 deletions pyrefly.mutmut.toml → .config/pyrefly.mutmut.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
use-ignore-files = false
disable-project-excludes-heuristics = true
python-platform = "linux"
project-includes = ["mutants/src/**"]
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/**",
]
17 changes: 9 additions & 8 deletions pyrefly.toml → .config/pyrefly.toml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
File renamed without changes.
File renamed without changes.
12 changes: 7 additions & 5 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -57,7 +57,9 @@
"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
"python.pyrefly.configPath": "/workspaces/copier-base-template/.config/pyrefly.toml",
"prettier.configPath": ".config/.prettierrc",
"[jsonc][json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
Expand All @@ -68,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): f239a981 # spellchecker:disable-line
// Devcontainer context hash (do not manually edit this, it's managed by a pre-commit hook): 007af1ff # spellchecker:disable-line
}
File renamed without changes.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md → .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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).

---

Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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: |
Expand Down
11 changes: 7 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ repos:
rev: 359573a3ff38f71c93e160f2ace96d118f6ff42e # frozen: v1
hooks:
- id: typos
args: [--write-changes, --force-exclude, --config, .config/_typos.toml]
exclude: |
(?x)^(
.*\.min.js|
Expand Down Expand Up @@ -108,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)^(
Expand Down Expand Up @@ -234,7 +236,7 @@ repos:
hooks:
- id: ruff
name: ruff-src
args: [--fix, --config=./ruff.toml]
args: [--fix, --config=./.config/ruff.toml]
files: src/.+\.py$
exclude: |
(?x)^(
Expand All @@ -244,7 +246,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)^(
Expand All @@ -253,6 +255,7 @@ repos:
template/.*|
)$
- id: ruff-format
args: [--config=./.config/ruff.toml]
exclude: |
(?x)^(
.*/generated/graphql/.*|
Expand All @@ -272,7 +275,7 @@ repos:
template/.*|
)$
args:
- --rcfile=./pylintrc.toml
- --rcfile=./.config/pylintrc.toml
verbose: true

- repo: local
Expand All @@ -299,7 +302,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)^(
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
1 change: 0 additions & 1 deletion CLAUDE.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://code.claude.com/docs/en/claude-directory#file-reference so based on this it seems it really wants CLAUDE.md to be at the root to apply at the fully project. But im guessing you didn't notice any difference b/c AGENTS.md is staying at the root and it likely picks that one up. Just something to think about if we do another pass and try to move more things from that root location.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

This file was deleted.

10 changes: 6 additions & 4 deletions copier.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,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').
default: "."
default: ".config"

_tasks:
- command: |
Expand Down Expand Up @@ -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' \
Expand All @@ -131,6 +131,8 @@ _tasks:

_min_copier_version: "9.8"

_answers_file: ".config/.copier-answers.yml"

_subdirectory: template

_templates_suffix: .jinja-base
Expand Down
7 changes: 4 additions & 3 deletions extensions/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -62,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"
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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+',
Expand Down
2 changes: 1 addition & 1 deletion pytest.ini
Original file line number Diff line number Diff line change
@@ -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
Expand Down
26 changes: 20 additions & 6 deletions src/copier_base_template/copier_tasks/copier_provenance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)


Expand Down Expand Up @@ -225,7 +235,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")
Expand All @@ -240,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():
Expand Down Expand Up @@ -269,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
Expand Down
20 changes: 18 additions & 2 deletions src/copier_base_template/openapi/openapi_schema_simplifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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"]


Expand Down
1 change: 1 addition & 0 deletions template/.config/.coveragerc
1 change: 1 addition & 0 deletions template/.config/.prettierrc
1 change: 1 addition & 0 deletions template/.config/_typos.toml
1 change: 1 addition & 0 deletions template/.config/pylintrc.toml
1 change: 1 addition & 0 deletions template/.config/pyrefly.toml
Loading