aegis-stack 0.1.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.
Potentially problematic release.
This version of aegis-stack might be problematic. Click here for more details.
- aegis/__init__.py +5 -0
- aegis/__main__.py +374 -0
- aegis/core/CLAUDE.md +365 -0
- aegis/core/__init__.py +6 -0
- aegis/core/components.py +115 -0
- aegis/core/dependency_resolver.py +119 -0
- aegis/core/template_generator.py +163 -0
- aegis/templates/CLAUDE.md +306 -0
- aegis/templates/cookiecutter-aegis-project/cookiecutter.json +27 -0
- aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +172 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +70 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +127 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +211 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py +321 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py +638 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +134 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +247 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/tasks.py.j2 +596 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +133 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +190 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/core/theme.py +46 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py +687 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py +138 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md +213 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +78 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +48 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +41 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +36 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +526 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +32 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +279 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +119 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +60 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py +67 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +85 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +61 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +661 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +269 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1105 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +169 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +195 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +414 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +156 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +104 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +239 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +76 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +81 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +376 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +633 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +665 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +602 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +96 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +224 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +50 -0
- aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
- aegis_stack-0.1.0.dist-info/METADATA +114 -0
- aegis_stack-0.1.0.dist-info/RECORD +103 -0
- aegis_stack-0.1.0.dist-info/WHEEL +4 -0
- aegis_stack-0.1.0.dist-info/entry_points.txt +2 -0
aegis/__init__.py
ADDED
aegis/__main__.py
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Aegis Stack CLI - Main entry point
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
aegis init PROJECT_NAME
|
|
7
|
+
aegis components
|
|
8
|
+
aegis --help
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
from aegis import __version__
|
|
16
|
+
from aegis.core.components import (
|
|
17
|
+
COMPONENTS,
|
|
18
|
+
ComponentSpec,
|
|
19
|
+
ComponentType,
|
|
20
|
+
get_components_by_type,
|
|
21
|
+
)
|
|
22
|
+
from aegis.core.dependency_resolver import DependencyResolver
|
|
23
|
+
from aegis.core.template_generator import TemplateGenerator
|
|
24
|
+
|
|
25
|
+
# Create the main Typer application
|
|
26
|
+
app = typer.Typer(
|
|
27
|
+
name="aegis",
|
|
28
|
+
help=(
|
|
29
|
+
"Aegis Stack CLI - Component generation and project management. "
|
|
30
|
+
"Available components: redis, worker, scheduler, database"
|
|
31
|
+
),
|
|
32
|
+
add_completion=False,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@app.command()
|
|
37
|
+
def version() -> None:
|
|
38
|
+
"""Show the Aegis Stack CLI version."""
|
|
39
|
+
typer.echo(f"Aegis Stack CLI v{__version__}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command()
|
|
43
|
+
def components() -> None:
|
|
44
|
+
"""List available components and their dependencies."""
|
|
45
|
+
|
|
46
|
+
typer.echo("\nš¦ CORE COMPONENTS")
|
|
47
|
+
typer.echo("=" * 40)
|
|
48
|
+
typer.echo(" backend - FastAPI backend server (always included)")
|
|
49
|
+
typer.echo(" frontend - Flet frontend interface (always included)")
|
|
50
|
+
|
|
51
|
+
typer.echo("\nšļø INFRASTRUCTURE COMPONENTS")
|
|
52
|
+
typer.echo("=" * 40)
|
|
53
|
+
|
|
54
|
+
infra_components = get_components_by_type(ComponentType.INFRASTRUCTURE)
|
|
55
|
+
for name, spec in infra_components.items():
|
|
56
|
+
typer.echo(f" {name:12} - {spec.description}")
|
|
57
|
+
if spec.requires:
|
|
58
|
+
typer.echo(f" Requires: {', '.join(spec.requires)}")
|
|
59
|
+
if spec.recommends:
|
|
60
|
+
typer.echo(f" Recommends: {', '.join(spec.recommends)}")
|
|
61
|
+
|
|
62
|
+
typer.echo(
|
|
63
|
+
"\nš” Use 'aegis init PROJECT_NAME --components redis,worker' "
|
|
64
|
+
"to select components"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def validate_and_resolve_components(
|
|
69
|
+
ctx: typer.Context, param: typer.CallbackParam, value: str | None
|
|
70
|
+
) -> list[str] | None:
|
|
71
|
+
"""Validate and resolve component dependencies."""
|
|
72
|
+
if not value:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
# Parse comma-separated string
|
|
76
|
+
components_raw = [c.strip() for c in value.split(",")]
|
|
77
|
+
|
|
78
|
+
# Check for empty components before filtering
|
|
79
|
+
if any(not c for c in components_raw):
|
|
80
|
+
typer.echo("ā Empty component name is not allowed", err=True)
|
|
81
|
+
raise typer.Exit(1)
|
|
82
|
+
|
|
83
|
+
selected = [c for c in components_raw if c]
|
|
84
|
+
|
|
85
|
+
# Validate components exist
|
|
86
|
+
errors = DependencyResolver.validate_components(selected)
|
|
87
|
+
if errors:
|
|
88
|
+
for error in errors:
|
|
89
|
+
typer.echo(f"ā {error}", err=True)
|
|
90
|
+
raise typer.Exit(1)
|
|
91
|
+
|
|
92
|
+
# Resolve dependencies
|
|
93
|
+
resolved = DependencyResolver.resolve_dependencies(selected)
|
|
94
|
+
|
|
95
|
+
# Show dependency resolution
|
|
96
|
+
auto_added = DependencyResolver.get_missing_dependencies(selected)
|
|
97
|
+
if auto_added:
|
|
98
|
+
typer.echo(f"š¦ Auto-added dependencies: {', '.join(auto_added)}")
|
|
99
|
+
|
|
100
|
+
# Show recommendations
|
|
101
|
+
recommendations = DependencyResolver.get_recommendations(resolved)
|
|
102
|
+
if recommendations:
|
|
103
|
+
rec_list = ", ".join(recommendations)
|
|
104
|
+
typer.echo(f"š” Recommended: {rec_list}")
|
|
105
|
+
# Note: Skip interactive recommendations for now to keep it simple
|
|
106
|
+
|
|
107
|
+
return resolved
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def validate_project_name(project_name: str) -> None:
|
|
111
|
+
"""Validate project name and raise typer.Exit if invalid."""
|
|
112
|
+
import re
|
|
113
|
+
|
|
114
|
+
# Check for invalid characters (only allow letters, numbers, hyphens,
|
|
115
|
+
# underscores)
|
|
116
|
+
if not re.match(r"^[a-zA-Z0-9_-]+$", project_name):
|
|
117
|
+
typer.echo(
|
|
118
|
+
"ā Invalid project name. Only letters, numbers, hyphens, and "
|
|
119
|
+
"underscores are allowed.",
|
|
120
|
+
err=True,
|
|
121
|
+
)
|
|
122
|
+
raise typer.Exit(1)
|
|
123
|
+
|
|
124
|
+
# Check for reserved names
|
|
125
|
+
reserved_names = {"aegis", "aegis-stack"}
|
|
126
|
+
if project_name.lower() in reserved_names:
|
|
127
|
+
typer.echo(f"ā '{project_name}' is a reserved name.", err=True)
|
|
128
|
+
raise typer.Exit(1)
|
|
129
|
+
|
|
130
|
+
# Check length limit
|
|
131
|
+
if len(project_name) > 50:
|
|
132
|
+
typer.echo("ā Project name too long. Maximum 50 characters allowed.", err=True)
|
|
133
|
+
raise typer.Exit(1)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@app.command()
|
|
137
|
+
def init(
|
|
138
|
+
project_name: str = typer.Argument(
|
|
139
|
+
..., help="Name of the new Aegis Stack project to create"
|
|
140
|
+
),
|
|
141
|
+
components: str | None = typer.Option(
|
|
142
|
+
None,
|
|
143
|
+
"--components",
|
|
144
|
+
"-c",
|
|
145
|
+
callback=validate_and_resolve_components,
|
|
146
|
+
help="Comma-separated list of components (redis,worker,scheduler,database)",
|
|
147
|
+
),
|
|
148
|
+
interactive: bool = typer.Option(
|
|
149
|
+
True,
|
|
150
|
+
"--interactive/--no-interactive",
|
|
151
|
+
"-i/-ni",
|
|
152
|
+
help="Use interactive component selection",
|
|
153
|
+
),
|
|
154
|
+
force: bool = typer.Option(
|
|
155
|
+
False, "--force", "-f", help="Overwrite existing directory if it exists"
|
|
156
|
+
),
|
|
157
|
+
output_dir: str | None = typer.Option(
|
|
158
|
+
None,
|
|
159
|
+
"--output-dir",
|
|
160
|
+
"-o",
|
|
161
|
+
help="Directory to create the project in (default: current directory)",
|
|
162
|
+
),
|
|
163
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation prompt"),
|
|
164
|
+
) -> None:
|
|
165
|
+
"""
|
|
166
|
+
Initialize a new Aegis Stack project with battle-tested component combinations.
|
|
167
|
+
|
|
168
|
+
This command creates a complete project structure with your chosen components,
|
|
169
|
+
ensuring all dependencies and configurations are compatible and tested.
|
|
170
|
+
|
|
171
|
+
Examples:\n
|
|
172
|
+
- aegis init my-app\n
|
|
173
|
+
- aegis init my-app --components redis,worker\n
|
|
174
|
+
- aegis init my-app --components redis,worker,scheduler,database --no-interactive\n
|
|
175
|
+
""" # noqa
|
|
176
|
+
|
|
177
|
+
# Validate project name first
|
|
178
|
+
validate_project_name(project_name)
|
|
179
|
+
|
|
180
|
+
typer.echo("š”ļø Aegis Stack Project Initialization")
|
|
181
|
+
typer.echo("=" * 50)
|
|
182
|
+
|
|
183
|
+
# Determine output directory
|
|
184
|
+
base_output_dir = Path(output_dir) if output_dir else Path.cwd()
|
|
185
|
+
project_path = base_output_dir / project_name
|
|
186
|
+
|
|
187
|
+
typer.echo(f"š Project will be created in: {project_path.resolve()}")
|
|
188
|
+
|
|
189
|
+
# Check if directory already exists
|
|
190
|
+
if project_path.exists():
|
|
191
|
+
if not force:
|
|
192
|
+
typer.echo(f"ā Directory '{project_path}' already exists", err=True)
|
|
193
|
+
typer.echo(
|
|
194
|
+
" Use --force to overwrite or choose a different name", err=True
|
|
195
|
+
)
|
|
196
|
+
raise typer.Exit(1)
|
|
197
|
+
else:
|
|
198
|
+
typer.echo(f"ā ļø Overwriting existing directory: {project_path}")
|
|
199
|
+
|
|
200
|
+
# Interactive component selection
|
|
201
|
+
selected_components = components if components else []
|
|
202
|
+
|
|
203
|
+
if interactive and not components:
|
|
204
|
+
selected_components = interactive_component_selection()
|
|
205
|
+
|
|
206
|
+
# Resolve dependencies for interactively selected components
|
|
207
|
+
if selected_components:
|
|
208
|
+
selected_components = DependencyResolver.resolve_dependencies(
|
|
209
|
+
selected_components
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
auto_added = DependencyResolver.get_missing_dependencies(
|
|
213
|
+
[c for c in selected_components if c not in ["backend", "frontend"]]
|
|
214
|
+
)
|
|
215
|
+
if auto_added:
|
|
216
|
+
typer.echo(f"\nš¦ Auto-added dependencies: {', '.join(auto_added)}")
|
|
217
|
+
|
|
218
|
+
# Create template generator
|
|
219
|
+
template_gen = TemplateGenerator(project_name, list(selected_components))
|
|
220
|
+
|
|
221
|
+
# Show selected configuration
|
|
222
|
+
typer.echo()
|
|
223
|
+
typer.echo(f"š Project Name: {project_name}")
|
|
224
|
+
typer.echo("šļø Project Structure:")
|
|
225
|
+
typer.echo(" ā
Core: backend, frontend")
|
|
226
|
+
|
|
227
|
+
# Show infrastructure components
|
|
228
|
+
infra_components = [
|
|
229
|
+
name
|
|
230
|
+
for name in selected_components
|
|
231
|
+
if name in COMPONENTS and COMPONENTS[name].type == ComponentType.INFRASTRUCTURE
|
|
232
|
+
]
|
|
233
|
+
if infra_components:
|
|
234
|
+
typer.echo(f" š¦ Infrastructure: {', '.join(infra_components)}")
|
|
235
|
+
|
|
236
|
+
# Show template files that will be generated
|
|
237
|
+
template_files = template_gen.get_template_files()
|
|
238
|
+
if template_files:
|
|
239
|
+
typer.echo("\nš Component Files:")
|
|
240
|
+
for file_path in template_files:
|
|
241
|
+
typer.echo(f" ⢠{file_path}")
|
|
242
|
+
|
|
243
|
+
# Show entrypoints that will be created
|
|
244
|
+
entrypoints = template_gen.get_entrypoints()
|
|
245
|
+
if entrypoints:
|
|
246
|
+
typer.echo("\nš Entrypoints:")
|
|
247
|
+
for entrypoint in entrypoints:
|
|
248
|
+
typer.echo(f" ⢠{entrypoint}")
|
|
249
|
+
|
|
250
|
+
# Show worker queues that will be created
|
|
251
|
+
worker_queues = template_gen.get_worker_queues()
|
|
252
|
+
if worker_queues:
|
|
253
|
+
typer.echo("\nš· Worker Queues:")
|
|
254
|
+
for queue in worker_queues:
|
|
255
|
+
typer.echo(f" ⢠{queue}")
|
|
256
|
+
|
|
257
|
+
# Show dependency information using template generator
|
|
258
|
+
deps = template_gen._get_pyproject_deps()
|
|
259
|
+
if deps:
|
|
260
|
+
typer.echo("\nš¦ Dependencies to be installed:")
|
|
261
|
+
for dep in deps:
|
|
262
|
+
typer.echo(f" ⢠{dep}")
|
|
263
|
+
|
|
264
|
+
# Confirm before proceeding
|
|
265
|
+
typer.echo()
|
|
266
|
+
if not yes and not typer.confirm("š Create this project?"):
|
|
267
|
+
typer.echo("ā Project creation cancelled")
|
|
268
|
+
raise typer.Exit(0)
|
|
269
|
+
|
|
270
|
+
# Create project using cookiecutter
|
|
271
|
+
typer.echo()
|
|
272
|
+
typer.echo(f"š§ Creating project: {project_name}")
|
|
273
|
+
|
|
274
|
+
try:
|
|
275
|
+
from cookiecutter.main import cookiecutter
|
|
276
|
+
|
|
277
|
+
# Get the template path
|
|
278
|
+
template_path = (
|
|
279
|
+
Path(__file__).parent / "templates" / "cookiecutter-aegis-project"
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
# Use template generator for context
|
|
283
|
+
extra_context = template_gen.get_template_context()
|
|
284
|
+
|
|
285
|
+
# Generate project with cookiecutter
|
|
286
|
+
cookiecutter(
|
|
287
|
+
str(template_path),
|
|
288
|
+
extra_context=extra_context,
|
|
289
|
+
output_dir=str(base_output_dir),
|
|
290
|
+
no_input=True, # Don't prompt user, use our context
|
|
291
|
+
overwrite_if_exists=force,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
typer.echo("ā
Project created successfully!")
|
|
295
|
+
|
|
296
|
+
# Show next steps
|
|
297
|
+
typer.echo()
|
|
298
|
+
typer.echo("š Next steps:")
|
|
299
|
+
typer.echo(f" cd {project_path.resolve()}")
|
|
300
|
+
typer.echo(" uv sync")
|
|
301
|
+
typer.echo(" cp .env.example .env")
|
|
302
|
+
typer.echo(" make run-local")
|
|
303
|
+
|
|
304
|
+
except ImportError:
|
|
305
|
+
typer.echo("ā Error: cookiecutter not installed", err=True)
|
|
306
|
+
raise typer.Exit(1)
|
|
307
|
+
except Exception as e:
|
|
308
|
+
typer.echo(f"ā Error creating project: {e}", err=True)
|
|
309
|
+
raise typer.Exit(1)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def get_interactive_infrastructure_components() -> list[ComponentSpec]:
|
|
313
|
+
"""Get infrastructure components available for interactive selection."""
|
|
314
|
+
# Get all infrastructure components
|
|
315
|
+
infra_components = []
|
|
316
|
+
for component_spec in COMPONENTS.values():
|
|
317
|
+
if component_spec.type == ComponentType.INFRASTRUCTURE:
|
|
318
|
+
infra_components.append(component_spec)
|
|
319
|
+
|
|
320
|
+
# Sort by name for consistent ordering
|
|
321
|
+
return sorted(infra_components, key=lambda x: x.name)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def interactive_component_selection() -> list[str]:
|
|
325
|
+
"""Interactive component selection with dependency awareness."""
|
|
326
|
+
|
|
327
|
+
typer.echo("šÆ Component Selection")
|
|
328
|
+
typer.echo("=" * 40)
|
|
329
|
+
typer.echo("ā
Core components (backend + frontend) included automatically\n")
|
|
330
|
+
|
|
331
|
+
selected = []
|
|
332
|
+
|
|
333
|
+
# Get all infrastructure components from registry
|
|
334
|
+
infra_components = get_interactive_infrastructure_components()
|
|
335
|
+
|
|
336
|
+
typer.echo("šļø Infrastructure Components:")
|
|
337
|
+
|
|
338
|
+
# Process components in a specific order to handle dependencies
|
|
339
|
+
component_order = ["redis", "worker", "scheduler", "database"]
|
|
340
|
+
|
|
341
|
+
for component_name in component_order:
|
|
342
|
+
# Find the component spec
|
|
343
|
+
component_spec = next(
|
|
344
|
+
(c for c in infra_components if c.name == component_name), None
|
|
345
|
+
)
|
|
346
|
+
if not component_spec:
|
|
347
|
+
continue # Skip if component doesn't exist in registry
|
|
348
|
+
|
|
349
|
+
# Handle special worker dependency logic
|
|
350
|
+
if component_name == "worker":
|
|
351
|
+
if "redis" in selected:
|
|
352
|
+
# Redis already selected, simple worker prompt
|
|
353
|
+
prompt = f" Add {component_spec.description.lower()}?"
|
|
354
|
+
if typer.confirm(prompt):
|
|
355
|
+
selected.append("worker")
|
|
356
|
+
else:
|
|
357
|
+
# Redis not selected, offer to add both
|
|
358
|
+
prompt = (
|
|
359
|
+
f" Add {component_spec.description.lower()}? (will auto-add Redis)"
|
|
360
|
+
)
|
|
361
|
+
if typer.confirm(prompt):
|
|
362
|
+
selected.extend(["redis", "worker"])
|
|
363
|
+
else:
|
|
364
|
+
# Standard prompt for other components
|
|
365
|
+
prompt = f" Add {component_spec.description}?"
|
|
366
|
+
if typer.confirm(prompt):
|
|
367
|
+
selected.append(component_name)
|
|
368
|
+
|
|
369
|
+
return selected
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
# This is what runs when you do: aegis
|
|
373
|
+
if __name__ == "__main__":
|
|
374
|
+
app()
|