simple-module-cli 0.0.7__tar.gz → 0.0.8__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 (78) hide show
  1. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/PKG-INFO +1 -1
  2. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/pyproject.toml +1 -1
  3. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/app_project.py +42 -4
  4. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/new.py +18 -1
  5. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/scaffolding.py +1 -0
  6. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/Makefile +5 -1
  7. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/pyproject.toml.tpl +6 -0
  8. simple_module_cli-0.0.8/simple_module_cli/templates/module/__PACKAGE__/settings.py.tpl +14 -0
  9. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/pyproject.toml.tpl +1 -0
  10. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/tests/test_cli_new.py +60 -0
  11. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/.gitignore +0 -0
  12. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/LICENSE +0 -0
  13. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/README.md +0 -0
  14. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/__init__.py +0 -0
  15. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/_env.py +0 -0
  16. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/case.py +0 -0
  17. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/catalog.py +0 -0
  18. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/cli.py +0 -0
  19. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/package_update.py +0 -0
  20. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/plugins.py +0 -0
  21. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/recipes.py +0 -0
  22. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills/README.md +0 -0
  23. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills/simple-module-cli/SKILL.md +0 -0
  24. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills/simple-module-conventions/SKILL.md +0 -0
  25. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills/simple-module-creating/SKILL.md +0 -0
  26. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills/simple-module-database/SKILL.md +0 -0
  27. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills/simple-module-doctor/SKILL.md +0 -0
  28. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills/simple-module-inertia-pages/SKILL.md +0 -0
  29. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills/simple-module-locales/SKILL.md +0 -0
  30. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills/simple-module-migrations/SKILL.md +0 -0
  31. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills/simple-module-registries/SKILL.md +0 -0
  32. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills/simple-module-testing/SKILL.md +0 -0
  33. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/skills_cmd.py +0 -0
  34. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/.env.example +0 -0
  35. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/.gitignore +0 -0
  36. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/README.md.tpl +0 -0
  37. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/_optional/background_tasks/Makefile.snippet +0 -0
  38. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/_optional/background_tasks/docker-compose.yml +0 -0
  39. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/_optional/background_tasks/run_worker.py +0 -0
  40. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/_optional/background_tasks/worker.Dockerfile +0 -0
  41. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/alembic.ini +0 -0
  42. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/client_app/app.tsx +0 -0
  43. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/client_app/main.tsx +0 -0
  44. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/client_app/package.json.tpl +0 -0
  45. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/client_app/pages/Error.tsx +0 -0
  46. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/client_app/pages.ts +0 -0
  47. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/client_app/styles.css +0 -0
  48. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/client_app/tsconfig.json +0 -0
  49. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/client_app/vite.config.ts +0 -0
  50. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/main.py +0 -0
  51. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/migrations/env.py +0 -0
  52. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/migrations/script.py.mako +0 -0
  53. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/migrations/versions/.gitkeep +0 -0
  54. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/host/templates/index.html +0 -0
  55. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/.github/workflows/ci.yml +0 -0
  56. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/.github/workflows/publish.yml.tpl +0 -0
  57. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/.gitignore +0 -0
  58. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/README.md.tpl +0 -0
  59. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/__PACKAGE__/__init__.py +0 -0
  60. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/__PACKAGE__/endpoints/__init__.py +0 -0
  61. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/__PACKAGE__/endpoints/api.py.tpl +0 -0
  62. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/__PACKAGE__/module.py.tpl +0 -0
  63. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/__PACKAGE__/pages/.gitkeep +0 -0
  64. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/__PACKAGE__/services.py.tpl +0 -0
  65. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/package.json.tpl +0 -0
  66. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/tests/test_module.py.tpl +0 -0
  67. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/templates/module/tsconfig.json.tpl +0 -0
  68. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/simple_module_cli/wizard.py +0 -0
  69. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/tests/test_build_packaging.py +0 -0
  70. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/tests/test_cli_catalog.py +0 -0
  71. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/tests/test_cli_package_update.py +0 -0
  72. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/tests/test_cli_recipes.py +0 -0
  73. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/tests/test_cli_wizard.py +0 -0
  74. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/tests/test_no_framework_deps.py +0 -0
  75. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/tests/test_plugin_discovery.py +0 -0
  76. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/tests/test_scaffolding_host.py +0 -0
  77. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/tests/test_scaffolding_module.py +0 -0
  78. {simple_module_cli-0.0.7 → simple_module_cli-0.0.8}/tests/test_skills_cmd.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simple_module_cli
3
- Version: 0.0.7
3
+ Version: 0.0.8
4
4
  Summary: Standalone scaffolder for the SimpleModule framework — `sm new`, `sm 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.7"
3
+ version = "0.0.8"
4
4
  description = "Standalone scaffolder for the SimpleModule framework — `sm new`, `sm create-module`, plugin host."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -18,15 +18,19 @@ from collections.abc import Sequence
18
18
  from importlib.metadata import PackageNotFoundError
19
19
  from importlib.metadata import version as _pkg_version
20
20
  from pathlib import Path
21
+ from typing import Any
21
22
 
22
23
  from simple_module_cli._env import set_env_key
23
24
  from simple_module_cli.case import to_kebab_case, to_pascal_case
24
25
  from simple_module_cli.catalog import CATALOG, PRESETS, expand_deps
25
26
  from simple_module_cli.recipes import RECIPES, ScaffoldCtx
26
- from simple_module_cli.scaffolding import create_host
27
+ from simple_module_cli.scaffolding import _module_to_pypi_name, create_host, create_module
27
28
 
28
29
  __all__ = ["create_app_project"]
29
30
 
31
+ _SAMPLE_MODULE_NAME = "hello"
32
+ _SAMPLE_MODULE_PKG = _module_to_pypi_name(_SAMPLE_MODULE_NAME)
33
+
30
34
 
31
35
  def _resolve_framework_version() -> str:
32
36
  """Resolve the framework version to pin scaffolded apps against.
@@ -69,6 +73,7 @@ def create_app_project(
69
73
  db: str = "sqlite",
70
74
  tenancy: bool = False,
71
75
  selected: Sequence[str] | None = None,
76
+ flat: bool = False,
72
77
  ) -> None:
73
78
  """Greenfield ``simple-module new`` scaffold.
74
79
 
@@ -101,19 +106,26 @@ def create_app_project(
101
106
  env_text = set_env_key(env_text, "SM_MULTI_TENANT", "true" if tenancy else "false")
102
107
  env_path.write_text(env_text, encoding="utf-8")
103
108
 
109
+ if not flat:
110
+ _scaffold_sample_module(target)
111
+ py_deps.append(_SAMPLE_MODULE_PKG)
112
+
104
113
  pyproject = target / "pyproject.toml"
105
114
  if pyproject.exists():
106
115
  text = pyproject.read_text(encoding="utf-8")
107
- text = _inject_py_deps(text, py_deps, _APP_PY_DEV_DEPS)
116
+ text = _rewrite_pyproject(text, py_deps, _APP_PY_DEV_DEPS, flat=flat)
108
117
  pyproject.write_text(text, encoding="utf-8")
109
118
 
110
119
  pkg_path = target / "package.json"
120
+ data: dict[str, Any]
111
121
  if pkg_path.exists():
112
122
  data = _json.loads(pkg_path.read_text(encoding="utf-8"))
113
123
  else:
114
124
  data = {"name": to_kebab_case(name), "private": True, "type": "module"}
115
125
  data.setdefault("dependencies", {}).update(_APP_NPM_DEPS)
116
126
  data.setdefault("devDependencies", {}).update(_APP_NPM_DEV_DEPS)
127
+ if not flat:
128
+ data["workspaces"] = ["client_app", "modules/*"]
117
129
  pkg_path.write_text(_json.dumps(data, indent=2) + "\n", encoding="utf-8")
118
130
 
119
131
  ctx = ScaffoldCtx(name=name, db=db, tenancy=tenancy, selected=tuple(resolved))
@@ -123,14 +135,32 @@ def create_app_project(
123
135
  RECIPES[recipe_key].apply(target, ctx)
124
136
 
125
137
 
138
+ def _scaffold_sample_module(target: Path) -> None:
139
+ """Give the user a place to copy when they want to add a feature module.
140
+
141
+ The alternative is reverse-engineering one of the wheel-installed
142
+ framework modules from ``.venv/site-packages/``.
143
+ """
144
+ sample_dest = target / "modules" / _SAMPLE_MODULE_NAME
145
+ if sample_dest.exists():
146
+ return
147
+ create_module(sample_dest, name=_SAMPLE_MODULE_NAME)
148
+
149
+
126
150
  def _db_url(db: str, slug: str) -> str:
127
151
  if db == "postgres":
128
152
  return f"postgresql+asyncpg://postgres:postgres@localhost:5432/{slug}"
129
153
  return "sqlite+aiosqlite:///./app.db"
130
154
 
131
155
 
132
- def _inject_py_deps(text: str, deps: list[str], dev_deps: list[str]) -> str:
133
- """Replace project.dependencies + dependency-groups.dev in a pyproject.toml."""
156
+ def _rewrite_pyproject(text: str, deps: list[str], dev_deps: list[str], *, flat: bool) -> str:
157
+ """Replace deps + wire uv workspace based on ``flat`` mode.
158
+
159
+ Workspace mode (``flat=False``) adds a ``[tool.uv.sources]`` entry so uv
160
+ resolves the bundled sample module from the workspace, not PyPI. Flat
161
+ mode strips the static ``[tool.uv.workspace]`` block inherited from the
162
+ template — there is no ``modules/`` tree for it to point at.
163
+ """
134
164
  import tomlkit
135
165
 
136
166
  doc = tomlkit.parse(text)
@@ -138,4 +168,12 @@ def _inject_py_deps(text: str, deps: list[str], dev_deps: list[str]) -> str:
138
168
  project["dependencies"] = list(deps)
139
169
  groups = doc.setdefault("dependency-groups", tomlkit.table())
140
170
  groups["dev"] = list(dev_deps)
171
+ tool = doc.setdefault("tool", tomlkit.table())
172
+ uv_table = tool.setdefault("uv", tomlkit.table())
173
+ if flat:
174
+ if "workspace" in uv_table:
175
+ del uv_table["workspace"]
176
+ else:
177
+ sources = uv_table.setdefault("sources", tomlkit.table())
178
+ sources[_SAMPLE_MODULE_PKG] = {"workspace": True}
141
179
  return tomlkit.dumps(doc)
@@ -64,6 +64,16 @@ def new_project(
64
64
  help="Skip 'uv sync' / 'npm install' / 'alembic upgrade head' after scaffolding.",
65
65
  ),
66
66
  ] = False,
67
+ flat: Annotated[
68
+ bool,
69
+ typer.Option(
70
+ "--flat",
71
+ help=(
72
+ "Skip the modules/ directory and sample module. Use when the host "
73
+ "will only consume published modules and never author its own."
74
+ ),
75
+ ),
76
+ ] = False,
67
77
  ) -> None:
68
78
  """Scaffold a new SimpleModule app, optionally with background jobs."""
69
79
  target = dest or Path.cwd() / name
@@ -90,7 +100,14 @@ def new_project(
90
100
  raise typer.Exit(code=1) from None
91
101
 
92
102
  try:
93
- create_app_project(target, name=name, db=db_final, tenancy=tenancy_final, selected=resolved)
103
+ create_app_project(
104
+ target,
105
+ name=name,
106
+ db=db_final,
107
+ tenancy=tenancy_final,
108
+ selected=resolved,
109
+ flat=flat,
110
+ )
94
111
  except FileExistsError as exc:
95
112
  typer.echo(f"ERROR: {exc}", err=True)
96
113
  raise typer.Exit(code=1) from exc
@@ -117,6 +117,7 @@ def create_module(
117
117
  "{{MODULE_NAME}}": display_name,
118
118
  "{{MODULE_SLUG}}": slug,
119
119
  "{{PACKAGE_NAME}}": package_name,
120
+ "{{PACKAGE_NAME_UPPER}}": package_name.upper(),
120
121
  },
121
122
  path_rewrites={_PACKAGE_PATH_TOKEN: package_name},
122
123
  )
@@ -1,8 +1,9 @@
1
- .PHONY: install dev dev-api dev-ui build migrate gen-pages
1
+ .PHONY: install dev dev-api dev-ui build migrate gen-pages sync-js-deps
2
2
 
3
3
  install:
4
4
  uv sync
5
5
  cd client_app && npm install
6
+ $(MAKE) sync-js-deps
6
7
 
7
8
  dev: gen-pages
8
9
  @echo "Starting API and UI dev servers..."
@@ -22,3 +23,6 @@ migrate:
22
23
 
23
24
  gen-pages:
24
25
  uv run python -m simple_module_hosting gen-pages --host-dir=client_app
26
+
27
+ sync-js-deps:
28
+ uv run python -m simple_module_hosting sync-js-deps --host-client-app=client_app
@@ -16,3 +16,9 @@ dependencies = [
16
16
  # Host is an application, not a distributable package.
17
17
  [tool.uv]
18
18
  package = false
19
+
20
+ # Add a new module with `sm create-module <name>`, then add
21
+ # `simple_module_<name>` to the dependency list above and to
22
+ # [tool.uv.sources] as `{ workspace = true }`.
23
+ [tool.uv.workspace]
24
+ members = ["modules/*"]
@@ -0,0 +1,14 @@
1
+ """{{MODULE_NAME}} module settings.
2
+
3
+ Per-module env-var prefix is ``SM_{{PACKAGE_NAME_UPPER}}_*``. Add fields here
4
+ as the module grows; the framework wires them onto
5
+ ``app.state.{{PACKAGE_NAME}}`` via ``register_settings``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pydantic_settings import BaseSettings, SettingsConfigDict
11
+
12
+
13
+ class {{MODULE_NAME}}Settings(BaseSettings):
14
+ model_config = SettingsConfigDict(env_prefix="SM_{{PACKAGE_NAME_UPPER}}_", extra="ignore")
@@ -7,6 +7,7 @@ dependencies = [
7
7
  "simple_module_core>=1.0,<2.0",
8
8
  "simple_module_db>=1.0,<2.0",
9
9
  "simple_module_hosting>=1.0,<2.0",
10
+ "pydantic-settings>=2.0",
10
11
  "sqlalchemy>=2.0",
11
12
  ]
12
13
 
@@ -196,6 +196,66 @@ def test_sm_new_interactive_full_preset(tmp_path: Path) -> None:
196
196
  assert (target / "docker-compose.yml").is_file()
197
197
 
198
198
 
199
+ def test_sm_new_default_scaffolds_sample_hello_module(tmp_path: Path) -> None:
200
+ """Default (workspace) mode lays down modules/hello/ as an authoring template."""
201
+ runner = CliRunner()
202
+ target = tmp_path / "demo"
203
+ result = runner.invoke(
204
+ app,
205
+ ["new", "demo", "--yes", "--db", "sqlite", "--no-install", "--dest", str(target)],
206
+ )
207
+ assert result.exit_code == 0, result.output
208
+ assert (target / "modules" / "hello" / "pyproject.toml").is_file()
209
+ assert (target / "modules" / "hello" / "hello" / "module.py").is_file()
210
+
211
+
212
+ def test_sm_new_default_wires_workspace_in_pyproject(tmp_path: Path) -> None:
213
+ """Default mode adds [tool.uv.workspace] members + a workspace source for the sample."""
214
+ runner = CliRunner()
215
+ target = tmp_path / "demo"
216
+ runner.invoke(
217
+ app,
218
+ ["new", "demo", "--yes", "--db", "sqlite", "--no-install", "--dest", str(target)],
219
+ )
220
+ pyproject_text = (target / "pyproject.toml").read_text()
221
+ assert "[tool.uv.workspace]" in pyproject_text
222
+ assert 'members = ["modules/*"]' in pyproject_text
223
+ assert "simple_module_hello" in pyproject_text
224
+ # Sample module is a workspace source, not pulled from PyPI.
225
+ assert "[tool.uv.sources" in pyproject_text
226
+ assert "workspace = true" in pyproject_text
227
+
228
+
229
+ def test_sm_new_default_adds_npm_workspaces_field(tmp_path: Path) -> None:
230
+ """Default mode declares ``workspaces`` so vite picks up modules/<name>/."""
231
+ runner = CliRunner()
232
+ target = tmp_path / "demo"
233
+ runner.invoke(
234
+ app,
235
+ ["new", "demo", "--yes", "--db", "sqlite", "--no-install", "--dest", str(target)],
236
+ )
237
+ data = json.loads((target / "package.json").read_text())
238
+ assert data.get("workspaces") == ["client_app", "modules/*"]
239
+
240
+
241
+ def test_sm_new_flat_skips_modules_dir(tmp_path: Path) -> None:
242
+ """``--flat`` keeps the legacy single-host layout: no modules/ tree, no sample."""
243
+ runner = CliRunner()
244
+ target = tmp_path / "demo"
245
+ result = runner.invoke(
246
+ app,
247
+ ["new", "demo", "--yes", "--flat", "--no-install", "--dest", str(target)],
248
+ )
249
+ assert result.exit_code == 0, result.output
250
+ assert not (target / "modules").exists()
251
+ pyproject_text = (target / "pyproject.toml").read_text()
252
+ assert "simple_module_hello" not in pyproject_text
253
+ # No workspace plumbing pointing at a non-existent modules/ tree.
254
+ assert "[tool.uv.workspace]" not in pyproject_text
255
+ data = json.loads((target / "package.json").read_text())
256
+ assert "workspaces" not in data
257
+
258
+
199
259
  def test_sm_new_refuses_to_overwrite(tmp_path: Path) -> None:
200
260
  target = tmp_path / "my-app"
201
261
  target.mkdir()