coregen-cli 3.1.6__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.
Files changed (172) hide show
  1. coregen.py +1585 -0
  2. coregen_cli-3.1.6.dist-info/METADATA +1211 -0
  3. coregen_cli-3.1.6.dist-info/RECORD +172 -0
  4. coregen_cli-3.1.6.dist-info/WHEEL +5 -0
  5. coregen_cli-3.1.6.dist-info/entry_points.txt +2 -0
  6. coregen_cli-3.1.6.dist-info/top_level.txt +2 -0
  7. generators/__init__.py +1 -0
  8. generators/assets/__init__.py +1 -0
  9. generators/assets/assets.py +30 -0
  10. generators/assets/main.py +4 -0
  11. generators/config/__init__.py +1 -0
  12. generators/config/analisy_options.py +42 -0
  13. generators/config/main.py +11 -0
  14. generators/config/pubspec.py +139 -0
  15. generators/cursor/__init__.py +3 -0
  16. generators/cursor/setup.py +87 -0
  17. generators/helpers/__init__.py +56 -0
  18. generators/helpers/component.py +1030 -0
  19. generators/helpers/config.py +255 -0
  20. generators/helpers/data_source.py +180 -0
  21. generators/helpers/domain.py +469 -0
  22. generators/helpers/feature.py +654 -0
  23. generators/helpers/navigation.py +274 -0
  24. generators/helpers/page.py +63 -0
  25. generators/helpers/project.py +33 -0
  26. generators/helpers/utils.py +338 -0
  27. generators/helpers/validation.py +364 -0
  28. generators/initializator.py +68 -0
  29. generators/main.py +81 -0
  30. generators/static/assets/logo.png +0 -0
  31. generators/static/assets/mock/auth_user.json +5 -0
  32. generators/static/assets/svgs/apple.svg +4 -0
  33. generators/static/assets/svgs/google.svg +2 -0
  34. generators/static/cursor/AGENTS.md.jinja +37 -0
  35. generators/static/cursor/agents/doc-writer.md +20 -0
  36. generators/static/cursor/agents/epic-orchestrator.md +28 -0
  37. generators/static/cursor/agents/feature-implementer.md +29 -0
  38. generators/static/cursor/agents/integration-wiring.md +30 -0
  39. generators/static/cursor/agents/layer-application.md +21 -0
  40. generators/static/cursor/agents/layer-guardian.md +21 -0
  41. generators/static/cursor/agents/layer-infrastructure.md +31 -0
  42. generators/static/cursor/agents/layer-model.md +24 -0
  43. generators/static/cursor/agents/layer-presentation.md +33 -0
  44. generators/static/cursor/docs/architecture/APIS_AND_INTEGRATION.md.jinja +42 -0
  45. generators/static/cursor/docs/architecture/CARAVAGGIO_COMPONENTS.md.jinja +317 -0
  46. generators/static/cursor/docs/architecture/DDD_LAYERS.md.jinja +32 -0
  47. generators/static/cursor/docs/architecture/FILE_TEMPLATES.md.jinja +76 -0
  48. generators/static/cursor/docs/architecture/MOCK_AND_REMOTE_DATA.md.jinja +87 -0
  49. generators/static/cursor/docs/architecture/REFERENCE_IMPLEMENTATIONS.md.jinja +48 -0
  50. generators/static/cursor/docs/architecture/WIDGETS_AND_CARAVAGGIO.md.jinja +76 -0
  51. generators/static/cursor/docs/epics/README.md +17 -0
  52. generators/static/cursor/lib/domain/AGENTS.md +23 -0
  53. generators/static/cursor/lib/features/AGENTS.md +23 -0
  54. generators/static/cursor/lib/widgets/AGENTS.md +21 -0
  55. generators/static/cursor/rules/apis-layer.mdc.jinja +21 -0
  56. generators/static/cursor/rules/application-layer.mdc.jinja +12 -0
  57. generators/static/cursor/rules/architecture-core.mdc.jinja +43 -0
  58. generators/static/cursor/rules/dart-conventions.mdc.jinja +14 -0
  59. generators/static/cursor/rules/domain-layer.mdc.jinja +24 -0
  60. generators/static/cursor/rules/login-context.mdc.jinja +16 -0
  61. generators/static/cursor/rules/presentation-layer.mdc.jinja +15 -0
  62. generators/static/cursor/rules/quality-gate.mdc.jinja +14 -0
  63. generators/static/cursor/rules/ui-caravaggio.mdc.jinja +22 -0
  64. generators/static/cursor/skills/caravaggio-ui/SKILL.md +138 -0
  65. generators/static/cursor/skills/epic-delivery/SKILL.md +74 -0
  66. generators/static/cursor/skills/epic-delivery/templates/epic-status.md +14 -0
  67. generators/static/cursor/skills/epic-delivery/templates/epic.md +36 -0
  68. generators/static/templates/apis/common/constants_template.jinja +48 -0
  69. generators/static/templates/apis/common/data_source_config_template.jinja +47 -0
  70. generators/static/templates/apis/core/api_injectable_module_template.jinja +14 -0
  71. generators/static/templates/apis/interceptors/api_logger_template.jinja +135 -0
  72. generators/static/templates/apis/interceptors/auth_interceptor_template.jinja +207 -0
  73. generators/static/templates/auth/application/auth_bloc_template.jinja +36 -0
  74. generators/static/templates/auth/application/auth_event_template.jinja +7 -0
  75. generators/static/templates/auth/application/auth_state_template.jinja +8 -0
  76. generators/static/templates/auth/infrastructure/auth_facade_module_template.jinja +16 -0
  77. generators/static/templates/auth/infrastructure/auth_facade_template.jinja +288 -0
  78. generators/static/templates/auth/infrastructure/mock_auth_facade_template.jinja +106 -0
  79. generators/static/templates/auth/model/auth_failure_template.jinja +11 -0
  80. generators/static/templates/auth/model/i_auth_facade_template.jinja +24 -0
  81. generators/static/templates/auth/model/user_template.jinja +34 -0
  82. generators/static/templates/auth/model/value_objects_template.jinja +54 -0
  83. generators/static/templates/auth/presentation/login_screen_template.jinja +24 -0
  84. generators/static/templates/auth/sign_in_form/application/sign_in_form_bloc_template.jinja +139 -0
  85. generators/static/templates/auth/sign_in_form/application/sign_in_form_event_template.jinja +15 -0
  86. generators/static/templates/auth/sign_in_form/application/sign_in_form_state_template.jinja +26 -0
  87. generators/static/templates/auth/sign_in_form/presentation/apple_form_template.jinja +16 -0
  88. generators/static/templates/auth/sign_in_form/presentation/email_password_form_template_INACTIVE.jinja +94 -0
  89. generators/static/templates/auth/sign_in_form/presentation/google_form_template.jinja +15 -0
  90. generators/static/templates/auth/sign_in_form/presentation/sign_in_form_template.jinja +381 -0
  91. generators/static/templates/component/component_bloc_template.jinja +49 -0
  92. generators/static/templates/component/component_event_template.jinja +7 -0
  93. generators/static/templates/component/component_form_bloc_template.jinja +56 -0
  94. generators/static/templates/component/component_form_event_template.jinja +7 -0
  95. generators/static/templates/component/component_form_state_template.jinja +18 -0
  96. generators/static/templates/component/component_form_widget_template.jinja +130 -0
  97. generators/static/templates/component/component_list_widget_template.jinja +95 -0
  98. generators/static/templates/component/component_state_template.jinja +9 -0
  99. generators/static/templates/component/component_widget_template.jinja +113 -0
  100. generators/static/templates/core/bloc/base_form_bloc_template.jinja +32 -0
  101. generators/static/templates/core/errors/error_localizer_template.jinja +61 -0
  102. generators/static/templates/core/infrastructure/base_mapper_template.jinja +21 -0
  103. generators/static/templates/core/infrastructure/base_repository_mixin_template.jinja +78 -0
  104. generators/static/templates/core/infrastructure/firebase_injectable_module_template.jinja +11 -0
  105. generators/static/templates/core/infrastructure/firestore_helpers_template.jinja +22 -0
  106. generators/static/templates/core/infrastructure/repository_error_handler_template.jinja +44 -0
  107. generators/static/templates/core/model/common_interfaces_template.jinja +3 -0
  108. generators/static/templates/core/model/entity_template.jinja +5 -0
  109. generators/static/templates/core/model/errors_template.jinja +15 -0
  110. generators/static/templates/core/model/failures_template.jinja +97 -0
  111. generators/static/templates/core/model/value_objects_template.jinja +135 -0
  112. generators/static/templates/core/model/value_validators_template.jinja +105 -0
  113. generators/static/templates/core/presentation/app_drawer_template.jinja +52 -0
  114. generators/static/templates/core/presentation/app_widget_template.jinja +41 -0
  115. generators/static/templates/core/presentation/bottom_nav_bar_template.jinja +32 -0
  116. generators/static/templates/domain/domain_mapper_template.jinja +39 -0
  117. generators/static/templates/domain/domain_remote_service_template.jinja +46 -0
  118. generators/static/templates/domain/domain_repository_template.jinja +90 -0
  119. generators/static/templates/domain/domain_service_module_template.jinja +23 -0
  120. generators/static/templates/domain/enum_template.jinja +3 -0
  121. generators/static/templates/domain/i_domain_service_template.jinja +18 -0
  122. generators/static/templates/domain/mock_domain_service_template.jinja +73 -0
  123. generators/static/templates/feature/feature_bloc_template.jinja +104 -0
  124. generators/static/templates/feature/feature_dto_template.jinja +26 -0
  125. generators/static/templates/feature/feature_entity_template.jinja +23 -0
  126. generators/static/templates/feature/feature_event_template.jinja +20 -0
  127. generators/static/templates/feature/feature_extensions_template.jinja +25 -0
  128. generators/static/templates/feature/feature_failure_template.jinja +11 -0
  129. generators/static/templates/feature/feature_page_template.jinja +113 -0
  130. generators/static/templates/feature/feature_repository_template.jinja +112 -0
  131. generators/static/templates/feature/feature_state_template.jinja +20 -0
  132. generators/static/templates/feature/i_feature_repository_template.jinja +21 -0
  133. generators/static/templates/feature/value_object_field_template.jinja +0 -0
  134. generators/static/templates/feature/value_object_template.jinja +0 -0
  135. generators/static/templates/feature/value_validators_template.jinja +57 -0
  136. generators/static/templates/home/home_screen_template.jinja +68 -0
  137. generators/static/templates/home/presentation/home_screen_template.jinja +52 -0
  138. generators/static/templates/home/screen_template.jinja +12 -0
  139. generators/static/templates/injection_template.jinja +8 -0
  140. generators/static/templates/logging/analytics_logging_template.jinja +42 -0
  141. generators/static/templates/logging/console_template.jinja +50 -0
  142. generators/static/templates/logging/logger_injectable_module_template.jinja +24 -0
  143. generators/static/templates/logging/logger_template.jinja +118 -0
  144. generators/static/templates/main_template.jinja +43 -0
  145. generators/static/templates/page_template.jinja +29 -0
  146. generators/static/templates/router_template.jinja +26 -0
  147. generators/static/templates/splash/presentation/splash_screen_auth_template.jinja +97 -0
  148. generators/static/templates/splash/presentation/splash_screen_template.jinja +51 -0
  149. generators/static/templates/storage/storage_repository_template.jinja +80 -0
  150. generators/static/templates/user_profile/infrastructure/user_profile_dto_template.jinja +27 -0
  151. generators/static/templates/user_profile/infrastructure/user_profile_mapper_template.jinja +43 -0
  152. generators/static/templates/user_profile/infrastructure/user_profile_repository_template.jinja +82 -0
  153. generators/static/templates/user_profile/infrastructure/user_profile_service_template.jinja +27 -0
  154. generators/static/templates/user_profile/model/i_user_profile_repository_template.jinja +34 -0
  155. generators/static/templates/user_profile/model/user_profile_failure_template.jinja +11 -0
  156. generators/static/templates/user_profile/model/user_profile_template.jinja +31 -0
  157. generators/static/templates/widgets/common/custom_scaffold_template.jinja +52 -0
  158. generators/static/templates/widgets/common/error_widget_template.jinja +22 -0
  159. generators/static/templates/widgets/common/loading_widget_template.jinja +14 -0
  160. generators/static/templates/widgets/common/unknown_state_widget_template.jinja +23 -0
  161. generators/templates/__init__.py +1 -0
  162. generators/templates/_core/core_generator.py +121 -0
  163. generators/templates/apis/apis_generator.py +50 -0
  164. generators/templates/auth/auth_generator.py +40 -0
  165. generators/templates/auth/sign_in_form_generator.py +22 -0
  166. generators/templates/copier.py +51 -0
  167. generators/templates/home/home_generator.py +26 -0
  168. generators/templates/lib/lib_generator.py +40 -0
  169. generators/templates/logging/logging_generator.py +28 -0
  170. generators/templates/main.py +75 -0
  171. generators/templates/splash/splash_generator.py +20 -0
  172. generators/templates/storage/storage_generator.py +5 -0
@@ -0,0 +1,255 @@
1
+ """Configuration management for CoreGen.
2
+
3
+ Supports configuration from multiple sources with priority:
4
+ 1. CLI flags (highest priority)
5
+ 2. Project-level coregen.yaml
6
+ 3. Global ~/.coregenrc
7
+ 4. Default values (lowest priority)
8
+ """
9
+
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Any, Optional
13
+ from dataclasses import dataclass, field
14
+
15
+ import yaml
16
+ from rich.console import Console
17
+
18
+ console = Console()
19
+
20
+ # Default configuration values
21
+ DEFAULTS = {
22
+ "feature_folder": "features", # Default features folder
23
+ "domain_folder": "domain", # Domain entities folder
24
+ "component_folder": "features/components", # Default components folder
25
+ "auto_run_build_runner": True,
26
+ "primary_color": "#2196F3",
27
+ "secondary_color": "#FF9800",
28
+ }
29
+
30
+ # Config file names
31
+ PROJECT_CONFIG_FILE = "coregen.yaml"
32
+ GLOBAL_CONFIG_FILE = ".coregenrc"
33
+
34
+
35
+ @dataclass
36
+ class CoreGenConfig:
37
+ """Configuration class for CoreGen."""
38
+
39
+ # Folder defaults
40
+ feature_folder: str = "features"
41
+ domain_folder: str = "domain"
42
+ component_folder: str = "features/components"
43
+
44
+ # Automation
45
+ auto_run_build_runner: bool = True
46
+
47
+ # UI/Styling
48
+ primary_color: str = "#2196F3"
49
+ secondary_color: str = "#FF9800"
50
+
51
+ # Custom templates (optional paths)
52
+ custom_templates: dict = field(default_factory=dict)
53
+
54
+ # Extra dependencies to add
55
+ extra_dependencies: list = field(default_factory=list)
56
+
57
+ # Source of config (for debugging)
58
+ _source: str = "defaults"
59
+
60
+ @classmethod
61
+ def from_dict(cls, data: dict, source: str = "unknown") -> "CoreGenConfig":
62
+ """Create config from dictionary."""
63
+ config = cls()
64
+
65
+ # Map nested 'defaults' section
66
+ if "defaults" in data:
67
+ defaults = data["defaults"]
68
+ if "feature_folder" in defaults:
69
+ config.feature_folder = defaults["feature_folder"]
70
+ if "domain_folder" in defaults:
71
+ config.domain_folder = defaults["domain_folder"]
72
+ if "component_folder" in defaults:
73
+ config.component_folder = defaults["component_folder"]
74
+ if "auto_run_build_runner" in defaults:
75
+ config.auto_run_build_runner = defaults["auto_run_build_runner"]
76
+
77
+ # Map 'styling' section
78
+ if "styling" in data:
79
+ styling = data["styling"]
80
+ if "primary_color" in styling:
81
+ config.primary_color = styling["primary_color"]
82
+ if "secondary_color" in styling:
83
+ config.secondary_color = styling["secondary_color"]
84
+
85
+ # Map 'templates' section
86
+ if "templates" in data:
87
+ config.custom_templates = data["templates"]
88
+
89
+ # Map 'dependencies' section
90
+ if "dependencies" in data:
91
+ config.extra_dependencies = data["dependencies"]
92
+
93
+ # Also support flat structure (for simple configs)
94
+ for key in ["feature_folder", "domain_folder", "component_folder",
95
+ "auto_run_build_runner",
96
+ "primary_color", "secondary_color"]:
97
+ if key in data:
98
+ setattr(config, key, data[key])
99
+
100
+ config._source = source
101
+ return config
102
+
103
+ def merge_with(self, other: "CoreGenConfig") -> "CoreGenConfig":
104
+ """Merge this config with another, other takes precedence for non-default values."""
105
+ result = CoreGenConfig()
106
+
107
+ # Feature folder: use other's if set (default is "features")
108
+ result.feature_folder = other.feature_folder if other.feature_folder != "features" else self.feature_folder
109
+
110
+ # Component folder: use other's if set (default is "features/components")
111
+ result.component_folder = other.component_folder if other.component_folder != "features/components" else self.component_folder
112
+
113
+ # Domain folder has default "domain", so use other's if set
114
+ result.domain_folder = other.domain_folder if other.domain_folder != "domain" else self.domain_folder
115
+
116
+ # Automation: use other's value (it's explicitly set)
117
+ result.auto_run_build_runner = other.auto_run_build_runner
118
+
119
+ for key in ["primary_color", "secondary_color"]:
120
+ setattr(result, key, getattr(other, key))
121
+
122
+ # Merge dictionaries and lists
123
+ result.custom_templates = {**self.custom_templates, **other.custom_templates}
124
+ result.extra_dependencies = self.extra_dependencies + other.extra_dependencies
125
+
126
+ result._source = f"{self._source} + {other._source}"
127
+ return result
128
+
129
+
130
+ def load_yaml_file(path: Path) -> Optional[dict]:
131
+ """Load a YAML file, return None if not found or invalid."""
132
+ if not path.exists():
133
+ return None
134
+
135
+ try:
136
+ with open(path, 'r') as f:
137
+ return yaml.safe_load(f) or {}
138
+ except yaml.YAMLError as e:
139
+ console.print(f"[yellow]⚠️ Warning: Invalid YAML in {path}: {e}[/yellow]")
140
+ return None
141
+ except Exception as e:
142
+ console.print(f"[yellow]⚠️ Warning: Could not read {path}: {e}[/yellow]")
143
+ return None
144
+
145
+
146
+ def get_global_config_path() -> Path:
147
+ """Get path to global config file."""
148
+ return Path.home() / GLOBAL_CONFIG_FILE
149
+
150
+
151
+ def get_project_config_path(project_dir: Path) -> Path:
152
+ """Get path to project config file."""
153
+ return project_dir / PROJECT_CONFIG_FILE
154
+
155
+
156
+ def load_config(project_dir: Optional[Path] = None) -> CoreGenConfig:
157
+ """
158
+ Load configuration from all sources with proper priority.
159
+
160
+ Priority (highest to lowest):
161
+ 1. CLI flags (handled by caller)
162
+ 2. Project-level coregen.yaml
163
+ 3. Global ~/.coregenrc
164
+ 4. Default values
165
+ """
166
+ # Start with defaults
167
+ config = CoreGenConfig(_source="defaults")
168
+
169
+ # Load global config
170
+ global_path = get_global_config_path()
171
+ global_data = load_yaml_file(global_path)
172
+ if global_data:
173
+ global_config = CoreGenConfig.from_dict(global_data, f"~/{GLOBAL_CONFIG_FILE}")
174
+ config = config.merge_with(global_config)
175
+
176
+ # Load project config (if project_dir provided)
177
+ if project_dir:
178
+ project_path = get_project_config_path(project_dir)
179
+ project_data = load_yaml_file(project_path)
180
+ if project_data:
181
+ project_config = CoreGenConfig.from_dict(project_data, PROJECT_CONFIG_FILE)
182
+ config = config.merge_with(project_config)
183
+
184
+ return config
185
+
186
+
187
+ def apply_cli_overrides(config: CoreGenConfig, **cli_args) -> CoreGenConfig:
188
+ """
189
+ Apply CLI argument overrides to config.
190
+ Only non-None CLI args override config values.
191
+ """
192
+ if cli_args.get("folder"):
193
+ config.feature_folder = cli_args["folder"]
194
+ config.component_folder = cli_args["folder"]
195
+
196
+ if cli_args.get("no_build") is True:
197
+ config.auto_run_build_runner = False
198
+
199
+ return config
200
+
201
+
202
+ def create_default_config(project_dir: Path, project_name: str) -> Path:
203
+ """Create a default coregen.yaml in the project directory."""
204
+ config_path = project_dir / PROJECT_CONFIG_FILE
205
+
206
+ default_config = f"""# CoreGen Configuration
207
+ # Project: {project_name}
208
+ # Documentation: https://github.com/lorenzo9598/coregen
209
+
210
+ # Default folders for generated code
211
+ defaults:
212
+ feature_folder: "features" # Features folder
213
+ domain_folder: "domain" # Domain entities folder (shared entities)
214
+ component_folder: "features/components" # Components folder
215
+ auto_run_build_runner: true # Run build_runner after generation
216
+
217
+ # UI/Styling configuration
218
+ styling:
219
+ primary_color: "#2196F3"
220
+ secondary_color: "#FF9800"
221
+
222
+ # Custom templates (optional)
223
+ # templates:
224
+ # entity: "templates/custom_entity.jinja"
225
+ # bloc: "templates/custom_bloc.jinja"
226
+
227
+ # Additional dependencies to include (optional)
228
+ # dependencies:
229
+ # - package: dartz
230
+ # version: ^0.10.1
231
+ """
232
+
233
+ with open(config_path, 'w') as f:
234
+ f.write(default_config)
235
+
236
+ return config_path
237
+
238
+
239
+ def show_config(config: CoreGenConfig) -> None:
240
+ """Display current configuration using rich."""
241
+ from rich.table import Table
242
+ from rich.panel import Panel
243
+
244
+ table = Table(show_header=True, header_style="bold cyan")
245
+ table.add_column("Setting", style="dim")
246
+ table.add_column("Value")
247
+
248
+ table.add_row("Feature Folder", config.feature_folder)
249
+ table.add_row("Domain Folder", config.domain_folder)
250
+ table.add_row("Component Folder", config.component_folder)
251
+ table.add_row("Auto Build Runner", "✅" if config.auto_run_build_runner else "❌")
252
+ table.add_row("Primary Color", config.primary_color)
253
+ table.add_row("Secondary Color", config.secondary_color)
254
+
255
+ console.print(Panel(table, title=f"⚙️ Configuration ({config._source})", border_style="blue"))
@@ -0,0 +1,180 @@
1
+ """Mock vs remote data source helpers — DataSourceConfig and mock JSON assets."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import re
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ from generators.templates.copier import generate_file
11
+
12
+ REMOTE_PATTERN = re.compile(
13
+ r"['\"](?P<key>[a-z][a-z0-9_]*)['\"]\s*:\s*DataSource\.remote",
14
+ )
15
+
16
+
17
+ def scan_domain_entity_keys(lib_path: Path, domain_folder: str = "domain") -> list[str]:
18
+ """Return snake_case entity keys that have a repository interface."""
19
+ domain_root = lib_path / domain_folder
20
+ if not domain_root.is_dir():
21
+ return []
22
+ keys: list[str] = []
23
+ for entity_dir in sorted(domain_root.iterdir()):
24
+ if not entity_dir.is_dir():
25
+ continue
26
+ i_repo = entity_dir / "model" / f"i_{entity_dir.name}_repository.dart"
27
+ if i_repo.is_file():
28
+ keys.append(entity_dir.name)
29
+ return keys
30
+
31
+
32
+ def read_preserved_remote_keys(config_path: Path) -> set[str]:
33
+ """Parse existing data_source_config.dart for entries already set to remote."""
34
+ if not config_path.is_file():
35
+ return set()
36
+ content = config_path.read_text(encoding="utf-8")
37
+ return {m.group("key") for m in REMOTE_PATTERN.finditer(content)}
38
+
39
+
40
+ def build_entities_map(
41
+ entity_keys: list[str],
42
+ *,
43
+ has_login: bool,
44
+ preserved_remote: set[str],
45
+ ) -> dict[str, str]:
46
+ """Build entity key -> 'mock' | 'remote' for template rendering."""
47
+ entities: dict[str, str] = {}
48
+ if has_login:
49
+ entities["auth"] = "remote" if "auth" in preserved_remote else "mock"
50
+ for key in entity_keys:
51
+ if key in preserved_remote:
52
+ entities[key] = "remote"
53
+ else:
54
+ entities[key] = "mock"
55
+ return entities
56
+
57
+
58
+ def _format_entities_entries(entities: dict[str, str]) -> str:
59
+ if not entities:
60
+ return ""
61
+ lines = []
62
+ for key, source in entities.items():
63
+ enum_val = "DataSource.mock" if source == "mock" else "DataSource.remote"
64
+ lines.append(f" '{key}': {enum_val},")
65
+ return "\n".join(lines)
66
+
67
+
68
+ def regenerate_data_source_config(
69
+ project_name: str,
70
+ lib_path: Path,
71
+ *,
72
+ domain_folder: str = "domain",
73
+ has_login: bool = False,
74
+ ) -> None:
75
+ """Regenerate lib/apis/common/data_source_config.dart from scanned entities."""
76
+ config_rel = "apis/common/data_source_config.dart"
77
+ config_path = lib_path / config_rel
78
+ entity_keys = scan_domain_entity_keys(lib_path, domain_folder)
79
+ preserved_remote = read_preserved_remote_keys(config_path)
80
+ entities = build_entities_map(
81
+ entity_keys,
82
+ has_login=has_login,
83
+ preserved_remote=preserved_remote,
84
+ )
85
+ generate_file(
86
+ project_name,
87
+ lib_path,
88
+ "apis/common/data_source_config_template.jinja",
89
+ config_rel,
90
+ {
91
+ "entities_entries": _format_entities_entries(entities),
92
+ "login": has_login,
93
+ },
94
+ )
95
+
96
+
97
+ def ensure_mock_assets_dir(project_path: Path) -> Path:
98
+ """Ensure assets/mock/ exists in the Flutter project root."""
99
+ mock_dir = project_path / "assets" / "mock"
100
+ mock_dir.mkdir(parents=True, exist_ok=True)
101
+ return mock_dir
102
+
103
+
104
+ def _sample_value_for_type(
105
+ field_name: str,
106
+ field_type: str,
107
+ index: int,
108
+ *,
109
+ known_enums: Optional[dict] = None,
110
+ ) -> object:
111
+ """Return a JSON-serializable sample value for a DTO field."""
112
+ known_enums = known_enums or {}
113
+ is_nullable = field_type.endswith("?")
114
+ base = field_type[:-1] if is_nullable else field_type
115
+
116
+ if field_name == "id":
117
+ return str(index)
118
+
119
+ base_lower = base.lower()
120
+ if base_lower in ("string", "str"):
121
+ return f"sample-{field_name}-{index}"
122
+ if base_lower == "bool":
123
+ return False
124
+ if base_lower == "int":
125
+ return index
126
+ if base_lower in ("double", "num", "float"):
127
+ return round(9.99 * index, 2)
128
+ if base_lower in ("datetime", "date"):
129
+ return "2024-01-15T10:00:00.000Z"
130
+
131
+ if base in known_enums:
132
+ values = known_enums[base].get("values") or []
133
+ if values:
134
+ return values[0]
135
+ return "pending"
136
+
137
+ if base.startswith("List<") or base.startswith("Set<"):
138
+ inner = base[base.index("<") + 1 : base.rindex(">")]
139
+ if inner in known_enums:
140
+ vals = known_enums[inner].get("values") or []
141
+ if vals:
142
+ return [vals[0]]
143
+ return []
144
+
145
+ if base.startswith("Map<"):
146
+ return {}
147
+
148
+ if base[0].isupper() if base else False:
149
+ return f"sample-{field_name}-{index}"
150
+
151
+ return f"sample-{field_name}-{index}"
152
+
153
+
154
+ def generate_mock_json(
155
+ project_path: Path,
156
+ entity_folder_name: str,
157
+ field_list: list[dict],
158
+ *,
159
+ known_enums: Optional[dict] = None,
160
+ item_count: int = 3,
161
+ ) -> Path:
162
+ """Write assets/mock/<entity>.json with sample items."""
163
+ mock_dir = ensure_mock_assets_dir(project_path)
164
+ items = []
165
+ for i in range(1, item_count + 1):
166
+ item: dict[str, object] = {}
167
+ for field in field_list:
168
+ item[field["name"]] = _sample_value_for_type(
169
+ field["name"],
170
+ field["type"],
171
+ i,
172
+ known_enums=known_enums,
173
+ )
174
+ items.append(item)
175
+ out_path = mock_dir / f"{entity_folder_name}.json"
176
+ out_path.write_text(
177
+ json.dumps({"items": items}, indent=2) + "\n",
178
+ encoding="utf-8",
179
+ )
180
+ return out_path