simple-module-hosting 0.0.11__tar.gz → 0.0.13__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 (46) hide show
  1. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/PKG-INFO +8 -8
  2. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/README.md +5 -5
  3. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/pyproject.toml +3 -3
  4. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/__main__.py +1 -1
  5. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/_inertia_setup.py +1 -1
  6. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/host_cli.py +1 -1
  7. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/manifest.py +4 -4
  8. simple_module_hosting-0.0.13/tests/test_manifest.py +165 -0
  9. simple_module_hosting-0.0.11/tests/test_manifest.py +0 -60
  10. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/.gitignore +0 -0
  11. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/LICENSE +0 -0
  12. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/__init__.py +0 -0
  13. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/_error_handlers.py +0 -0
  14. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/_host_services.py +0 -0
  15. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/_hydrate_step.py +0 -0
  16. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/_inertia_shared.py +0 -0
  17. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/_observability.py +0 -0
  18. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/_phase_helpers.py +0 -0
  19. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/app_builder.py +0 -0
  20. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/bootstrap_settings.py +0 -0
  21. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/health.py +0 -0
  22. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/host_settings.py +0 -0
  23. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/i18n_deps.py +0 -0
  24. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/i18n_manifest.py +0 -0
  25. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/i18n_middleware.py +0 -0
  26. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/inertia_deps.py +0 -0
  27. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/inertia_utils.py +0 -0
  28. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/logging.py +0 -0
  29. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/middleware.py +0 -0
  30. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/migrations.py +0 -0
  31. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/permissions.py +0 -0
  32. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/py.typed +0 -0
  33. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/redirects.py +0 -0
  34. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/simple_module_hosting/settings.py +0 -0
  35. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_app.py +0 -0
  36. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_health.py +0 -0
  37. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_host_cli.py +0 -0
  38. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_hosting_permissions.py +0 -0
  39. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_i18n_manifest.py +0 -0
  40. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_inertia_i18n_shared_props.py +0 -0
  41. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_locale_middleware.py +0 -0
  42. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_logging.py +0 -0
  43. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_settings_i18n.py +0 -0
  44. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_settings_secrets.py +0 -0
  45. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_tenant_middleware.py +0 -0
  46. {simple_module_hosting-0.0.11 → simple_module_hosting-0.0.13}/tests/test_translator_dep.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simple_module_hosting
3
- Version: 0.0.11
3
+ Version: 0.0.13
4
4
  Summary: FastAPI + Inertia.js host runtime for simple_module — app_builder, middleware stack, CLI (sm / simple-module), scaffolding
5
5
  Project-URL: Homepage, https://github.com/antosubash/simple_module_python
6
6
  Project-URL: Repository, https://github.com/antosubash/simple_module_python
@@ -26,8 +26,8 @@ Requires-Dist: fastapi-inertia>=1.0
26
26
  Requires-Dist: fastapi>=0.115
27
27
  Requires-Dist: httpx>=0.27
28
28
  Requires-Dist: jinja2>=3.1
29
- Requires-Dist: simple-module-core==0.0.11
30
- Requires-Dist: simple-module-db==0.0.11
29
+ Requires-Dist: simple-module-core==0.0.13
30
+ Requires-Dist: simple-module-db==0.0.13
31
31
  Requires-Dist: starlette>=0.44
32
32
  Requires-Dist: tomlkit>=0.13
33
33
  Requires-Dist: uvicorn[standard]>=0.34
@@ -35,7 +35,7 @@ Description-Content-Type: text/markdown
35
35
 
36
36
  # simple_module_hosting
37
37
 
38
- FastAPI + Inertia.js host runtime for the [simple_module](https://github.com/antosubash/simple_module_python) framework — builds the app, wires the middleware pipeline, and contributes the `sm host` plugin to the standalone `sm` CLI.
38
+ FastAPI + Inertia.js host runtime for the [simple_module](https://github.com/antosubash/simple_module_python) framework — builds the app, wires the middleware pipeline, and contributes the `smpy host` plugin to the standalone `smpy` CLI.
39
39
 
40
40
  ## Install
41
41
 
@@ -46,7 +46,7 @@ pip install simple_module_hosting
46
46
  For a new project, most users run the generator instead (shipped by the standalone `simple_module_cli` distribution):
47
47
 
48
48
  ```bash
49
- uvx --from simple_module_cli sm new my-app
49
+ uvx --from simple_module_cli smpy new my-app
50
50
  ```
51
51
 
52
52
  ## What it provides
@@ -54,7 +54,7 @@ uvx --from simple_module_cli sm new my-app
54
54
  - `create_app(settings)` — returns a fully-wired `FastAPI` instance with all discovered modules registered.
55
55
  - Middleware pipeline (execution order): CorrelationId → RequestLogging → SecurityHeaders → Session → `<module middleware>` → Tenant (opt-in) → Locale → InertiaLayoutData → app.
56
56
  - Inertia wiring — shared props (`auth`, `menus`, `i18n`), `InertiaDep`, page-route lookup.
57
- - `sm host` plugin — `sm host gen-pages` regenerates the frontend pages manifest; `sm host sync-js-deps` installs JS deps declared by installed modules. The `sm` binary itself comes from `simple_module_cli`.
57
+ - `smpy host` plugin — `smpy host gen-pages` regenerates the frontend pages manifest; `smpy host sync-js-deps` installs JS deps declared by installed modules. The `smpy` binary itself comes from `simple_module_cli`.
58
58
 
59
59
  ## Usage
60
60
 
@@ -75,8 +75,8 @@ if __name__ == "__main__":
75
75
  CLI (after also installing `simple_module_cli`):
76
76
 
77
77
  ```bash
78
- sm host gen-pages # regenerate client_app/modules.generated.ts
79
- sm host sync-js-deps # sync module JS deps into client_app/node_modules
78
+ smpy host gen-pages # regenerate client_app/modules.generated.ts
79
+ smpy host sync-js-deps # sync module JS deps into client_app/node_modules
80
80
  ```
81
81
 
82
82
  ## Depends on
@@ -1,6 +1,6 @@
1
1
  # simple_module_hosting
2
2
 
3
- FastAPI + Inertia.js host runtime for the [simple_module](https://github.com/antosubash/simple_module_python) framework — builds the app, wires the middleware pipeline, and contributes the `sm host` plugin to the standalone `sm` CLI.
3
+ FastAPI + Inertia.js host runtime for the [simple_module](https://github.com/antosubash/simple_module_python) framework — builds the app, wires the middleware pipeline, and contributes the `smpy host` plugin to the standalone `smpy` CLI.
4
4
 
5
5
  ## Install
6
6
 
@@ -11,7 +11,7 @@ pip install simple_module_hosting
11
11
  For a new project, most users run the generator instead (shipped by the standalone `simple_module_cli` distribution):
12
12
 
13
13
  ```bash
14
- uvx --from simple_module_cli sm new my-app
14
+ uvx --from simple_module_cli smpy new my-app
15
15
  ```
16
16
 
17
17
  ## What it provides
@@ -19,7 +19,7 @@ uvx --from simple_module_cli sm new my-app
19
19
  - `create_app(settings)` — returns a fully-wired `FastAPI` instance with all discovered modules registered.
20
20
  - Middleware pipeline (execution order): CorrelationId → RequestLogging → SecurityHeaders → Session → `<module middleware>` → Tenant (opt-in) → Locale → InertiaLayoutData → app.
21
21
  - Inertia wiring — shared props (`auth`, `menus`, `i18n`), `InertiaDep`, page-route lookup.
22
- - `sm host` plugin — `sm host gen-pages` regenerates the frontend pages manifest; `sm host sync-js-deps` installs JS deps declared by installed modules. The `sm` binary itself comes from `simple_module_cli`.
22
+ - `smpy host` plugin — `smpy host gen-pages` regenerates the frontend pages manifest; `smpy host sync-js-deps` installs JS deps declared by installed modules. The `smpy` binary itself comes from `simple_module_cli`.
23
23
 
24
24
  ## Usage
25
25
 
@@ -40,8 +40,8 @@ if __name__ == "__main__":
40
40
  CLI (after also installing `simple_module_cli`):
41
41
 
42
42
  ```bash
43
- sm host gen-pages # regenerate client_app/modules.generated.ts
44
- sm host sync-js-deps # sync module JS deps into client_app/node_modules
43
+ smpy host gen-pages # regenerate client_app/modules.generated.ts
44
+ smpy host sync-js-deps # sync module JS deps into client_app/node_modules
45
45
  ```
46
46
 
47
47
  ## Depends on
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "simple_module_hosting"
3
- version = "0.0.11"
3
+ version = "0.0.13"
4
4
  description = "FastAPI + Inertia.js host runtime for simple_module — app_builder, middleware stack, CLI (sm / simple-module), scaffolding"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -26,8 +26,8 @@ dependencies = [
26
26
  "fastapi-inertia>=1.0",
27
27
  "httpx>=0.27",
28
28
  "jinja2>=3.1",
29
- "simple_module_core==0.0.11",
30
- "simple_module_db==0.0.11",
29
+ "simple_module_core==0.0.13",
30
+ "simple_module_db==0.0.13",
31
31
  "starlette>=0.44",
32
32
  "tomlkit>=0.13",
33
33
  "uvicorn[standard]>=0.34",
@@ -3,7 +3,7 @@
3
3
  Without this, ``python -m simple_module_hosting.host_cli`` would import the
4
4
  module without running the Typer app — silently no-op'ing commands like
5
5
  ``gen-pages``. Provides the same Typer ``app`` callable that the
6
- ``simple_module_cli.cli_plugins`` entry point exposes as ``sm host``.
6
+ ``simple_module_cli.cli_plugins`` entry point exposes as ``smpy host``.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
@@ -28,7 +28,7 @@ def setup_inertia(
28
28
 
29
29
  Two host layouts are supported: ``host/templates`` (the framework's
30
30
  own host package) and ``templates`` at the project root (what
31
- ``sm new`` produces). The first one found wins so it can override
31
+ ``smpy new`` produces). The first one found wins so it can override
32
32
  module-contributed templates.
33
33
  """
34
34
  from fastapi.templating import Jinja2Templates
@@ -1,4 +1,4 @@
1
- """``sm host`` plugin — project-time helpers exposed through the simple-module CLI.
1
+ """``smpy host`` plugin — project-time helpers exposed through the simple-module CLI.
2
2
 
3
3
  Commands here need module discovery (``simple_module_core.discover_modules``)
4
4
  and the manifest helpers; they're not part of the standalone scaffolder.
@@ -8,7 +8,7 @@
8
8
  * :func:`read_module_package_json` / :func:`collect_module_js_deps` locate
9
9
  the per-module ``package.json`` shipped inside wheels (or alongside the
10
10
  source for editable installs) and aggregate its ``dependencies`` block.
11
- Used by the ``sm sync-js-deps`` CLI.
11
+ Used by the ``smpy sync-js-deps`` CLI.
12
12
  """
13
13
 
14
14
  from __future__ import annotations
@@ -26,7 +26,7 @@ logger = logging.getLogger(__name__)
26
26
 
27
27
  _GENERATED_TS_HEADER = """\
28
28
  // AUTO-GENERATED by simple_module_hosting.manifest — do not edit by hand.
29
- // Regenerate with: sm gen-pages
29
+ // Regenerate with: smpy gen-pages
30
30
  //
31
31
  // Maps ModuleName -> record of page import paths. Paths below are
32
32
  // relative to this file (host/client_app/) so that pages shipped inside
@@ -35,7 +35,7 @@ _GENERATED_TS_HEADER = """\
35
35
 
36
36
  _GENERATED_CSS_HEADER = """\
37
37
  /* AUTO-GENERATED by simple_module_hosting.manifest — do not edit by hand.
38
- * Regenerate with: sm gen-pages
38
+ * Regenerate with: smpy gen-pages
39
39
  *
40
40
  * @source entries for module pages shipped inside pip-installed wheels.
41
41
  * In-repo modules are covered by the static @source glob in
@@ -54,7 +54,7 @@ def repo_root_from_client_app(client_app_dir: Path) -> Path:
54
54
  Walks up looking for the nearest ``package.json`` that declares
55
55
  ``workspaces`` (the workspace root npm uses), falling back to any
56
56
  ``package.json``. This handles both the framework repo's
57
- ``host/client_app/`` layout and the flat ``sm new`` scaffold's
57
+ ``host/client_app/`` layout and the flat ``smpy new`` scaffold's
58
58
  ``client_app/`` layout (where the parent IS the workspace root).
59
59
  """
60
60
  here = client_app_dir.resolve()
@@ -0,0 +1,165 @@
1
+ """Tests for ``simple_module_hosting.manifest`` helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib.util
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+ from simple_module_core import ModuleBase, ModuleMeta
12
+ from simple_module_hosting.manifest import (
13
+ collect_module_js_deps,
14
+ read_module_package_json,
15
+ repo_root_from_client_app,
16
+ )
17
+
18
+
19
+ def test_repo_root_finds_workspace_root_in_framework_layout(tmp_path: Path) -> None:
20
+ """``host/client_app/`` shape: walk up to the workspace package.json."""
21
+ workspace = tmp_path / "ws"
22
+ (workspace / "host" / "client_app").mkdir(parents=True)
23
+ (workspace / "package.json").write_text(
24
+ json.dumps({"name": "ws", "workspaces": ["host/client_app", "modules/*"]})
25
+ )
26
+ (workspace / "host" / "package.json").write_text(json.dumps({"name": "ws-host"}))
27
+
28
+ assert repo_root_from_client_app(workspace / "host" / "client_app") == workspace.resolve()
29
+
30
+
31
+ def test_repo_root_finds_root_in_flat_scaffold_layout(tmp_path: Path) -> None:
32
+ """Flat scaffold: ``client_app/`` directly under the host root."""
33
+ host = tmp_path / "my-app"
34
+ (host / "client_app").mkdir(parents=True)
35
+ (host / "package.json").write_text(json.dumps({"name": "my-app"}))
36
+
37
+ assert repo_root_from_client_app(host / "client_app") == host.resolve()
38
+
39
+
40
+ def test_repo_root_prefers_workspace_package_json_over_nearer_one(tmp_path: Path) -> None:
41
+ """When a non-workspace package.json sits between client_app and the
42
+ workspace root, the workspace one wins so npm install runs at the right cwd."""
43
+ workspace = tmp_path / "ws"
44
+ (workspace / "host" / "client_app").mkdir(parents=True)
45
+ (workspace / "package.json").write_text(
46
+ json.dumps({"name": "ws", "workspaces": ["host/client_app"]})
47
+ )
48
+ (workspace / "host" / "package.json").write_text(json.dumps({"name": "ws-host"}))
49
+
50
+ assert repo_root_from_client_app(workspace / "host" / "client_app") == workspace.resolve()
51
+
52
+
53
+ def test_repo_root_falls_back_to_nearest_package_json(tmp_path: Path) -> None:
54
+ """No workspaces field anywhere — settle on the nearest package.json found."""
55
+ root = tmp_path / "root"
56
+ (root / "client_app").mkdir(parents=True)
57
+ (root / "package.json").write_text(json.dumps({"name": "root"}))
58
+
59
+ assert repo_root_from_client_app(root / "client_app") == root.resolve()
60
+
61
+
62
+ def test_repo_root_falls_back_to_two_levels_up_if_no_package_json(tmp_path: Path) -> None:
63
+ """No package.json exists yet — preserve the legacy two-up behaviour so
64
+ framework-internal callers still get a path before any npm setup."""
65
+ deep = tmp_path / "outer" / "inner" / "client_app"
66
+ deep.mkdir(parents=True)
67
+
68
+ assert repo_root_from_client_app(deep) == (tmp_path / "outer").resolve()
69
+
70
+
71
+ @pytest.fixture
72
+ def fake_module_factory(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
73
+ """Build a fake installed module with a configurable on-disk layout.
74
+
75
+ Returns a callable that takes a ``layout`` ∈ {"wheel", "source"} plus
76
+ optional ``dependencies`` and ``peer_dependencies`` maps, lays the
77
+ files down under ``tmp_path``, registers the package in ``sys.modules``,
78
+ and returns a ``ModuleBase`` subclass pinned to it.
79
+ """
80
+ counter = {"n": 0}
81
+
82
+ def _build(
83
+ layout: str,
84
+ dependencies: dict[str, str] | None = None,
85
+ peer_dependencies: dict[str, str] | None = None,
86
+ ) -> type[ModuleBase]:
87
+ counter["n"] += 1
88
+ pkg_name = f"fake_module_{counter['n']}"
89
+ if layout == "wheel":
90
+ # Wheel: <pkg>/ contains code + package.json (Hatch force-include).
91
+ pkg_root = tmp_path / pkg_name
92
+ pkg_root.mkdir()
93
+ pkg_json_path = pkg_root / "package.json"
94
+ elif layout == "source":
95
+ # Source-tree / editable: package.json sits above the Python pkg.
96
+ module_root = tmp_path / f"{pkg_name}_repo"
97
+ module_root.mkdir()
98
+ pkg_root = module_root / pkg_name
99
+ pkg_root.mkdir()
100
+ pkg_json_path = module_root / "package.json"
101
+ else:
102
+ raise ValueError(f"unknown layout: {layout}")
103
+ init_py = pkg_root / "__init__.py"
104
+ init_py.write_text("")
105
+ pkg_json: dict[str, object] = {
106
+ "name": f"@fake/{pkg_name}",
107
+ "dependencies": dependencies or {},
108
+ }
109
+ if peer_dependencies is not None:
110
+ pkg_json["peerDependencies"] = peer_dependencies
111
+ pkg_json_path.write_text(json.dumps(pkg_json))
112
+
113
+ # Register the package with a real importlib spec so that
114
+ # importlib.resources.files() can locate the on-disk pkg_root.
115
+ spec = importlib.util.spec_from_file_location(
116
+ pkg_name, init_py, submodule_search_locations=[str(pkg_root)]
117
+ )
118
+ assert spec is not None and spec.loader is not None
119
+ mod = importlib.util.module_from_spec(spec)
120
+ spec.loader.exec_module(mod)
121
+ monkeypatch.setitem(sys.modules, pkg_name, mod)
122
+
123
+ class FakeMod(ModuleBase):
124
+ meta = ModuleMeta(name=pkg_name.title().replace("_", ""))
125
+
126
+ FakeMod.__module__ = pkg_name
127
+ return FakeMod
128
+
129
+ return _build
130
+
131
+
132
+ def test_read_module_package_json_finds_wheel_layout(fake_module_factory) -> None:
133
+ """Wheel install: package.json sits next to the Python package."""
134
+ fake_cls = fake_module_factory(
135
+ "wheel",
136
+ {"dep-a": "^1.0.0"},
137
+ peer_dependencies={"@host/peer": "^2.0.0"},
138
+ )
139
+ pkg = read_module_package_json(fake_cls())
140
+ assert pkg is not None
141
+ assert pkg["dependencies"] == {"dep-a": "^1.0.0"}
142
+ # The TS-side vite.config.ts also reads peerDependencies for its
143
+ # optimizeDeps walk — make sure the raw dict surfaces both blocks.
144
+ assert pkg["peerDependencies"] == {"@host/peer": "^2.0.0"}
145
+
146
+
147
+ def test_read_module_package_json_finds_source_layout(fake_module_factory) -> None:
148
+ """Source-tree / workspace: package.json sits at the module repo root."""
149
+ fake_cls = fake_module_factory("source", {"dep-b": "^2.0.0"})
150
+ pkg = read_module_package_json(fake_cls())
151
+ assert pkg is not None
152
+ assert pkg["dependencies"] == {"dep-b": "^2.0.0"}
153
+
154
+
155
+ def test_collect_module_js_deps_aggregates_across_layouts(fake_module_factory) -> None:
156
+ """Mixed-layout modules all contribute their declared deps."""
157
+ wheel_cls = fake_module_factory("wheel", {"cmdk": "^1.0.0"})
158
+ source_cls = fake_module_factory("source", {"maplibre-gl": "^4.7.0", "pmtiles": "^3.2.0"})
159
+ empty_cls = fake_module_factory("wheel", {})
160
+
161
+ deps = collect_module_js_deps([wheel_cls(), source_cls(), empty_cls()])
162
+ # Empty deps are dropped; both populated modules appear by their meta.name.
163
+ assert empty_cls.meta.name not in deps
164
+ assert deps[wheel_cls.meta.name] == {"cmdk": "^1.0.0"}
165
+ assert deps[source_cls.meta.name] == {"maplibre-gl": "^4.7.0", "pmtiles": "^3.2.0"}
@@ -1,60 +0,0 @@
1
- """Tests for ``simple_module_hosting.manifest`` helpers."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- from pathlib import Path
7
-
8
- from simple_module_hosting.manifest import repo_root_from_client_app
9
-
10
-
11
- def test_repo_root_finds_workspace_root_in_framework_layout(tmp_path: Path) -> None:
12
- """``host/client_app/`` shape: walk up to the workspace package.json."""
13
- workspace = tmp_path / "ws"
14
- (workspace / "host" / "client_app").mkdir(parents=True)
15
- (workspace / "package.json").write_text(
16
- json.dumps({"name": "ws", "workspaces": ["host/client_app", "modules/*"]})
17
- )
18
- (workspace / "host" / "package.json").write_text(json.dumps({"name": "ws-host"}))
19
-
20
- assert repo_root_from_client_app(workspace / "host" / "client_app") == workspace.resolve()
21
-
22
-
23
- def test_repo_root_finds_root_in_flat_scaffold_layout(tmp_path: Path) -> None:
24
- """Flat scaffold: ``client_app/`` directly under the host root."""
25
- host = tmp_path / "my-app"
26
- (host / "client_app").mkdir(parents=True)
27
- (host / "package.json").write_text(json.dumps({"name": "my-app"}))
28
-
29
- assert repo_root_from_client_app(host / "client_app") == host.resolve()
30
-
31
-
32
- def test_repo_root_prefers_workspace_package_json_over_nearer_one(tmp_path: Path) -> None:
33
- """When a non-workspace package.json sits between client_app and the
34
- workspace root, the workspace one wins so npm install runs at the right cwd."""
35
- workspace = tmp_path / "ws"
36
- (workspace / "host" / "client_app").mkdir(parents=True)
37
- (workspace / "package.json").write_text(
38
- json.dumps({"name": "ws", "workspaces": ["host/client_app"]})
39
- )
40
- (workspace / "host" / "package.json").write_text(json.dumps({"name": "ws-host"}))
41
-
42
- assert repo_root_from_client_app(workspace / "host" / "client_app") == workspace.resolve()
43
-
44
-
45
- def test_repo_root_falls_back_to_nearest_package_json(tmp_path: Path) -> None:
46
- """No workspaces field anywhere — settle on the nearest package.json found."""
47
- root = tmp_path / "root"
48
- (root / "client_app").mkdir(parents=True)
49
- (root / "package.json").write_text(json.dumps({"name": "root"}))
50
-
51
- assert repo_root_from_client_app(root / "client_app") == root.resolve()
52
-
53
-
54
- def test_repo_root_falls_back_to_two_levels_up_if_no_package_json(tmp_path: Path) -> None:
55
- """No package.json exists yet — preserve the legacy two-up behaviour so
56
- framework-internal callers still get a path before any npm setup."""
57
- deep = tmp_path / "outer" / "inner" / "client_app"
58
- deep.mkdir(parents=True)
59
-
60
- assert repo_root_from_client_app(deep) == (tmp_path / "outer").resolve()