2525
2626__all__ = [
2727 "SAFE_PRESERVED_NAMES" ,
28- "check_dest_safe" ,
2928 "create_host" ,
3029 "create_module" ,
3130 "create_workspace" ,
3635_TEMPLATES_PACKAGE = "simple_module_cli.templates"
3736_PACKAGE_PATH_TOKEN = "__PACKAGE__"
3837
39- # Top-level entries we tolerate at the scaffold target — typical leftovers
40- # from ``git init`` / ``gh repo create`` / IDE setup. The scaffold's copy is
41- # skipped (the user's file wins) and the preserved path is surfaced so users
42- # can merge content in by hand if they care.
38+ # Pre-existing entries we tolerate at a scaffold target — typical leftovers
39+ # from ``git init`` / ``gh repo create`` / IDE setup.
4340SAFE_PRESERVED_NAMES = frozenset (
4441 {".git" , ".gitignore" , ".gitattributes" , ".editorconfig" , ".DS_Store" }
4542 | {".claude" , ".vscode" , ".idea" }
4946)
5047
5148
52- def check_dest_safe (target : Path ) -> None :
53- """Refuse a non-empty target unless every entry is in ``SAFE_PRESERVED_NAMES``."""
54- if not target .exists ():
55- return
56- unexpected = sorted (p .name for p in target .iterdir () if p .name not in SAFE_PRESERVED_NAMES )
57- if unexpected :
58- raise FileExistsError (
59- f"Destination { target } exists and contains files that would collide "
60- f"with the scaffold: { ', ' .join (unexpected )} . "
61- "Move them aside or choose another path."
62- )
63-
64-
6549def _module_to_pypi_name (name : str ) -> str :
6650 return f"simple_module_{ name .lower ()} "
6751
@@ -76,15 +60,15 @@ def _iter_template_files(template_root: Path):
7660 yield path
7761
7862
79- def _require_empty_dest (dest : Path , * , allowed : frozenset [str ] = frozenset ()) -> None :
63+ def _require_empty_dest (dest : Path , * , preserve_existing : frozenset [str ] = frozenset ()) -> None :
8064 """Refuse a non-empty destination unless every top-level entry is allowed.
8165
82- ``allowed `` is matched against the *name* of each top-level entry, so callers
83- can permit common pre-existing files (``.git``, ``README.md``, ...) without
84- silently overwriting unrelated user content.
66+ ``preserve_existing `` is matched against the *name* of each top-level entry,
67+ so callers can permit common pre-existing files (``.git``, ``README.md``,
68+ ...) without silently overwriting unrelated user content.
8569 """
8670 if dest .exists ():
87- unexpected = sorted (p .name for p in dest .iterdir () if p .name not in allowed )
71+ unexpected = sorted (p .name for p in dest .iterdir () if p .name not in preserve_existing )
8872 if unexpected :
8973 raise FileExistsError (
9074 f"Destination { dest } exists and contains files that would collide "
@@ -108,12 +92,7 @@ def _apply_template_files(
10892 path_rewrites : Mapping [str , str ] | None = None ,
10993 preserve_existing : frozenset [str ] = frozenset (),
11094) -> list [Path ]:
111- """Write template files into ``dest``; return paths that were preserved.
112-
113- When a top-level path segment matches ``preserve_existing`` *and* the target
114- file already exists, the scaffold's copy is skipped and the path is
115- returned so the caller can warn the user.
116- """
95+ """Write template files into ``dest``; return paths skipped to preserve the user's copy."""
11796 preserved : list [Path ] = []
11897 for src in _iter_template_files (src_root ):
11998 rel_str = str (src .relative_to (src_root ))
@@ -141,26 +120,27 @@ def create_workspace(
141120 name : str ,
142121 template_root : Path | None = None ,
143122 * ,
144- tolerate_existing : frozenset [str ] = frozenset (),
123+ preserve_existing : frozenset [str ] = frozenset (),
145124) -> list [Path ]:
146- """Materialize the workspace-root shell at ``dest``.
125+ """Materialize the workspace-root shell at ``dest``; return preserved paths .
147126
148127 Lays down the top-level ``pyproject.toml`` (uv workspace), ``package.json``
149128 (npm workspace), ``Makefile`` (delegates to host), ``.env.example``,
150129 ``.gitignore``, and ``README.md``. Does NOT create the host or any
151130 modules — those go under ``dest/host`` and ``dest/modules/`` afterwards.
152131
153- ``tolerate_existing `` lists top-level entry names (``.git``, ``README.md``,
132+ ``preserve_existing `` lists top-level entry names (``.git``, ``README.md``,
154133 ...) that may already exist in ``dest``; the scaffold's copy is skipped and
155- the preserved path is included in the returned list.
134+ the preserved path is included in the returned list. Other pre-existing
135+ entries raise ``FileExistsError``.
156136 """
157137 dest = Path (dest )
158- dest . mkdir ( parents = True , exist_ok = True )
138+ _require_empty_dest ( dest , preserve_existing = preserve_existing )
159139 preserved = _apply_template_files (
160140 _resolve_template_root ("workspace" , template_root ),
161141 dest ,
162142 {"{{HOST_NAME}}" : to_kebab_case (name )},
163- preserve_existing = tolerate_existing ,
143+ preserve_existing = preserve_existing ,
164144 )
165145 logger .info ("Scaffolded workspace root at %s" , dest )
166146 return preserved
@@ -173,17 +153,14 @@ def create_host(
173153 template_root : Path | None = None ,
174154 framework_version : str = "*" ,
175155 * ,
176- tolerate_existing : frozenset [str ] = frozenset (),
156+ preserve_existing : frozenset [str ] = frozenset (),
177157) -> list [Path ]:
178158 """Scaffold a host project at ``dest``; return preserved pre-existing paths.
179159
180- ``tolerate_existing`` lists top-level entry names (``.git``, ``README.md``,
181- ...) that may already exist in ``dest``. Their scaffold counterparts are
182- skipped and returned so the caller can surface a notice. Anything else
183- pre-existing raises ``FileExistsError``.
160+ ``preserve_existing`` semantics match :func:`create_workspace`.
184161 """
185162 dest = Path (dest )
186- _require_empty_dest (dest , allowed = tolerate_existing )
163+ _require_empty_dest (dest , preserve_existing = preserve_existing )
187164 module_dep_lines = "\n " .join (f' "{ _module_to_pypi_name (m )} >=0.1,<1.0",' for m in modules )
188165 preserved = _apply_template_files (
189166 _resolve_template_root ("host" , template_root ),
@@ -193,7 +170,7 @@ def create_host(
193170 "{{MODULE_DEPS}}" : module_dep_lines ,
194171 "{{FRAMEWORK_VERSION}}" : framework_version ,
195172 },
196- preserve_existing = tolerate_existing ,
173+ preserve_existing = preserve_existing ,
197174 )
198175 logger .info (
199176 "Scaffolded host '%s' at %s (modules: %s)" , name , dest , ", " .join (modules ) or "<none>"
0 commit comments