rapidkit 1.0.0-beta.4
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.
- package/LICENSE +21 -0
- package/README.md +410 -0
- package/dist/create.d.ts +8 -0
- package/dist/create.d.ts.map +1 -0
- package/dist/create.js +505 -0
- package/dist/create.js.map +1 -0
- package/dist/demo-kit.d.ts +10 -0
- package/dist/demo-kit.d.ts.map +1 -0
- package/dist/demo-kit.js +124 -0
- package/dist/demo-kit.js.map +1 -0
- package/dist/generator.d.ts +12 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +877 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
- package/templates/kits/fastapi-standard/README.md.j2 +30 -0
- package/templates/kits/fastapi-standard/kit.yaml +120 -0
- package/templates/kits/fastapi-standard/pyproject.toml.j2 +41 -0
- package/templates/kits/fastapi-standard/src/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-standard/src/cli.py.j2 +333 -0
- package/templates/kits/fastapi-standard/src/main.py.j2 +41 -0
- package/templates/kits/fastapi-standard/src/modules/__init__.py.j2 +3 -0
- package/templates/kits/fastapi-standard/src/routing/__init__.py.j2 +13 -0
- package/templates/kits/fastapi-standard/src/routing/health.py.j2 +13 -0
- package/templates/kits/fastapi-standard/tests/__init__.py.j2 +1 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Professional CLI commands for {{ project_name }} - Poetry Scripts Integration
|
|
3
|
+
|
|
4
|
+
A modern CLI inspired by Next.js developer experience for FastAPI projects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _print_banner(emoji: str, message: str, color_code: str = "36") -> None:
|
|
15
|
+
"""Print colored banner message."""
|
|
16
|
+
print(f"\033[{color_code}m{emoji} {message}\033[0m")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _print_error(message: str) -> None:
|
|
20
|
+
"""Print error message in red."""
|
|
21
|
+
print(f"\033[91mโ {message}\033[0m")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _print_success(message: str) -> None:
|
|
25
|
+
"""Print success message in green."""
|
|
26
|
+
print(f"\033[92mโ
{message}\033[0m")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _get_env_var(key: str, default: str) -> str:
|
|
30
|
+
"""Get environment variable with default."""
|
|
31
|
+
return os.getenv(key, default)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _parse_args() -> dict:
|
|
35
|
+
"""Parse command line arguments."""
|
|
36
|
+
args = {"port": "8000", "host": "0.0.0.0", "workers": "1", "env": "development"}
|
|
37
|
+
|
|
38
|
+
i = 0
|
|
39
|
+
while i < len(sys.argv):
|
|
40
|
+
if sys.argv[i] in ["--port", "-p"] and i + 1 < len(sys.argv):
|
|
41
|
+
args["port"] = sys.argv[i + 1]
|
|
42
|
+
i += 2
|
|
43
|
+
elif sys.argv[i] in ["--host", "-h"] and i + 1 < len(sys.argv):
|
|
44
|
+
args["host"] = sys.argv[i + 1]
|
|
45
|
+
i += 2
|
|
46
|
+
elif sys.argv[i] in ["--workers", "-w"] and i + 1 < len(sys.argv):
|
|
47
|
+
args["workers"] = sys.argv[i + 1]
|
|
48
|
+
i += 2
|
|
49
|
+
elif sys.argv[i] in ["--env", "-e"] and i + 1 < len(sys.argv):
|
|
50
|
+
args["env"] = sys.argv[i + 1]
|
|
51
|
+
i += 2
|
|
52
|
+
else:
|
|
53
|
+
i += 1
|
|
54
|
+
|
|
55
|
+
return args
|
|
56
|
+
|
|
57
|
+
def _run_uvicorn(module: str, reload: bool = False, **kwargs: Any) -> None:
|
|
58
|
+
"""Run uvicorn server with proper error handling."""
|
|
59
|
+
args: Dict[str, Any] = _parse_args()
|
|
60
|
+
|
|
61
|
+
cmd: list[str] = [
|
|
62
|
+
sys.executable, "-m", "uvicorn",
|
|
63
|
+
module,
|
|
64
|
+
"--host", str(args.get("host", "0.0.0.0")),
|
|
65
|
+
"--port", str(args.get("port", 8000))
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
if reload:
|
|
69
|
+
cmd.append("--reload")
|
|
70
|
+
cmd.extend(["--reload-dir", "src"])
|
|
71
|
+
|
|
72
|
+
env: str = args.get("env", "development")
|
|
73
|
+
if env == "production" and "workers" in args:
|
|
74
|
+
cmd.extend(["--workers", str(args["workers"])])
|
|
75
|
+
cmd.append("--no-access-log")
|
|
76
|
+
|
|
77
|
+
extra_args: list[str] = kwargs.get("extra_args", [])
|
|
78
|
+
cmd.extend(extra_args)
|
|
79
|
+
|
|
80
|
+
env_vars: Dict[str, str] = os.environ.copy()
|
|
81
|
+
env_vars.update({
|
|
82
|
+
"PYTHONPATH": str(Path.cwd()),
|
|
83
|
+
"ENVIRONMENT": env
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
subprocess.run(cmd, check=False, env=env_vars)
|
|
88
|
+
except KeyboardInterrupt:
|
|
89
|
+
_print_banner("๐", "Server stopped by user")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
_print_error(f"Failed to start server: {e}")
|
|
92
|
+
sys.exit(1)
|
|
93
|
+
|
|
94
|
+
def dev() -> None:
|
|
95
|
+
"""๐ Start development server with hot reload
|
|
96
|
+
|
|
97
|
+
Usage:
|
|
98
|
+
poetry run dev [options]
|
|
99
|
+
|
|
100
|
+
Options:
|
|
101
|
+
--port, -p PORT Server port (default: 8000)
|
|
102
|
+
--host, -h HOST Server host (default: 0.0.0.0)
|
|
103
|
+
--env, -e ENV Environment (default: development)
|
|
104
|
+
|
|
105
|
+
Examples:
|
|
106
|
+
poetry run dev
|
|
107
|
+
poetry run dev --port 3000
|
|
108
|
+
poetry run dev --host 127.0.0.1 --port 8080
|
|
109
|
+
"""
|
|
110
|
+
_print_banner("๐", "Starting development server with hot reload...")
|
|
111
|
+
_print_banner("๐", f"Working directory: {Path.cwd()}")
|
|
112
|
+
|
|
113
|
+
args = _parse_args()
|
|
114
|
+
_print_banner("๐", f"Server will be available at: http://{args['host']}:{args['port']}")
|
|
115
|
+
|
|
116
|
+
_run_uvicorn("src.main:app", reload=True)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def start() -> None:
|
|
120
|
+
"""โก Start production server
|
|
121
|
+
|
|
122
|
+
Usage:
|
|
123
|
+
poetry run start [options]
|
|
124
|
+
|
|
125
|
+
Options:
|
|
126
|
+
--port, -p PORT Server port (default: 8000)
|
|
127
|
+
--host, -h HOST Server host (default: 0.0.0.0)
|
|
128
|
+
--workers, -w NUM Number of workers (default: 1)
|
|
129
|
+
|
|
130
|
+
Examples:
|
|
131
|
+
poetry run start
|
|
132
|
+
poetry run start --workers 4 --port 80
|
|
133
|
+
"""
|
|
134
|
+
_print_banner("โก", "Starting production server...")
|
|
135
|
+
|
|
136
|
+
args = _parse_args()
|
|
137
|
+
_print_banner("๐ฅ", f"Workers: {args['workers']}")
|
|
138
|
+
_print_banner("๐", f"Server will be available at: http://{args['host']}:{args['port']}")
|
|
139
|
+
|
|
140
|
+
_run_uvicorn("src.main:app", reload=False, env="production")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def build() -> None:
|
|
144
|
+
"""๐ฆ Build project for production
|
|
145
|
+
|
|
146
|
+
This command prepares the project for production deployment:
|
|
147
|
+
- Validates dependencies
|
|
148
|
+
- Runs type checking
|
|
149
|
+
- Builds distribution packages
|
|
150
|
+
"""
|
|
151
|
+
_print_banner("๐ฆ", "Building project for production...")
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
# Type checking
|
|
155
|
+
_print_banner("๐", "Running type checks...")
|
|
156
|
+
result = subprocess.run([
|
|
157
|
+
sys.executable, "-m", "mypy", "src", "--ignore-missing-imports"
|
|
158
|
+
], capture_output=True, text=True)
|
|
159
|
+
|
|
160
|
+
if result.returncode != 0:
|
|
161
|
+
_print_error("Type checking failed!")
|
|
162
|
+
print(result.stdout)
|
|
163
|
+
print(result.stderr)
|
|
164
|
+
else:
|
|
165
|
+
_print_success("Type checking passed!")
|
|
166
|
+
|
|
167
|
+
# Build wheel
|
|
168
|
+
_print_banner("๐๏ธ", "Building distribution packages...")
|
|
169
|
+
subprocess.run([sys.executable, "-m", "build"], check=True)
|
|
170
|
+
|
|
171
|
+
_print_success("Build completed successfully!")
|
|
172
|
+
_print_banner("๐", "Distribution files created in 'dist/' directory")
|
|
173
|
+
|
|
174
|
+
except subprocess.CalledProcessError as e:
|
|
175
|
+
_print_error(f"Build failed: {e}")
|
|
176
|
+
sys.exit(1)
|
|
177
|
+
except FileNotFoundError:
|
|
178
|
+
_print_error("Build tools not found. Install with: poetry install --group dev")
|
|
179
|
+
sys.exit(1)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def test() -> None:
|
|
183
|
+
"""๐งช Run tests with coverage
|
|
184
|
+
|
|
185
|
+
Usage:
|
|
186
|
+
poetry run test [pytest-options]
|
|
187
|
+
|
|
188
|
+
Examples:
|
|
189
|
+
poetry run test
|
|
190
|
+
poetry run test -v
|
|
191
|
+
poetry run test tests/test_api.py
|
|
192
|
+
poetry run test --cov-report=html
|
|
193
|
+
"""
|
|
194
|
+
_print_banner("๐งช", "Running tests with coverage...")
|
|
195
|
+
|
|
196
|
+
# Get additional pytest args
|
|
197
|
+
pytest_args = [arg for arg in sys.argv[2:] if not arg.startswith("-")]
|
|
198
|
+
|
|
199
|
+
cmd = [
|
|
200
|
+
sys.executable, "-m", "pytest",
|
|
201
|
+
"--cov=src",
|
|
202
|
+
"--cov-report=term-missing",
|
|
203
|
+
"--cov-report=xml",
|
|
204
|
+
"-v"
|
|
205
|
+
] + pytest_args
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
result = subprocess.run(cmd, check=False)
|
|
209
|
+
|
|
210
|
+
if result.returncode == 0:
|
|
211
|
+
_print_success("All tests passed!")
|
|
212
|
+
else:
|
|
213
|
+
_print_error("Some tests failed!")
|
|
214
|
+
sys.exit(result.returncode)
|
|
215
|
+
|
|
216
|
+
except FileNotFoundError:
|
|
217
|
+
_print_error("pytest not found. Install with: poetry install --group dev")
|
|
218
|
+
sys.exit(1)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def lint() -> None:
|
|
222
|
+
"""๏ฟฝ Run code linting and formatting
|
|
223
|
+
|
|
224
|
+
This command runs:
|
|
225
|
+
- ruff for linting
|
|
226
|
+
- black for formatting
|
|
227
|
+
- isort for import sorting
|
|
228
|
+
"""
|
|
229
|
+
_print_banner("๏ฟฝ", "Running linting and formatting...")
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
# Ruff linting
|
|
233
|
+
_print_banner("๐", "Running ruff linter...")
|
|
234
|
+
subprocess.run([sys.executable, "-m", "ruff", "check", "src"], check=True)
|
|
235
|
+
|
|
236
|
+
# Black formatting
|
|
237
|
+
_print_banner("๐จ", "Running black formatter...")
|
|
238
|
+
subprocess.run([sys.executable, "-m", "black", "src", "--check"], check=True)
|
|
239
|
+
|
|
240
|
+
# isort import sorting
|
|
241
|
+
_print_banner("๐ฆ", "Checking import sorting...")
|
|
242
|
+
subprocess.run([sys.executable, "-m", "isort", "src", "--check-only"], check=True)
|
|
243
|
+
|
|
244
|
+
_print_success("All linting checks passed!")
|
|
245
|
+
|
|
246
|
+
except subprocess.CalledProcessError:
|
|
247
|
+
_print_error("Linting failed! Run 'poetry run format' to fix issues.")
|
|
248
|
+
sys.exit(1)
|
|
249
|
+
except FileNotFoundError:
|
|
250
|
+
_print_error("Linting tools not found. Install with: poetry install --group dev")
|
|
251
|
+
sys.exit(1)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def format() -> None:
|
|
255
|
+
"""โจ Format code automatically
|
|
256
|
+
|
|
257
|
+
This command auto-fixes:
|
|
258
|
+
- Code formatting with black
|
|
259
|
+
- Import sorting with isort
|
|
260
|
+
- Auto-fixable linting issues with ruff
|
|
261
|
+
"""
|
|
262
|
+
_print_banner("โจ", "Formatting code...")
|
|
263
|
+
|
|
264
|
+
try:
|
|
265
|
+
# Ruff auto-fix
|
|
266
|
+
_print_banner("๐ง", "Auto-fixing with ruff...")
|
|
267
|
+
subprocess.run([sys.executable, "-m", "ruff", "check", "src", "--fix"], check=True)
|
|
268
|
+
|
|
269
|
+
# Black formatting
|
|
270
|
+
_print_banner("๐จ", "Formatting with black...")
|
|
271
|
+
subprocess.run([sys.executable, "-m", "black", "src"], check=True)
|
|
272
|
+
|
|
273
|
+
# isort import sorting
|
|
274
|
+
_print_banner("๐ฆ", "Sorting imports with isort...")
|
|
275
|
+
subprocess.run([sys.executable, "-m", "isort", "src"], check=True)
|
|
276
|
+
|
|
277
|
+
_print_success("Code formatting completed!")
|
|
278
|
+
|
|
279
|
+
except subprocess.CalledProcessError as e:
|
|
280
|
+
_print_error(f"Formatting failed: {e}")
|
|
281
|
+
sys.exit(1)
|
|
282
|
+
except FileNotFoundError:
|
|
283
|
+
_print_error("Formatting tools not found. Install with: poetry install --group dev")
|
|
284
|
+
sys.exit(1)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def help_cmd() -> None:
|
|
288
|
+
"""๐ Show detailed help information"""
|
|
289
|
+
_print_banner("๐", "{{ project_name }} - Available Commands")
|
|
290
|
+
|
|
291
|
+
commands = {
|
|
292
|
+
"dev": "๐ Start development server with hot reload",
|
|
293
|
+
"start": "โก Start production server",
|
|
294
|
+
"build": "๐ฆ Build project for production",
|
|
295
|
+
"test": "๐งช Run tests with coverage",
|
|
296
|
+
"lint": "๐ง Run code linting and formatting checks",
|
|
297
|
+
"format": "โจ Format code automatically",
|
|
298
|
+
"help": "๐ Show this help message"
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
print("\nCommands:")
|
|
302
|
+
for cmd, desc in commands.items():
|
|
303
|
+
print(f" poetry run {cmd:<10} {desc}")
|
|
304
|
+
|
|
305
|
+
print("\nFor detailed command help:")
|
|
306
|
+
print(" poetry run <command> --help")
|
|
307
|
+
|
|
308
|
+
print("\nProject: {{ project_name }}")
|
|
309
|
+
print("Framework: FastAPI + RapidKit")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
if __name__ == "__main__":
|
|
313
|
+
if len(sys.argv) < 2:
|
|
314
|
+
help_cmd()
|
|
315
|
+
sys.exit(0)
|
|
316
|
+
|
|
317
|
+
command = sys.argv[1]
|
|
318
|
+
commands = {
|
|
319
|
+
"dev": dev,
|
|
320
|
+
"start": start,
|
|
321
|
+
"build": build,
|
|
322
|
+
"test": test,
|
|
323
|
+
"lint": lint,
|
|
324
|
+
"format": format,
|
|
325
|
+
"help": help_cmd,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if command in commands:
|
|
329
|
+
commands[command]()
|
|
330
|
+
else:
|
|
331
|
+
_print_error(f"Unknown command: {command}")
|
|
332
|
+
print("\nRun 'poetry run help' to see available commands.")
|
|
333
|
+
sys.exit(1)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""{{ project_name }} application entrypoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
7
|
+
|
|
8
|
+
# <<<inject:imports>>>
|
|
9
|
+
|
|
10
|
+
from .routing import api_router
|
|
11
|
+
|
|
12
|
+
app = FastAPI(
|
|
13
|
+
title="{{ project_name }}",
|
|
14
|
+
description="{{ description }}",
|
|
15
|
+
version="{{ app_version }}",
|
|
16
|
+
docs_url="/docs",
|
|
17
|
+
redoc_url="/redoc",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
app.add_middleware(
|
|
21
|
+
CORSMiddleware,
|
|
22
|
+
allow_origins=["*"],
|
|
23
|
+
allow_credentials=True,
|
|
24
|
+
allow_methods=["*"],
|
|
25
|
+
allow_headers=["*"],
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
app.include_router(api_router, prefix="/api")
|
|
29
|
+
# <<<inject:routes>>>
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.on_event("startup")
|
|
33
|
+
async def _on_startup() -> None:
|
|
34
|
+
"""Execute startup hooks provided by modules."""
|
|
35
|
+
# <<<inject:startup>>>
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@app.on_event("shutdown")
|
|
39
|
+
async def _on_shutdown() -> None:
|
|
40
|
+
"""Execute shutdown hooks provided by modules."""
|
|
41
|
+
# <<<inject:shutdown>>>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""API router assembly."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter
|
|
6
|
+
|
|
7
|
+
from .health import router as health_router
|
|
8
|
+
# <<<inject:router-imports>>>
|
|
9
|
+
|
|
10
|
+
api_router = APIRouter()
|
|
11
|
+
|
|
12
|
+
api_router.include_router(health_router, prefix="/health", tags=["health"])
|
|
13
|
+
# <<<inject:router-mount>>>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Health endpoints."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter
|
|
6
|
+
|
|
7
|
+
router = APIRouter()
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@router.get("/", summary="Health check")
|
|
11
|
+
async def heartbeat() -> dict[str, str]:
|
|
12
|
+
"""Return basic service heartbeat."""
|
|
13
|
+
return {"status": "ok"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Test package placeholder."""
|