pyreactor-forge 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.
- pyreactor_forge/__init__.py +3 -0
- pyreactor_forge/cli.py +234 -0
- pyreactor_forge/generators/__init__.py +0 -0
- pyreactor_forge/generators/app.py +281 -0
- pyreactor_forge/generators/backend/__init__.py +0 -0
- pyreactor_forge/generators/backend/django.py +206 -0
- pyreactor_forge/generators/backend/fastapi.py +700 -0
- pyreactor_forge/generators/backend/flask.py +190 -0
- pyreactor_forge/generators/devops.py +376 -0
- pyreactor_forge/generators/entity.py +495 -0
- pyreactor_forge/generators/frontend.py +640 -0
- pyreactor_forge/utils/__init__.py +0 -0
- pyreactor_forge/utils/display.py +58 -0
- pyreactor_forge-0.1.0.dist-info/METADATA +266 -0
- pyreactor_forge-0.1.0.dist-info/RECORD +19 -0
- pyreactor_forge-0.1.0.dist-info/WHEEL +5 -0
- pyreactor_forge-0.1.0.dist-info/entry_points.txt +2 -0
- pyreactor_forge-0.1.0.dist-info/licenses/LICENSE +21 -0
- pyreactor_forge-0.1.0.dist-info/top_level.txt +1 -0
pyreactor_forge/cli.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""PyReactor Forge CLI - JHipster-inspired full-stack generator for Python + React."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.text import Text
|
|
9
|
+
|
|
10
|
+
from pyreactor_forge import __version__
|
|
11
|
+
from pyreactor_forge.generators.app import AppGenerator
|
|
12
|
+
from pyreactor_forge.generators.entity import EntityGenerator
|
|
13
|
+
from pyreactor_forge.utils.display import print_banner, print_success, print_error
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _force_utf8_stdio():
|
|
17
|
+
"""Windows defaults stdio to the legacy ANSI code page (cp1252 on most
|
|
18
|
+
installs), which cannot encode the Unicode we print. It only bites when
|
|
19
|
+
output is redirected -- a bare terminal goes through WriteConsoleW and
|
|
20
|
+
works fine -- so piping to a file or running under CI would otherwise
|
|
21
|
+
crash with UnicodeEncodeError.
|
|
22
|
+
"""
|
|
23
|
+
for stream in (sys.stdout, sys.stderr):
|
|
24
|
+
reconfigure = getattr(stream, "reconfigure", None)
|
|
25
|
+
if reconfigure is not None:
|
|
26
|
+
reconfigure(encoding="utf-8", errors="replace")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_force_utf8_stdio()
|
|
30
|
+
|
|
31
|
+
console = Console()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@click.group()
|
|
35
|
+
@click.version_option(version=__version__, prog_name="PyReactor Forge")
|
|
36
|
+
def cli():
|
|
37
|
+
"""
|
|
38
|
+
\b
|
|
39
|
+
██████╗ ██╗ ██╗██████╗ ███████╗ █████╗ ██████╗████████╗ ██████╗ ██████╗
|
|
40
|
+
██╔══██╗╚██╗ ██╔╝██╔══██╗██╔════╝██╔══██╗██╔════╝╚══██╔══╝██╔═══██╗██╔══██╗
|
|
41
|
+
██████╔╝ ╚████╔╝ ██████╔╝█████╗ ███████║██║ ██║ ██║ ██║██████╔╝
|
|
42
|
+
██╔═══╝ ╚██╔╝ ██╔══██╗██╔══╝ ██╔══██║██║ ██║ ██║ ██║██╔══██╗
|
|
43
|
+
██║ ██║ ██║ ██║███████╗██║ ██║╚██████╗ ██║ ╚██████╔╝██║ ██║
|
|
44
|
+
╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝
|
|
45
|
+
|
|
46
|
+
Full-stack Python + React application generator.
|
|
47
|
+
Inspired by JHipster. Built for the Python ecosystem.
|
|
48
|
+
"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@cli.command()
|
|
53
|
+
@click.option("--name", "-n", prompt="Application name", help="Name of the application")
|
|
54
|
+
@click.option(
|
|
55
|
+
"--backend",
|
|
56
|
+
"-b",
|
|
57
|
+
type=click.Choice(["fastapi", "django", "flask"], case_sensitive=False),
|
|
58
|
+
default="fastapi",
|
|
59
|
+
prompt="Backend framework",
|
|
60
|
+
show_default=True,
|
|
61
|
+
help="Python backend framework",
|
|
62
|
+
)
|
|
63
|
+
@click.option(
|
|
64
|
+
"--frontend",
|
|
65
|
+
"-f",
|
|
66
|
+
type=click.Choice(["react", "react-ts"], case_sensitive=False),
|
|
67
|
+
default="react-ts",
|
|
68
|
+
prompt="Frontend framework",
|
|
69
|
+
show_default=True,
|
|
70
|
+
help="Frontend framework",
|
|
71
|
+
)
|
|
72
|
+
@click.option(
|
|
73
|
+
"--database",
|
|
74
|
+
"-d",
|
|
75
|
+
type=click.Choice(["postgresql", "mysql", "sqlite"], case_sensitive=False),
|
|
76
|
+
default="postgresql",
|
|
77
|
+
prompt="Database",
|
|
78
|
+
show_default=True,
|
|
79
|
+
help="Database engine",
|
|
80
|
+
)
|
|
81
|
+
@click.option(
|
|
82
|
+
"--auth",
|
|
83
|
+
"-a",
|
|
84
|
+
type=click.Choice(["jwt", "session", "oauth2"], case_sensitive=False),
|
|
85
|
+
default="jwt",
|
|
86
|
+
prompt="Authentication type",
|
|
87
|
+
show_default=True,
|
|
88
|
+
help="Authentication strategy",
|
|
89
|
+
)
|
|
90
|
+
@click.option(
|
|
91
|
+
"--output-dir",
|
|
92
|
+
"-o",
|
|
93
|
+
default=".",
|
|
94
|
+
help="Output directory (default: current directory)",
|
|
95
|
+
)
|
|
96
|
+
@click.option(
|
|
97
|
+
"--docker/--no-docker",
|
|
98
|
+
default=True,
|
|
99
|
+
prompt="Include Docker configuration?",
|
|
100
|
+
help="Generate Docker and docker-compose files",
|
|
101
|
+
)
|
|
102
|
+
@click.option(
|
|
103
|
+
"--ci",
|
|
104
|
+
type=click.Choice(["github-actions", "gitlab-ci", "none"], case_sensitive=False),
|
|
105
|
+
default="github-actions",
|
|
106
|
+
prompt="CI/CD pipeline",
|
|
107
|
+
show_default=True,
|
|
108
|
+
help="CI/CD pipeline configuration",
|
|
109
|
+
)
|
|
110
|
+
def new(name, backend, frontend, database, auth, output_dir, docker, ci):
|
|
111
|
+
"""Generate a new full-stack Python + React application."""
|
|
112
|
+
print_banner()
|
|
113
|
+
|
|
114
|
+
config = {
|
|
115
|
+
"name": name,
|
|
116
|
+
"backend": backend,
|
|
117
|
+
"frontend": frontend,
|
|
118
|
+
"database": database,
|
|
119
|
+
"auth": auth,
|
|
120
|
+
"docker": docker,
|
|
121
|
+
"ci": ci,
|
|
122
|
+
"output_dir": output_dir,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.print(
|
|
126
|
+
Panel.fit(
|
|
127
|
+
f"[bold cyan]Generating application:[/bold cyan] [yellow]{name}[/yellow]\n"
|
|
128
|
+
f" Backend: [green]{backend}[/green]\n"
|
|
129
|
+
f" Frontend: [green]{frontend}[/green]\n"
|
|
130
|
+
f" Database: [green]{database}[/green]\n"
|
|
131
|
+
f" Auth: [green]{auth}[/green]\n"
|
|
132
|
+
f" Docker: [green]{'yes' if docker else 'no'}[/green]\n"
|
|
133
|
+
f" CI/CD: [green]{ci}[/green]",
|
|
134
|
+
title="⚡ PyReactor Forge",
|
|
135
|
+
border_style="cyan",
|
|
136
|
+
)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
generator = AppGenerator(config)
|
|
140
|
+
try:
|
|
141
|
+
generator.generate()
|
|
142
|
+
print_success(name, config)
|
|
143
|
+
except Exception as e:
|
|
144
|
+
print_error(str(e))
|
|
145
|
+
sys.exit(1)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@cli.command()
|
|
149
|
+
@click.option("--name", "-n", prompt="Entity name (singular, PascalCase)", help="Entity name, e.g. Product")
|
|
150
|
+
@click.option(
|
|
151
|
+
"--app-dir",
|
|
152
|
+
"-a",
|
|
153
|
+
default=".",
|
|
154
|
+
help="Path to the generated app directory",
|
|
155
|
+
)
|
|
156
|
+
def entity(name, app_dir):
|
|
157
|
+
"""Add a new entity (model + API + UI) to an existing app."""
|
|
158
|
+
app_path = Path(app_dir)
|
|
159
|
+
config_file = app_path / ".pyforge.json"
|
|
160
|
+
|
|
161
|
+
if not config_file.exists():
|
|
162
|
+
console.print(
|
|
163
|
+
"[red]Error:[/red] No .pyforge.json found. "
|
|
164
|
+
"Run this command inside a PyReactor Forge-generated project."
|
|
165
|
+
)
|
|
166
|
+
sys.exit(1)
|
|
167
|
+
|
|
168
|
+
import json
|
|
169
|
+
with open(config_file, encoding="utf-8") as f:
|
|
170
|
+
app_config = json.load(f)
|
|
171
|
+
|
|
172
|
+
console.print(f"\n[bold cyan]⚡ Adding entity:[/bold cyan] [yellow]{name}[/yellow]\n")
|
|
173
|
+
|
|
174
|
+
# Interactively collect fields
|
|
175
|
+
fields = []
|
|
176
|
+
console.print("[dim]Define fields (press Enter with no name to finish):[/dim]\n")
|
|
177
|
+
|
|
178
|
+
while True:
|
|
179
|
+
field_name = click.prompt(" Field name", default="", show_default=False)
|
|
180
|
+
if not field_name:
|
|
181
|
+
break
|
|
182
|
+
|
|
183
|
+
field_type = click.prompt(
|
|
184
|
+
" Field type",
|
|
185
|
+
type=click.Choice(["string", "integer", "float", "boolean", "date", "datetime", "text"]),
|
|
186
|
+
default="string",
|
|
187
|
+
)
|
|
188
|
+
required = click.confirm(" Required?", default=True)
|
|
189
|
+
fields.append({"name": field_name, "type": field_type, "required": required})
|
|
190
|
+
console.print()
|
|
191
|
+
|
|
192
|
+
if not fields:
|
|
193
|
+
console.print("[yellow]No fields defined. Aborting.[/yellow]")
|
|
194
|
+
sys.exit(0)
|
|
195
|
+
|
|
196
|
+
gen = EntityGenerator(name, fields, app_config, app_path)
|
|
197
|
+
gen.generate()
|
|
198
|
+
|
|
199
|
+
console.print(f"\n[bold green]✓ Entity [yellow]{name}[/yellow] added successfully![/bold green]")
|
|
200
|
+
console.print("\nFiles created/updated:")
|
|
201
|
+
for f in gen.created_files:
|
|
202
|
+
console.print(f" [cyan]→[/cyan] {f}")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@cli.command()
|
|
206
|
+
def info():
|
|
207
|
+
"""Display information about the PyReactor Forge generator."""
|
|
208
|
+
print_banner()
|
|
209
|
+
console.print(
|
|
210
|
+
Panel(
|
|
211
|
+
"[bold]PyReactor Forge[/bold] is a full-stack code generator inspired by JHipster.\n\n"
|
|
212
|
+
"[bold cyan]Supported backends:[/bold cyan]\n"
|
|
213
|
+
" • [green]FastAPI[/green] — Modern, fast async Python API framework\n"
|
|
214
|
+
" • [green]Django[/green] — Batteries-included Python web framework\n"
|
|
215
|
+
" • [green]Flask[/green] — Lightweight Python micro-framework\n\n"
|
|
216
|
+
"[bold cyan]Supported frontends:[/bold cyan]\n"
|
|
217
|
+
" • [green]React[/green] — JavaScript (Vite + React)\n"
|
|
218
|
+
" • [green]React-TS[/green] — TypeScript (Vite + React + TypeScript)\n\n"
|
|
219
|
+
"[bold cyan]Supported databases:[/bold cyan]\n"
|
|
220
|
+
" • [green]PostgreSQL[/green] • [green]MySQL[/green] • [green]SQLite[/green]\n\n"
|
|
221
|
+
"[bold cyan]Supported authentication:[/bold cyan]\n"
|
|
222
|
+
" • [green]JWT[/green] • [green]Session[/green] • [green]OAuth2[/green]\n\n"
|
|
223
|
+
"[bold cyan]Commands:[/bold cyan]\n"
|
|
224
|
+
" • [yellow]pyforge new[/yellow] — Scaffold a new application\n"
|
|
225
|
+
" • [yellow]pyforge entity[/yellow] — Add a new entity to an existing app\n"
|
|
226
|
+
" • [yellow]pyforge info[/yellow] — Show this information",
|
|
227
|
+
title="ℹ️ About PyReactor Forge",
|
|
228
|
+
border_style="blue",
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
if __name__ == "__main__":
|
|
234
|
+
cli()
|
|
File without changes
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""App Generator - scaffolds the full project structure."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
9
|
+
|
|
10
|
+
from pyreactor_forge.generators.backend.fastapi import FastAPIGenerator
|
|
11
|
+
from pyreactor_forge.generators.backend.django import DjangoGenerator
|
|
12
|
+
from pyreactor_forge.generators.backend.flask import FlaskGenerator
|
|
13
|
+
from pyreactor_forge.generators.frontend import FrontendGenerator
|
|
14
|
+
from pyreactor_forge.generators.devops import DevOpsGenerator
|
|
15
|
+
|
|
16
|
+
console = Console()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AppGenerator:
|
|
20
|
+
def __init__(self, config: dict):
|
|
21
|
+
self.config = config
|
|
22
|
+
self.name = config["name"]
|
|
23
|
+
self.slug = self.name.lower().replace(" ", "-").replace("_", "-")
|
|
24
|
+
self.output_dir = Path(config.get("output_dir", ".")) / self.slug
|
|
25
|
+
|
|
26
|
+
def generate(self):
|
|
27
|
+
if self.output_dir.exists():
|
|
28
|
+
raise FileExistsError(
|
|
29
|
+
f"Directory '{self.output_dir}' already exists. "
|
|
30
|
+
"Please remove it or choose a different name."
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
self.output_dir.mkdir(parents=True)
|
|
34
|
+
|
|
35
|
+
with Progress(
|
|
36
|
+
SpinnerColumn(),
|
|
37
|
+
TextColumn("[progress.description]{task.description}"),
|
|
38
|
+
console=console,
|
|
39
|
+
) as progress:
|
|
40
|
+
# Backend
|
|
41
|
+
task = progress.add_task("[cyan]Generating backend...", total=None)
|
|
42
|
+
self._generate_backend()
|
|
43
|
+
progress.update(task, description="[green]✓ Backend generated")
|
|
44
|
+
|
|
45
|
+
# Frontend
|
|
46
|
+
task2 = progress.add_task("[cyan]Generating frontend...", total=None)
|
|
47
|
+
self._generate_frontend()
|
|
48
|
+
progress.update(task2, description="[green]✓ Frontend generated")
|
|
49
|
+
|
|
50
|
+
# DevOps
|
|
51
|
+
if self.config.get("docker") or self.config.get("ci") != "none":
|
|
52
|
+
task3 = progress.add_task("[cyan]Generating DevOps config...", total=None)
|
|
53
|
+
self._generate_devops()
|
|
54
|
+
progress.update(task3, description="[green]✓ DevOps config generated")
|
|
55
|
+
|
|
56
|
+
# Root files
|
|
57
|
+
task4 = progress.add_task("[cyan]Writing project files...", total=None)
|
|
58
|
+
self._write_root_files()
|
|
59
|
+
progress.update(task4, description="[green]✓ Project files written")
|
|
60
|
+
|
|
61
|
+
def _generate_backend(self):
|
|
62
|
+
backend = self.config["backend"]
|
|
63
|
+
backend_dir = self.output_dir / "backend"
|
|
64
|
+
backend_dir.mkdir()
|
|
65
|
+
|
|
66
|
+
if backend == "fastapi":
|
|
67
|
+
gen = FastAPIGenerator(self.config, backend_dir)
|
|
68
|
+
elif backend == "django":
|
|
69
|
+
gen = DjangoGenerator(self.config, backend_dir)
|
|
70
|
+
else:
|
|
71
|
+
gen = FlaskGenerator(self.config, backend_dir)
|
|
72
|
+
|
|
73
|
+
gen.generate()
|
|
74
|
+
|
|
75
|
+
def _generate_frontend(self):
|
|
76
|
+
frontend_dir = self.output_dir / "frontend"
|
|
77
|
+
frontend_dir.mkdir()
|
|
78
|
+
gen = FrontendGenerator(self.config, frontend_dir)
|
|
79
|
+
gen.generate()
|
|
80
|
+
|
|
81
|
+
def _generate_devops(self):
|
|
82
|
+
gen = DevOpsGenerator(self.config, self.output_dir)
|
|
83
|
+
gen.generate()
|
|
84
|
+
|
|
85
|
+
def _write_root_files(self):
|
|
86
|
+
# .pyforge.json - project metadata
|
|
87
|
+
meta = {
|
|
88
|
+
"name": self.name,
|
|
89
|
+
"slug": self.slug,
|
|
90
|
+
"backend": self.config["backend"],
|
|
91
|
+
"frontend": self.config["frontend"],
|
|
92
|
+
"database": self.config["database"],
|
|
93
|
+
"auth": self.config["auth"],
|
|
94
|
+
"version": "0.1.0",
|
|
95
|
+
}
|
|
96
|
+
with open(self.output_dir / ".pyforge.json", "w", encoding="utf-8") as f:
|
|
97
|
+
json.dump(meta, f, indent=2)
|
|
98
|
+
|
|
99
|
+
# Root README
|
|
100
|
+
readme = self._render_readme()
|
|
101
|
+
(self.output_dir / "README.md").write_text(readme, encoding="utf-8")
|
|
102
|
+
|
|
103
|
+
# Root .gitignore
|
|
104
|
+
(self.output_dir / ".gitignore").write_text(ROOT_GITIGNORE, encoding="utf-8")
|
|
105
|
+
|
|
106
|
+
# Makefile
|
|
107
|
+
(self.output_dir / "Makefile").write_text(self._render_makefile(), encoding="utf-8")
|
|
108
|
+
|
|
109
|
+
def _render_readme(self):
|
|
110
|
+
name = self.name
|
|
111
|
+
backend = self.config["backend"]
|
|
112
|
+
frontend = self.config["frontend"]
|
|
113
|
+
db = self.config["database"]
|
|
114
|
+
auth = self.config["auth"]
|
|
115
|
+
|
|
116
|
+
return f"""# {name}
|
|
117
|
+
|
|
118
|
+
> Generated by [PyReactor Forge](https://github.com/panosadamop/pyreactor-forge) — Full-stack Python + React generator.
|
|
119
|
+
|
|
120
|
+
## Stack
|
|
121
|
+
|
|
122
|
+
| Layer | Technology |
|
|
123
|
+
|------------|-------------------------------------|
|
|
124
|
+
| Backend | {backend.capitalize()} |
|
|
125
|
+
| Frontend | {frontend.upper()} |
|
|
126
|
+
| Database | {db.capitalize()} |
|
|
127
|
+
| Auth | {auth.upper()} |
|
|
128
|
+
|
|
129
|
+
## Quick Start
|
|
130
|
+
|
|
131
|
+
### Prerequisites
|
|
132
|
+
|
|
133
|
+
- Python 3.11+
|
|
134
|
+
- Node.js 18+
|
|
135
|
+
- {db.capitalize()} running locally (or use Docker)
|
|
136
|
+
|
|
137
|
+
### Development
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Install all dependencies and start both servers
|
|
141
|
+
make dev
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Or manually:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Backend
|
|
148
|
+
cd backend
|
|
149
|
+
python -m venv .venv && source .venv/bin/activate
|
|
150
|
+
pip install -r requirements.txt
|
|
151
|
+
{"python manage.py migrate && python manage.py runserver" if backend == "django" else "uvicorn app.main:app --reload"}
|
|
152
|
+
|
|
153
|
+
# Frontend (in another terminal)
|
|
154
|
+
cd frontend
|
|
155
|
+
npm install
|
|
156
|
+
npm run dev
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Docker
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
docker-compose up --build
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Project Structure
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
{name.lower()}/
|
|
169
|
+
├── backend/ # {backend.capitalize()} application
|
|
170
|
+
│ ├── app/ # Application code
|
|
171
|
+
│ │ ├── models/ # Database models (SQLAlchemy / Django ORM)
|
|
172
|
+
│ │ ├── routers/ # API route handlers
|
|
173
|
+
│ │ ├── schemas/ # Pydantic schemas / serializers
|
|
174
|
+
│ │ └── core/ # Config, auth, database setup
|
|
175
|
+
│ └── tests/ # Backend tests
|
|
176
|
+
├── frontend/ # React application
|
|
177
|
+
│ ├── src/
|
|
178
|
+
│ │ ├── components/ # Reusable UI components
|
|
179
|
+
│ │ ├── pages/ # Route-level page components
|
|
180
|
+
│ │ ├── services/ # API client layer
|
|
181
|
+
│ │ ├── hooks/ # Custom React hooks
|
|
182
|
+
│ │ └── store/ # State management
|
|
183
|
+
│ └── public/
|
|
184
|
+
├── docker-compose.yml
|
|
185
|
+
├── Makefile
|
|
186
|
+
└── .pyforge.json # PyReactor Forge project metadata
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## API Documentation
|
|
190
|
+
|
|
191
|
+
When the backend is running, visit:
|
|
192
|
+
- Swagger UI: http://localhost:8000/docs
|
|
193
|
+
- ReDoc: http://localhost:8000/redoc
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
def _render_makefile(self):
|
|
201
|
+
backend = self.config["backend"]
|
|
202
|
+
run_cmd = (
|
|
203
|
+
"python manage.py runserver"
|
|
204
|
+
if backend == "django"
|
|
205
|
+
else "uvicorn app.main:app --reload --host 0.0.0.0 --port 8000"
|
|
206
|
+
)
|
|
207
|
+
migrate_cmd = (
|
|
208
|
+
"python manage.py migrate"
|
|
209
|
+
if backend == "django"
|
|
210
|
+
else "alembic upgrade head"
|
|
211
|
+
)
|
|
212
|
+
return f"""# Makefile for {self.name}
|
|
213
|
+
|
|
214
|
+
.PHONY: dev backend frontend install migrate test lint docker-up docker-down
|
|
215
|
+
|
|
216
|
+
dev:
|
|
217
|
+
\t$(MAKE) -j2 backend frontend
|
|
218
|
+
|
|
219
|
+
backend:
|
|
220
|
+
\tcd backend && source .venv/bin/activate && {run_cmd}
|
|
221
|
+
|
|
222
|
+
frontend:
|
|
223
|
+
\tcd frontend && npm run dev
|
|
224
|
+
|
|
225
|
+
install:
|
|
226
|
+
\tcd backend && python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt
|
|
227
|
+
\tcd frontend && npm install
|
|
228
|
+
|
|
229
|
+
migrate:
|
|
230
|
+
\tcd backend && source .venv/bin/activate && {migrate_cmd}
|
|
231
|
+
|
|
232
|
+
test:
|
|
233
|
+
\tcd backend && source .venv/bin/activate && pytest
|
|
234
|
+
\tcd frontend && npm run test
|
|
235
|
+
|
|
236
|
+
lint:
|
|
237
|
+
\tcd backend && source .venv/bin/activate && ruff check . && mypy .
|
|
238
|
+
\tcd frontend && npm run lint
|
|
239
|
+
|
|
240
|
+
docker-up:
|
|
241
|
+
\tdocker-compose up --build -d
|
|
242
|
+
|
|
243
|
+
docker-down:
|
|
244
|
+
\tdocker-compose down
|
|
245
|
+
"""
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
ROOT_GITIGNORE = """# Python
|
|
249
|
+
__pycache__/
|
|
250
|
+
*.py[cod]
|
|
251
|
+
*.egg-info/
|
|
252
|
+
.venv/
|
|
253
|
+
.env
|
|
254
|
+
dist/
|
|
255
|
+
build/
|
|
256
|
+
.mypy_cache/
|
|
257
|
+
.ruff_cache/
|
|
258
|
+
.pytest_cache/
|
|
259
|
+
|
|
260
|
+
# Node
|
|
261
|
+
node_modules/
|
|
262
|
+
dist/
|
|
263
|
+
.env.local
|
|
264
|
+
.env.*.local
|
|
265
|
+
|
|
266
|
+
# Database
|
|
267
|
+
*.db
|
|
268
|
+
*.sqlite3
|
|
269
|
+
|
|
270
|
+
# IDE
|
|
271
|
+
.vscode/
|
|
272
|
+
.idea/
|
|
273
|
+
*.swp
|
|
274
|
+
|
|
275
|
+
# OS
|
|
276
|
+
.DS_Store
|
|
277
|
+
Thumbs.db
|
|
278
|
+
|
|
279
|
+
# Docker
|
|
280
|
+
.docker/
|
|
281
|
+
"""
|
|
File without changes
|