framework-m-core 0.4.2__py3-none-any.whl → 0.5.0__py3-none-any.whl

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.
@@ -0,0 +1,137 @@
1
+ """Init CLI Command - Scaffold frontend from template.
2
+
3
+ This module provides commands to initialize new components of Framework M.
4
+
5
+ Architecture:
6
+ - No global state - all paths passed explicitly
7
+ - Template resources via importlib.resources (Python 3.9+)
8
+ - Explicit error handling for file operations
9
+ - User feedback with print statements
10
+
11
+ Usage:
12
+ m init:frontend # Initialize in ./frontend
13
+ m init:frontend ../my-frontend # Initialize in custom location
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import shutil
19
+ import subprocess
20
+ from importlib.resources import files
21
+ from pathlib import Path
22
+ from typing import Annotated
23
+
24
+ import cyclopts
25
+
26
+
27
+ def _copy_template(source_template: Path, target: Path) -> None:
28
+ """Copy template directory to target location.
29
+
30
+ Args:
31
+ source_template: Source template directory
32
+ target: Target directory
33
+
34
+ Raises:
35
+ FileExistsError: If target already exists
36
+ RuntimeError: If copy fails
37
+ """
38
+ if target.exists():
39
+ msg = f"Target directory already exists: {target}"
40
+ raise FileExistsError(msg)
41
+
42
+ try:
43
+ shutil.copytree(source_template, target)
44
+ print(f"✓ Copied template to {target}")
45
+ except Exception as e:
46
+ msg = f"Failed to copy template: {e}"
47
+ raise RuntimeError(msg) from e
48
+
49
+
50
+ def _install_dependencies(target: Path) -> None:
51
+ """Install pnpm dependencies in target directory.
52
+
53
+ Args:
54
+ target: Frontend directory
55
+
56
+ Raises:
57
+ RuntimeError: If pnpm install fails
58
+ """
59
+ print("\nInstalling dependencies with pnpm...")
60
+ try:
61
+ subprocess.run(
62
+ ["pnpm", "install"],
63
+ cwd=str(target),
64
+ check=True,
65
+ )
66
+ print("✓ Dependencies installed")
67
+ except subprocess.CalledProcessError as e:
68
+ msg = f"Failed to install dependencies: {e}"
69
+ raise RuntimeError(msg) from e
70
+
71
+
72
+ def init_frontend_command(
73
+ target: Annotated[
74
+ Path | None,
75
+ cyclopts.Parameter(
76
+ name=["target"],
77
+ help="Target directory for frontend (default: ./frontend)",
78
+ ),
79
+ ] = None,
80
+ ) -> None:
81
+ """Initialize a new frontend from template.
82
+
83
+ This scaffolds the bundled Desk frontend template to a target directory
84
+ and installs dependencies. The template includes:
85
+ - React + TypeScript + Vite
86
+ - @framework-m/desk npm package integration
87
+ - Refine.dev setup with authProvider, dataProvider, liveProvider
88
+ - Tailwind CSS + shadcn/ui components
89
+
90
+ Examples:
91
+ m init:frontend # Initialize in ./frontend
92
+ m init:frontend ../my-frontend # Custom location
93
+
94
+ Architecture Notes:
95
+ - Uses apps/studio/frontend as template source
96
+ - No global state - all paths explicit
97
+ - Validates target doesn't exist (prevents overwrites)
98
+ - Runs pnpm install automatically
99
+ """
100
+ # Default to ./frontend
101
+ if target is None:
102
+ target = Path("frontend")
103
+
104
+ # Resolve to absolute path
105
+ target = target.resolve()
106
+
107
+ # Find template source from framework_m package
108
+ try:
109
+ template_resource = files("framework_m") / "templates" / "frontend"
110
+ if not template_resource.is_dir():
111
+ msg = "Frontend template not found in framework_m package"
112
+ raise RuntimeError(msg)
113
+
114
+ # Convert to Path for file operations
115
+ template_source = Path(str(template_resource))
116
+ except Exception as e:
117
+ msg = f"Failed to locate template: {e}"
118
+ raise RuntimeError(msg) from e
119
+
120
+ print(f"Initializing frontend at {target}")
121
+ print(f"Template source: {template_source}")
122
+
123
+ # Copy template
124
+ _copy_template(template_source, target)
125
+
126
+ # Install dependencies
127
+ _install_dependencies(target)
128
+
129
+ print("\n✓ Frontend initialized successfully!")
130
+ print("\nNext steps:")
131
+ print(f" cd {target}")
132
+ print(" pnpm dev")
133
+ print("\nOr start with:")
134
+ print(" m start --with-frontend")
135
+
136
+
137
+ __all__ = ["init_frontend_command"]
@@ -2,6 +2,7 @@ import cyclopts
2
2
 
3
3
  from framework_m_core import __version__
4
4
  from framework_m_core.cli.config import config_set_command, config_show_command
5
+ from framework_m_core.cli.init import init_frontend_command
5
6
  from framework_m_core.cli.plugin_loader import load_plugins
6
7
  from framework_m_core.cli.utility import info_command
7
8
 
@@ -18,6 +19,9 @@ app.command(info_command, name="info")
18
19
  app.command(config_show_command, name="config:show")
19
20
  app.command(config_set_command, name="config:set")
20
21
 
22
+ # Init commands
23
+ app.command(init_frontend_command, name="init:frontend")
24
+
21
25
  # Load plugins
22
26
  load_plugins(app)
23
27
 
@@ -94,6 +94,11 @@ def register_plugins(app: cyclopts.App, plugins: list[EntryPoint]) -> None:
94
94
  logger.warning(
95
95
  f"Plugin '{ep.name}' is not a cyclopts App or callable, skipping"
96
96
  )
97
+ except cyclopts.exceptions.CommandCollisionError:
98
+ # Command already registered (core takes precedence over plugins)
99
+ logger.debug(
100
+ f"CLI plugin '{ep.name}' skipped - command already registered by core"
101
+ )
97
102
  except Exception as e:
98
103
  logger.warning(
99
104
  f"Failed to register CLI plugin '{ep.name}': {e}",
@@ -27,7 +27,7 @@ Example:
27
27
  from __future__ import annotations
28
28
 
29
29
  from datetime import UTC, datetime
30
- from enum import Enum
30
+ from enum import StrEnum
31
31
  from typing import Any
32
32
  from uuid import UUID, uuid4
33
33
 
@@ -39,7 +39,7 @@ def _utc_now() -> datetime:
39
39
  return datetime.now(UTC)
40
40
 
41
41
 
42
- class OutboxStatus(str, Enum):
42
+ class OutboxStatus(StrEnum):
43
43
  """Status of an outbox entry.
44
44
 
45
45
  Attributes:
framework_m_core/pii.py CHANGED
@@ -23,7 +23,7 @@ Example:
23
23
 
24
24
  from __future__ import annotations
25
25
 
26
- from enum import Enum
26
+ from enum import StrEnum
27
27
  from typing import Any
28
28
 
29
29
  from pydantic import BaseModel, Field
@@ -35,7 +35,7 @@ from framework_m_core.config import load_config
35
35
  # =============================================================================
36
36
 
37
37
 
38
- class AuthMode(str, Enum):
38
+ class AuthMode(StrEnum):
39
39
  """Available authentication modes.
40
40
 
41
41
  Attributes:
@@ -142,7 +142,7 @@ def is_sensitive_pii(field_name: str) -> bool:
142
142
  # =============================================================================
143
143
 
144
144
 
145
- class DeletionMode(str, Enum):
145
+ class DeletionMode(StrEnum):
146
146
  """How to handle user data on account deletion.
147
147
 
148
148
  Attributes:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: framework-m-core
3
- Version: 0.4.2
3
+ Version: 0.5.0
4
4
  Summary: Core protocols and dependency injection for Framework M
5
5
  Author: Framework M Contributors
6
6
  License: MIT
@@ -5,7 +5,7 @@ framework_m_core/decorators.py,sha256=UFY1HbiSLVmjKO8Ov7C6jdfMq7ND5OgwHb8WfIHiwT
5
5
  framework_m_core/exceptions.py,sha256=BwFviTUBB4FW2_fzDWYB5eAYzf0z1GVVX-vjcU4Fi78,4098
6
6
  framework_m_core/permission_lookup.py,sha256=dhJHiazQQ8SvC_MYizmFfwKgrTgT5g0qxa4Vq9thk8Q,6370
7
7
  framework_m_core/permissions.py,sha256=e7RRA1xbIO1WfFPEiTO4lDmvxkhW29QBisHPYqbzbt0,7177
8
- framework_m_core/pii.py,sha256=uIO3Mbrw9OGK9RLjTUsi6jJ-W_YpvPlWUT6N4Zsq2K4,5885
8
+ framework_m_core/pii.py,sha256=lu3dk52SAS0TLAEDnLgiWbvHIzZtceidv5-SOVh-Z6A,5884
9
9
  framework_m_core/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
10
  framework_m_core/registry.py,sha256=FHgzaczGQjxIQU2JosnKppDyl3DB6ud09Ukgx-6GTVA,10279
11
11
  framework_m_core/rls.py,sha256=iBKGiWepYGW3CrObJeRjSra6Xv7xODRjEF6J--2NhsU,6158
@@ -15,8 +15,9 @@ framework_m_core/system_context.py,sha256=Axf5zixlBc81BcXLUuiiUBH2YzuRJ8BHj2CX2y
15
15
  framework_m_core/unit_of_work.py,sha256=FtOtsW0IKuxelyb_h2Hjszhr6ZWwQLIoVI4ZJle3bv4,4158
16
16
  framework_m_core/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  framework_m_core/cli/config.py,sha256=iePjU1xcsMSvK5M6QpO_FpossOOetRb9NdaK4T4ym2Y,3457
18
- framework_m_core/cli/main.py,sha256=hyavgEmTZ8_YcubPppFkr_BN7bf6Ai1FhF3iMYmYdHo,636
19
- framework_m_core/cli/plugin_loader.py,sha256=KqDI-vR-vhb5v61_rJxT7-6Z0-TuYbtxZqUO-Fj8d7M,3947
18
+ framework_m_core/cli/init.py,sha256=Ru6DIeMW42akVnkV70Q6HWd9jp6M8UbrvO1gCLpGawg,4022
19
+ framework_m_core/cli/main.py,sha256=5J7BTrFZSgUfHtbOhnXS8aKqZ6B0jkicZ5U8ZP5pOeY,770
20
+ framework_m_core/cli/plugin_loader.py,sha256=-ITJk3DVyRjUl1PwNcjXNk6padDfM6dxmalRb4mf4b4,4210
20
21
  framework_m_core/cli/utility.py,sha256=CHPIs5_yfKb-xLL-H2NKM_FOQkFSVTlZGBL1C1njgYU,1275
21
22
  framework_m_core/doctypes/__init__.py,sha256=vC7opN27HoEKiRDuPwN-tq-dXwG3retVoDKeBlbiTrg,1847
22
23
  framework_m_core/doctypes/activity_log.py,sha256=_5XYRqe0NC98u1_9aWpZk1DdjHaTNPKp-Qb02_aE8pI,3564
@@ -49,7 +50,7 @@ framework_m_core/domain/base_controller.py,sha256=fXBMtogBxI-Igw8ntXH2iBe-OUFM1Y
49
50
  framework_m_core/domain/base_doctype.py,sha256=NShhuo_4rLruFLG4NL26swZ-bd3P-f4EIO2K5CLeaWc,9258
50
51
  framework_m_core/domain/mixins.py,sha256=Yk8fwYWyq9MJp_uU_r5jxf1nj2q5lYN3i1Ip_1ORFe8,3596
51
52
  framework_m_core/domain/naming_counter.py,sha256=UWQeOSeGqFHCFc-XqxWnudCsZHgSM43f5u93lvj3HSY,1626
52
- framework_m_core/domain/outbox.py,sha256=omso-0AJjSlxxyPy3TuPIOWRDLyHs_G2qMeEu9vgZMk,2778
53
+ framework_m_core/domain/outbox.py,sha256=s39lpeTIni3Olr9K28a1PhEb19V6Qvj6WxfF5Td_DK0,2779
53
54
  framework_m_core/events/__init__.py,sha256=3qB9L7DjPTArt6OQnehsGXLJbLJ91DpWPMdsMsvtBS8,2760
54
55
  framework_m_core/interfaces/__init__.py,sha256=mfYjbV4Iwc2l_iaj2iVf2AbElc9LO-3a9WIqCBRXLaE,672
55
56
  framework_m_core/interfaces/audit.py,sha256=tRD63O6vCjOHt4R3XtBsQM-BeCjIjATSqDP5O1-plP8,6740
@@ -82,6 +83,6 @@ framework_m_core/interfaces/workflow.py,sha256=C1LDohdvmWF9P_PMq0FVHySHb2URHzxAO
82
83
  framework_m_core/services/__init__.py,sha256=xcv_UWRyhBSt70yJBVBEyrtRSpaINj-JC1Rs8KIpnUw,351
83
84
  framework_m_core/services/user_manager.py,sha256=PYI0URoVOc0cU4bQUlrIT1nK1VTwy2ThsvChhDeJ2Ss,5696
84
85
  framework_m_core/types/job_context.py,sha256=9Xhqidj--eiJZNASENRsQi_qZkFj-Nsy_-fDj1uMP9E,2766
85
- framework_m_core-0.4.2.dist-info/METADATA,sha256=lah3OjeIYm8JmRip9wAYvWnSHVCwPdHcUvYwCNPo0vg,2663
86
- framework_m_core-0.4.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
87
- framework_m_core-0.4.2.dist-info/RECORD,,
86
+ framework_m_core-0.5.0.dist-info/METADATA,sha256=y6_og0PoEMz5hxI1vFomNLFwpxr1Qs8ASIEhTI66V-8,2663
87
+ framework_m_core-0.5.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
88
+ framework_m_core-0.5.0.dist-info/RECORD,,