simple-module-cli 0.0.19__tar.gz → 0.0.20__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/.gitignore +1 -0
  2. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/PKG-INFO +1 -1
  3. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/pyproject.toml +1 -1
  4. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/app_project.py +9 -5
  5. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/cli.py +25 -2
  6. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/scaffolding.py +35 -0
  7. simple_module_cli-0.0.20/tests/test_create_module_ci_skip.py +107 -0
  8. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/LICENSE +0 -0
  9. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/README.md +0 -0
  10. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/__init__.py +0 -0
  11. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/_env.py +0 -0
  12. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/case.py +0 -0
  13. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/catalog.py +0 -0
  14. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/new.py +0 -0
  15. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/package_update.py +0 -0
  16. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/pins.py +0 -0
  17. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/plugins.py +0 -0
  18. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/recipes.py +0 -0
  19. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills/README.md +0 -0
  20. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills/simple-module-cli/SKILL.md +0 -0
  21. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills/simple-module-conventions/SKILL.md +0 -0
  22. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills/simple-module-creating/SKILL.md +0 -0
  23. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills/simple-module-database/SKILL.md +0 -0
  24. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills/simple-module-doctor/SKILL.md +0 -0
  25. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills/simple-module-inertia-pages/SKILL.md +0 -0
  26. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills/simple-module-locales/SKILL.md +0 -0
  27. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills/simple-module-migrations/SKILL.md +0 -0
  28. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills/simple-module-registries/SKILL.md +0 -0
  29. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills/simple-module-testing/SKILL.md +0 -0
  30. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/skills_cmd.py +0 -0
  31. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/.env.example +0 -0
  32. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/.gitignore +0 -0
  33. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/Makefile +0 -0
  34. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/README.md.tpl +0 -0
  35. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/_optional/background_tasks/Makefile.snippet +0 -0
  36. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/_optional/background_tasks/docker-compose.yml +0 -0
  37. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/_optional/background_tasks/host.Dockerfile +0 -0
  38. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/_optional/background_tasks/run_worker.py +0 -0
  39. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/_optional/background_tasks/worker.Dockerfile +0 -0
  40. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/alembic.ini +0 -0
  41. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/client_app/app.tsx +0 -0
  42. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/client_app/main.tsx +0 -0
  43. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/client_app/package.json.tpl +0 -0
  44. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/client_app/pages/Error.tsx +0 -0
  45. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/client_app/pages/Landing.tsx +0 -0
  46. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/client_app/pages.ts +0 -0
  47. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/client_app/styles.css +0 -0
  48. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/client_app/tsconfig.json +0 -0
  49. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/client_app/vite.config.ts +0 -0
  50. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/main.py +0 -0
  51. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/migrations/env.py +0 -0
  52. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/migrations/script.py.mako +0 -0
  53. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/migrations/versions/.gitkeep +0 -0
  54. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/pyproject.toml.tpl +0 -0
  55. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/routes.py +0 -0
  56. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/host/templates/index.html +0 -0
  57. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/.github/workflows/ci.yml +0 -0
  58. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/.github/workflows/publish.yml.tpl +0 -0
  59. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/.gitignore +0 -0
  60. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/README.md.tpl +0 -0
  61. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/__PACKAGE__/__init__.py +0 -0
  62. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/__PACKAGE__/endpoints/__init__.py +0 -0
  63. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/__PACKAGE__/endpoints/api.py.tpl +0 -0
  64. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/__PACKAGE__/module.py.tpl +0 -0
  65. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/__PACKAGE__/pages/.gitkeep +0 -0
  66. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/__PACKAGE__/services.py.tpl +0 -0
  67. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/__PACKAGE__/settings.py.tpl +0 -0
  68. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/package.json.tpl +0 -0
  69. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/pyproject.toml.tpl +0 -0
  70. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/tests/test_module.py.tpl +0 -0
  71. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/module/tsconfig.json.tpl +0 -0
  72. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/workspace/.env.example +0 -0
  73. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/workspace/.gitignore +0 -0
  74. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/workspace/Makefile +0 -0
  75. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/workspace/README.md.tpl +0 -0
  76. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/workspace/package.json.tpl +0 -0
  77. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/templates/workspace/pyproject.toml.tpl +0 -0
  78. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/simple_module_cli/wizard.py +0 -0
  79. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_build_packaging.py +0 -0
  80. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_case.py +0 -0
  81. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_cli_catalog.py +0 -0
  82. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_cli_new.py +0 -0
  83. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_cli_new_dest_tolerance.py +0 -0
  84. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_cli_new_regressions.py +0 -0
  85. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_cli_new_scaffold_layout.py +0 -0
  86. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_cli_package_update.py +0 -0
  87. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_cli_recipes.py +0 -0
  88. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_cli_wizard.py +0 -0
  89. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_env_helper.py +0 -0
  90. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_module_pages_manifest.py +0 -0
  91. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_no_framework_deps.py +0 -0
  92. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_plugin_discovery.py +0 -0
  93. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_scaffold_rollback.py +0 -0
  94. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_scaffolding_host.py +0 -0
  95. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_scaffolding_module.py +0 -0
  96. {simple_module_cli-0.0.19 → simple_module_cli-0.0.20}/tests/test_skills_cmd.py +0 -0
@@ -61,3 +61,4 @@ Thumbs.db
61
61
  .playwright-mcp/*
62
62
  host/client_app/.playwright-cli/*
63
63
  .superpowers/
64
+ .qa/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simple_module_cli
3
- Version: 0.0.19
3
+ Version: 0.0.20
4
4
  Summary: Standalone scaffolder for the SimpleModule framework — `smpy new`, `smpy create-module`, plugin host.
5
5
  Project-URL: Homepage, https://github.com/antosubash/simple_module_python
6
6
  Project-URL: Repository, https://github.com/antosubash/simple_module_python
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "simple_module_cli"
3
- version = "0.0.19"
3
+ version = "0.0.20"
4
4
  description = "Standalone scaffolder for the SimpleModule framework — `smpy new`, `smpy create-module`, plugin host."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -15,7 +15,6 @@ from __future__ import annotations
15
15
 
16
16
  import json as _json
17
17
  import secrets as _secrets
18
- import shutil as _shutil
19
18
  from collections.abc import Sequence
20
19
  from pathlib import Path
21
20
  from typing import Any
@@ -193,10 +192,15 @@ def _scaffold_sample_module(target: Path) -> None:
193
192
  # Pin the sample's framework deps to the exact framework version so the
194
193
  # workspace resolves (the template's >=1.0,<2.0 ranges don't exist on PyPI
195
194
  # pre-1.0). See GH #195.
196
- create_module(sample_dest, name=_SAMPLE_MODULE_NAME, framework_version=_FRAMEWORK_VERSION)
197
- # GitHub only reads workflows from the repo root, so the template's
198
- # .github/ is dead inside a workspace.
199
- _shutil.rmtree(sample_dest / ".github")
195
+ # The sample lives inside the workspace, so it gets no per-module .github/:
196
+ # GitHub only reads workflows from the repo root, where the template's
197
+ # .github/ would be dead anyway. See GH #210.
198
+ create_module(
199
+ sample_dest,
200
+ name=_SAMPLE_MODULE_NAME,
201
+ framework_version=_FRAMEWORK_VERSION,
202
+ include_ci=False,
203
+ )
200
204
  _seed_static_dist_placeholder(sample_dest / _SAMPLE_MODULE_NAME / "static" / "dist")
201
205
 
202
206
 
@@ -23,7 +23,7 @@ from simple_module_cli.package_update import package_update
23
23
  from simple_module_cli.plugins import discover_and_mount
24
24
  from simple_module_cli.scaffolding import create_host as _create_host
25
25
  from simple_module_cli.scaffolding import create_module as _create_module
26
- from simple_module_cli.scaffolding import resolve_framework_version
26
+ from simple_module_cli.scaffolding import is_inside_existing_repo, resolve_framework_version
27
27
  from simple_module_cli.skills_cmd import app as skills_app
28
28
 
29
29
  app = typer.Typer(
@@ -90,21 +90,44 @@ def create_module(
90
90
  Path | None,
91
91
  typer.Option("--dest", help="Destination dir. Defaults to ./simple_module_<name>."),
92
92
  ] = None,
93
+ standalone: Annotated[
94
+ bool,
95
+ typer.Option(
96
+ "--standalone",
97
+ help="Emit the module's own .github/ CI + PyPI publish workflows. "
98
+ "By default they are omitted when the module lands inside an "
99
+ "existing repo/host (nested workflows never run there).",
100
+ ),
101
+ ] = False,
93
102
  ) -> None:
94
103
  """Scaffold a publishable SimpleModule module package."""
95
104
  slug = to_kebab_case(name)
96
105
  package = slug.replace("-", "_")
97
106
  target = dest or Path.cwd() / f"simple_module_{package}"
107
+ # An in-repo module (the documented modules/* layout) gets no .github/: those
108
+ # nested workflows never run and publish.yml is a PyPI footgun. --standalone
109
+ # forces them for a module that lives in its own repo. See GH #210.
110
+ include_ci = standalone or not is_inside_existing_repo(target)
98
111
  try:
99
112
  # Pin framework deps to the installed framework version so the module
100
113
  # resolves against the app that created it (the template's >=1.0,<2.0
101
114
  # ranges don't exist on PyPI pre-1.0). See GH #195.
102
- _create_module(target, name=name, framework_version=resolve_framework_version())
115
+ _create_module(
116
+ target,
117
+ name=name,
118
+ framework_version=resolve_framework_version(),
119
+ include_ci=include_ci,
120
+ )
103
121
  except FileExistsError as exc:
104
122
  typer.echo(f"ERROR: {exc}", err=True)
105
123
  raise typer.Exit(code=1) from exc
106
124
 
107
125
  typer.echo(f"Created module 'simple_module_{package}' at {target}")
126
+ if not include_ci:
127
+ typer.echo(
128
+ "Skipped .github/ workflows: this module is inside an existing repo, "
129
+ "where nested workflows never run. Use --standalone to emit them."
130
+ )
108
131
  typer.echo("\nNext steps:")
109
132
  typer.echo(f" cd {target}")
110
133
  typer.echo(" uv sync --extra dev")
@@ -37,6 +37,7 @@ __all__ = [
37
37
  "create_host",
38
38
  "create_module",
39
39
  "create_workspace",
40
+ "is_inside_existing_repo",
40
41
  "pin_framework_deps",
41
42
  "resolve_framework_version",
42
43
  ]
@@ -61,6 +62,31 @@ def _module_to_pypi_name(name: str) -> str:
61
62
  return f"simple_module_{name.lower()}"
62
63
 
63
64
 
65
+ def is_inside_existing_repo(dest: Path) -> bool:
66
+ """Return True when ``dest`` lands inside an existing repo / host project.
67
+
68
+ A module scaffolded under an existing host application (the documented
69
+ monorepo ``modules/*`` layout) is an *in-repo* module: GitHub only runs
70
+ workflows from the repository-root ``.github/workflows/``, so a per-module
71
+ ``.github/`` is dead weight there — and the bundled ``publish.yml`` (which
72
+ publishes ``simple_module_<name>`` to PyPI on any ``v*`` tag) is a footgun if
73
+ it ever surfaces at the repo root. We detect this by walking up from
74
+ ``dest``'s parent for a ``.git`` directory or a ``pyproject.toml`` (an
75
+ existing repo / host / workspace member).
76
+
77
+ ``dest`` itself is *excluded* from the walk — the module's own scaffolded
78
+ ``pyproject.toml`` must not count as "an existing host". A truly standalone
79
+ target (no repo/pyproject above it) returns False. See GH #210.
80
+ """
81
+ # ``resolve()`` allows ``dest`` to not exist yet; the walk is over its
82
+ # absolute parents so a relative ``--dest`` is handled the same way.
83
+ start = Path(dest).resolve().parent
84
+ for parent in (start, *start.parents):
85
+ if (parent / ".git").exists() or (parent / "pyproject.toml").is_file():
86
+ return True
87
+ return False
88
+
89
+
64
90
  def _should_pin_framework_version(version: str | None) -> bool:
65
91
  """Whether ``version`` is a concrete pin rather than a skip sentinel.
66
92
 
@@ -222,6 +248,7 @@ def create_module(
222
248
  template_root: Path | None = None,
223
249
  *,
224
250
  framework_version: str | None = None,
251
+ include_ci: bool = True,
225
252
  ) -> Path:
226
253
  """Scaffold a module package at ``dest``.
227
254
 
@@ -230,6 +257,12 @@ def create_module(
230
257
  the module resolves against that framework version (e.g. ``uv add`` into the
231
258
  workspace that created it). Left as ``None`` (or the ``"*"`` sentinel), the
232
259
  template's ranges are kept verbatim. See GH #195.
260
+
261
+ When ``include_ci`` is False, the scaffolded ``.github/`` (CI + PyPI publish
262
+ workflows) is omitted. Those nested workflows never run inside an existing
263
+ host repo (GitHub only reads the repo-root ``.github/``) and ``publish.yml``
264
+ is a footgun there, so callers creating an *in-repo* module pass
265
+ ``include_ci=False``. See GH #210.
233
266
  """
234
267
  dest = Path(dest)
235
268
  existed_before = dest.exists()
@@ -249,6 +282,8 @@ def create_module(
249
282
  },
250
283
  path_rewrites={_PACKAGE_PATH_TOKEN: package_name},
251
284
  )
285
+ if not include_ci:
286
+ shutil.rmtree(dest / ".github", ignore_errors=True)
252
287
  if _should_pin_framework_version(framework_version):
253
288
  pin_framework_deps(dest / "pyproject.toml", framework_version)
254
289
  except Exception:
@@ -0,0 +1,107 @@
1
+ """GH #210: create-module omits the per-module .github/ for in-repo modules.
2
+
3
+ Nested ``.github/workflows`` never run (GitHub only reads the repo-root
4
+ ``.github/``) and the bundled ``publish.yml`` is a PyPI-publish footgun, so a
5
+ module scaffolded inside an existing repo/host gets no ``.github/`` by default.
6
+ ``--standalone`` forces it for a module that lives in its own repo.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+
12
+ class TestCreateModuleIncludeCi:
13
+ async def test_include_ci_false_omits_github(self, tmp_path):
14
+ from simple_module_cli.scaffolding import create_module
15
+
16
+ dest = tmp_path / "simple-module-orders"
17
+ create_module(dest, name="Orders", include_ci=False)
18
+
19
+ assert not (dest / ".github").exists()
20
+ # The rest of the package is still scaffolded.
21
+ assert (dest / "orders" / "module.py").is_file()
22
+
23
+ async def test_include_ci_true_default_ships_github(self, tmp_path):
24
+ from simple_module_cli.scaffolding import create_module
25
+
26
+ dest = tmp_path / "simple-module-orders"
27
+ create_module(dest, name="Orders") # include_ci defaults to True
28
+
29
+ assert (dest / ".github" / "workflows" / "ci.yml").is_file()
30
+ assert (dest / ".github" / "workflows" / "publish.yml").is_file()
31
+
32
+
33
+ class TestCreateModuleCliContext:
34
+ async def test_cli_omits_github_inside_repo(self, tmp_path):
35
+ """A dest inside an existing repo (parent ``.git``) omits ``.github/`` + notes it."""
36
+ from simple_module_cli.cli import app
37
+ from typer.testing import CliRunner
38
+
39
+ (tmp_path / ".git").mkdir() # simulate an existing host repo
40
+ dest = tmp_path / "modules" / "orders"
41
+
42
+ result = CliRunner().invoke(app, ["create-module", "Orders", "--dest", str(dest)])
43
+ assert result.exit_code == 0, result.output
44
+ assert not (dest / ".github").exists(), ".github/ must be omitted for in-repo modules"
45
+ assert (dest / "orders" / "module.py").is_file()
46
+ # The skip is discoverable from the command output.
47
+ assert ".github" in result.output and "--standalone" in result.output
48
+
49
+ async def test_cli_standalone_forces_github_inside_repo(self, tmp_path):
50
+ """``--standalone`` emits ``.github/`` even inside an existing repo."""
51
+ from simple_module_cli.cli import app
52
+ from typer.testing import CliRunner
53
+
54
+ (tmp_path / ".git").mkdir()
55
+ dest = tmp_path / "modules" / "orders"
56
+
57
+ result = CliRunner().invoke(
58
+ app, ["create-module", "Orders", "--dest", str(dest), "--standalone"]
59
+ )
60
+ assert result.exit_code == 0, result.output
61
+ assert (dest / ".github" / "workflows" / "ci.yml").is_file()
62
+ assert (dest / ".github" / "workflows" / "publish.yml").is_file()
63
+
64
+ async def test_cli_emits_github_for_standalone_target(self, tmp_path):
65
+ """A clean target (no repo/pyproject ancestor) keeps ``.github/`` by default."""
66
+ import pytest
67
+ from simple_module_cli.cli import app
68
+ from simple_module_cli.scaffolding import is_inside_existing_repo
69
+ from typer.testing import CliRunner
70
+
71
+ dest = tmp_path / "simple-module-orders"
72
+ # This test's premise is that ``dest`` has no repo/pyproject ancestor.
73
+ # Under ``pytest --basetemp=<repo-subdir>`` that wouldn't hold, so the
74
+ # emit-by-default behaviour can't be exercised — skip rather than fail
75
+ # misleadingly.
76
+ if is_inside_existing_repo(dest):
77
+ pytest.skip(
78
+ "tmp_path resolves inside an existing repo (e.g. --basetemp under the repo)"
79
+ )
80
+
81
+ result = CliRunner().invoke(app, ["create-module", "Orders", "--dest", str(dest)])
82
+ assert result.exit_code == 0, result.output
83
+ assert (dest / ".github" / "workflows" / "ci.yml").is_file()
84
+ assert "Skipped .github" not in result.output
85
+
86
+
87
+ class TestIsInsideExistingRepo:
88
+ async def test_detects_git_parent(self, tmp_path):
89
+ from simple_module_cli.scaffolding import is_inside_existing_repo
90
+
91
+ (tmp_path / ".git").mkdir()
92
+ assert is_inside_existing_repo(tmp_path / "modules" / "orders")
93
+
94
+ async def test_detects_pyproject_parent(self, tmp_path):
95
+ from simple_module_cli.scaffolding import is_inside_existing_repo
96
+
97
+ (tmp_path / "pyproject.toml").write_text("[project]\n", encoding="utf-8")
98
+ assert is_inside_existing_repo(tmp_path / "modules" / "orders")
99
+
100
+ async def test_clean_target_is_not_in_repo(self, tmp_path):
101
+ from simple_module_cli.scaffolding import is_inside_existing_repo
102
+
103
+ # The module's own scaffolded pyproject.toml at dest must not count.
104
+ dest = tmp_path / "standalone"
105
+ dest.mkdir()
106
+ (dest / "pyproject.toml").write_text("[project]\n", encoding="utf-8")
107
+ assert not is_inside_existing_repo(dest)