djboost 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.
djboost/__init__.py ADDED
@@ -0,0 +1 @@
1
+ # init
djboost/cli.py ADDED
@@ -0,0 +1,37 @@
1
+ import typer
2
+ from djboost.commands.create_project import create_project_command
3
+ from djboost.commands.create_app import create_app_command
4
+ from djboost.commands.add_cicd import add_cicd_command
5
+ from djboost.commands.remove_cicd import remove_cicd_command
6
+
7
+ app = typer.Typer(help="djboost — Django project generator CLI")
8
+ create = typer.Typer(help="Create a new project or app")
9
+ add = typer.Typer(help="Add integrations to your project")
10
+ remove = typer.Typer(help="Remove integrations from your project")
11
+
12
+ app.add_typer(create, name="create")
13
+ app.add_typer(add, name="add")
14
+ app.add_typer(remove, name="remove")
15
+
16
+ create.command("project")(create_project_command)
17
+ create.command("app")(create_app_command)
18
+ add.command("cicd")(add_cicd_command)
19
+ remove.command("cicd")(remove_cicd_command)
20
+
21
+
22
+ def version_callback(value: bool):
23
+ if value:
24
+ typer.echo("djboost version 0.1.0")
25
+ raise typer.Exit()
26
+
27
+
28
+ @app.callback()
29
+ def main(
30
+ version: bool = typer.Option(
31
+ None, "--version", "-v",
32
+ callback=version_callback,
33
+ is_eager=True,
34
+ help="Show the version and exit."
35
+ )
36
+ ):
37
+ pass
@@ -0,0 +1 @@
1
+ # init
@@ -0,0 +1,16 @@
1
+ import typer
2
+ from rich import print
3
+ from djboost.generator import generate_github_actions, generate_gitlab_ci, check_virtual_environment
4
+
5
+ def add_cicd_command(provider: str = typer.Argument(..., help="The CI/CD provider to add (github or gitlab)")):
6
+ check_virtual_environment()
7
+ provider = provider.lower()
8
+ if provider == "github":
9
+ generate_github_actions()
10
+ print("[green]GitHub Actions workflow created successfully![/green]")
11
+ elif provider == "gitlab":
12
+ generate_gitlab_ci()
13
+ print("[green]GitLab CI pipeline created successfully![/green]")
14
+ else:
15
+ print(f"[red]Error: Unsupported provider '{provider}'. Supported providers are: github, gitlab.[/red]")
16
+ raise typer.Exit(code=1)
@@ -0,0 +1,130 @@
1
+ import re
2
+ import sys
3
+ import subprocess
4
+ from pathlib import Path
5
+ import typer
6
+ from rich import print
7
+ from djboost.generator import check_virtual_environment, validate_name
8
+
9
+
10
+ def get_project_name():
11
+ if not Path("manage.py").exists():
12
+ print("[red]Error: manage.py not found. Are you in the project root?[/red]")
13
+ raise typer.Exit(1)
14
+
15
+ content = Path("manage.py").read_text(encoding="utf-8")
16
+ match = re.search(r"['\"]DJANGO_SETTINGS_MODULE['\"],\s*['\"]([^.]+)\.settings['\"]", content)
17
+ if match:
18
+ return match.group(1)
19
+
20
+ print("[red]Error: Could not determine project name from manage.py[/red]")
21
+ raise typer.Exit(1)
22
+
23
+
24
+ def update_settings(project_name: str, app_name: str):
25
+ settings_path = Path(project_name) / "settings.py"
26
+ if not settings_path.exists():
27
+ print(f"[yellow]Warning: Could not find settings.py at {settings_path}. Skipping.[/yellow]")
28
+ return
29
+
30
+ content = settings_path.read_text(encoding="utf-8")
31
+ app_string = f"'apps.{app_name}',"
32
+
33
+ if app_string in content or f'"apps.{app_name}",' in content:
34
+ print(f"[yellow]App '{app_name}' is already in INSTALLED_APPS[/yellow]")
35
+ return
36
+
37
+ if "INSTALLED_APPS = [" in content:
38
+ content = re.sub(
39
+ r"(INSTALLED_APPS\s*=\s*\[.*?)(\n?\])",
40
+ rf"\1\n {app_string}\2",
41
+ content,
42
+ flags=re.DOTALL
43
+ )
44
+ settings_path.write_text(content, encoding="utf-8")
45
+ print(f"[green]✔ Added '{app_string}' to INSTALLED_APPS[/green]")
46
+ else:
47
+ print("[yellow]Warning: Could not find INSTALLED_APPS in settings.py[/yellow]")
48
+
49
+
50
+ def update_urls(project_name: str, app_name: str):
51
+ urls_path = Path(project_name) / "urls.py"
52
+ if not urls_path.exists():
53
+ print(f"[yellow]Warning: Could not find urls.py at {urls_path}. Skipping.[/yellow]")
54
+ return
55
+
56
+ content = urls_path.read_text(encoding="utf-8")
57
+
58
+ if f"apps.{app_name}.urls" in content:
59
+ print(f"[yellow]App '{app_name}' is already mapped in urls.py[/yellow]")
60
+ return
61
+
62
+ if "include" not in content:
63
+ content = re.sub(r"(from django\.urls import.*?path)", r"\1, include", content)
64
+
65
+ if "urlpatterns = [" in content:
66
+ content = content.replace(
67
+ "urlpatterns = [",
68
+ f"urlpatterns = [\n path('api/{app_name}/', include('apps.{app_name}.urls')),"
69
+ )
70
+ urls_path.write_text(content, encoding="utf-8")
71
+ print(f"[green]✔ Mapped /api/{app_name}/ in {project_name}/urls.py[/green]")
72
+ else:
73
+ print("[yellow]Warning: Could not find urlpatterns in urls.py[/yellow]")
74
+
75
+
76
+ def create_app_urls(app_name: str):
77
+ urls_path = Path("apps") / app_name / "urls.py"
78
+ content = f"""from django.urls import path
79
+ from . import views
80
+
81
+ app_name = '{app_name}'
82
+
83
+ urlpatterns = [
84
+ # path('', views.MyView.as_view(), name='my-view'),
85
+ ]
86
+ """
87
+ urls_path.write_text(content, encoding="utf-8")
88
+
89
+
90
+ def create_app_command(name: str = typer.Argument(..., help="The name of the Django app to create")):
91
+ check_virtual_environment()
92
+ validate_name(name, "app name")
93
+
94
+ if not Path("manage.py").exists():
95
+ print("[red]Error: manage.py not found. Run this command from your Django project root.[/red]")
96
+ raise typer.Exit(1)
97
+
98
+ app_path = Path("apps") / name
99
+ if app_path.exists():
100
+ print(f"[red]Error: App '{name}' already exists at apps/{name}.[/red]")
101
+ raise typer.Exit(1)
102
+
103
+ Path("apps").mkdir(exist_ok=True)
104
+
105
+ print(f"[cyan]Creating app '{name}'...[/cyan]")
106
+ result = subprocess.run(
107
+ [sys.executable, "manage.py", "startapp", name, f"apps/{name}"],
108
+ capture_output=True, text=True
109
+ )
110
+ if result.returncode != 0:
111
+ print(f"[red]Error creating app:\n{result.stderr}[/red]")
112
+ raise typer.Exit(1)
113
+
114
+ (Path(f"apps/{name}") / "__init__.py").touch()
115
+
116
+ # Fix apps.py name
117
+ apps_py_path = Path(f"apps/{name}/apps.py")
118
+ if apps_py_path.exists():
119
+ apps_content = apps_py_path.read_text(encoding="utf-8")
120
+ apps_content = re.sub(rf"name\s*=\s*['\"]{name}['\"]", f"name = 'apps.{name}'", apps_content)
121
+ apps_py_path.write_text(apps_content, encoding="utf-8")
122
+
123
+ try:
124
+ project_name = get_project_name()
125
+ update_settings(project_name, name)
126
+ update_urls(project_name, name)
127
+ create_app_urls(name)
128
+ print(f"[bold green]✅ App '{name}' created and configured successfully![/bold green]")
129
+ except Exception as e:
130
+ print(f"[red]Error during auto-configuration: {str(e)}[/red]")
@@ -0,0 +1,6 @@
1
+ import typer
2
+ from djboost.generator import create_project
3
+
4
+
5
+ def create_project_command(name: str = typer.Argument("core", help="The name of the Django project")):
6
+ create_project(name)
@@ -0,0 +1,34 @@
1
+ import os
2
+ import shutil
3
+ import typer
4
+ from rich import print
5
+ from djboost.generator import check_virtual_environment
6
+
7
+ def remove_cicd_command(provider: str = typer.Argument(..., help="The CI/CD provider to remove (github or gitlab)")):
8
+ check_virtual_environment()
9
+ provider = provider.lower()
10
+ if provider == "github":
11
+ github_dir = ".github"
12
+ if os.path.exists(github_dir):
13
+ try:
14
+ shutil.rmtree(github_dir)
15
+ print("[green]GitHub Actions workflow removed successfully![/green]")
16
+ except Exception as e:
17
+ print(f"[red]Failed to remove GitHub Actions workflow: {e}[/red]")
18
+ else:
19
+ print("[yellow]GitHub Actions workflow is not present in this project.[/yellow]")
20
+
21
+ elif provider == "gitlab":
22
+ gitlab_file = ".gitlab-ci.yml"
23
+ if os.path.exists(gitlab_file):
24
+ try:
25
+ os.remove(gitlab_file)
26
+ print("[green]GitLab CI pipeline removed successfully![/green]")
27
+ except Exception as e:
28
+ print(f"[red]Failed to remove GitLab CI pipeline: {e}[/red]")
29
+ else:
30
+ print("[yellow]GitLab CI pipeline is not present in this project.[/yellow]")
31
+
32
+ else:
33
+ print(f"[red]Error: Unsupported provider '{provider}'. Supported providers are: github, gitlab.[/red]")
34
+ raise typer.Exit(code=1)
djboost/generator.py ADDED
@@ -0,0 +1,880 @@
1
+ import os
2
+ import sys
3
+ import subprocess
4
+ import re
5
+ from pathlib import Path
6
+ from rich import print
7
+ from rich.console import Console
8
+
9
+ console = Console()
10
+
11
+
12
+ def check_virtual_environment():
13
+ """Check if user is inside a virtual environment."""
14
+ in_venv = (
15
+ hasattr(sys, 'real_prefix') or
16
+ (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)
17
+ )
18
+ if not in_venv:
19
+ print("[bold red]⚠ You are not inside a virtual environment![/bold red]")
20
+ print("[yellow]Please create and activate a virtual environment first:[/yellow]")
21
+ print()
22
+ print(" [cyan]python -m venv env[/cyan]")
23
+ print()
24
+ print(" [cyan]# Windows:[/cyan]")
25
+ print(" [cyan]env\\Scripts\\activate[/cyan]")
26
+ print()
27
+ print(" [cyan]# Mac/Linux:[/cyan]")
28
+ print(" [cyan]source env/bin/activate[/cyan]")
29
+ print()
30
+ import typer
31
+ raise typer.Exit(1)
32
+
33
+
34
+ def validate_name(name: str, label: str = "name"):
35
+ """Validate that name is a valid Python identifier."""
36
+ if not name.isidentifier():
37
+ print(f"[red]Error: '{name}' is not a valid {label}. Use only letters, numbers, and underscores. Must not start with a number.[/red]")
38
+ import typer
39
+ raise typer.Exit(1)
40
+ if name[0].isdigit():
41
+ print(f"[red]Error: '{name}' must not start with a digit.[/red]")
42
+ import typer
43
+ raise typer.Exit(1)
44
+
45
+
46
+ def update_settings_file(settings_path: str, name: str):
47
+ with open(settings_path, "r", encoding="utf-8") as f:
48
+ content = f.read()
49
+
50
+ # Extract the original SECRET_KEY generated by Django
51
+ match = re.search(r"SECRET_KEY\s*=\s*(['\"].*?['\"])", content)
52
+ secret_key = match.group(1) if match else "'your-secret-key-here'"
53
+
54
+ # 1. Add imports at the top
55
+ content = content.replace(
56
+ "from pathlib import Path",
57
+ "from pathlib import Path\nfrom datetime import timedelta\nfrom celery.schedules import crontab\nfrom decouple import config"
58
+ )
59
+
60
+ # 2. Update SECRET_KEY, DEBUG, ALLOWED_HOSTS
61
+ content = re.sub(r"SECRET_KEY = .*", "SECRET_KEY = config('SECRET_KEY')", content)
62
+ content = re.sub(r"DEBUG = .*", "DEBUG = config('DEBUG', default=False, cast=bool)", content)
63
+ content = re.sub(
64
+ r"ALLOWED_HOSTS = .*",
65
+ "ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost', cast=lambda v: [s.strip() for s in v.split(',')])",
66
+ content
67
+ )
68
+
69
+ # 3. Update INSTALLED_APPS
70
+ apps_addition = """ 'corsheaders',
71
+ 'rest_framework',
72
+ 'rest_framework_simplejwt',
73
+ 'rest_framework_simplejwt.token_blacklist',
74
+ 'channels',
75
+ 'channels_redis',
76
+ 'drf_spectacular',"""
77
+ content = re.sub(
78
+ r"['\"]django\.contrib\.staticfiles['\"],",
79
+ f"'daphne',\n 'django.contrib.staticfiles',\n{apps_addition}",
80
+ content
81
+ )
82
+
83
+ # 4. Update MIDDLEWARE
84
+ content = content.replace(
85
+ "MIDDLEWARE = [",
86
+ "MIDDLEWARE = [\n 'corsheaders.middleware.CorsMiddleware',\n 'whitenoise.middleware.WhiteNoiseMiddleware',"
87
+ )
88
+
89
+ # 5. Update DATABASES
90
+ db_config = """DATABASES = {
91
+ 'default': {
92
+ 'ENGINE': config("DB_ENGINE", default="django.db.backends.sqlite3"),
93
+ 'NAME': config('DB_NAME', default=BASE_DIR / 'db.sqlite3'),
94
+ 'USER': config('DB_USER', default=''),
95
+ 'PASSWORD': config('DB_PASSWORD', default=''),
96
+ 'HOST': config('DB_HOST', default='localhost'),
97
+ 'PORT': config('DB_PORT', default=5432, cast=int),
98
+ 'CONN_MAX_AGE': config('CONN_MAX_AGE', default=600, cast=int),
99
+ }
100
+ }"""
101
+ content = re.sub(r"DATABASES\s*=\s*\{.*?\}\s*\}", db_config, content, flags=re.DOTALL)
102
+
103
+ # 6. Update WSGI / ASGI
104
+ content = re.sub(
105
+ rf"WSGI_APPLICATION\s*=\s*['\"]{name}\.wsgi\.application['\"]",
106
+ f"WSGI_APPLICATION = '{name}.wsgi.application'\nASGI_APPLICATION = '{name}.asgi.application'",
107
+ content
108
+ )
109
+
110
+ # 6.5 Update Static / Media Files
111
+ static_media_config = """STATIC_URL = '/static/'
112
+ MEDIA_URL = '/media/'
113
+
114
+ STATICFILES_DIRS = [BASE_DIR / 'static']
115
+ STATIC_ROOT = BASE_DIR / 'assets'
116
+ MEDIA_ROOT = BASE_DIR / 'media'"""
117
+ content = re.sub(r"STATIC_URL\s*=\s*['\"]static/['\"]", static_media_config, content)
118
+
119
+ # 7. Append custom configurations at the end
120
+ custom_configs = """
121
+
122
+ # ── Caching Configuration (Redis) ─────────────────────────────────────────────
123
+ CACHES = {
124
+ "default": {
125
+ "BACKEND": "django.core.cache.backends.redis.RedisCache",
126
+ "LOCATION": config("REDIS_URL", default="redis://127.0.0.1:6379/1"),
127
+ }
128
+ }
129
+
130
+ # ── Security & Performance Settings ───────────────────────────────────────────
131
+ SECURE_BROWSER_XSS_FILTER = True
132
+ SECURE_CONTENT_TYPE_NOSNIFF = True
133
+ X_FRAME_OPTIONS = 'DENY'
134
+ STORAGES = {
135
+ "staticfiles": {
136
+ "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
137
+ },
138
+ }
139
+
140
+ # ── Channels Configuration ────────────────────────────────────────────────────
141
+ CHANNEL_LAYERS = {
142
+ "default": {
143
+ "BACKEND": "channels_redis.core.RedisChannelLayer",
144
+ "CONFIG": {
145
+ "hosts": [(config("REDIS_HOST", default="127.0.0.1"), config("REDIS_PORT", default=6379, cast=int))],
146
+ },
147
+ },
148
+ }
149
+
150
+ # ── REST Framework & JWT Settings ─────────────────────────────────────────────
151
+ REST_FRAMEWORK = {
152
+ 'DEFAULT_AUTHENTICATION_CLASSES': (
153
+ 'rest_framework_simplejwt.authentication.JWTAuthentication',
154
+ ),
155
+ 'EXCEPTION_HANDLER': 'core.utils.custom_exception_handler',
156
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
157
+ 'PAGE_SIZE': 10,
158
+ 'DEFAULT_THROTTLE_CLASSES': [
159
+ 'rest_framework.throttling.AnonRateThrottle',
160
+ 'rest_framework.throttling.UserRateThrottle'
161
+ ],
162
+ 'DEFAULT_THROTTLE_RATES': {
163
+ 'anon': '100/day',
164
+ 'user': '1000/day'
165
+ },
166
+ 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
167
+ }
168
+
169
+ SPECTACULAR_SETTINGS = {
170
+ 'TITLE': 'API Documentation',
171
+ 'DESCRIPTION': 'Project API Documentation',
172
+ 'VERSION': '1.0.0',
173
+ 'SERVE_INCLUDE_SCHEMA': False,
174
+ }
175
+
176
+ SIMPLE_JWT = {
177
+ 'ACCESS_TOKEN_LIFETIME': timedelta(days=10),
178
+ 'REFRESH_TOKEN_LIFETIME': timedelta(days=30),
179
+ 'ROTATE_REFRESH_TOKENS': True,
180
+ 'BLACKLIST_AFTER_ROTATION': True,
181
+ 'ALGORITHM': 'HS256',
182
+ 'SIGNING_KEY': SECRET_KEY,
183
+ 'AUTH_HEADER_TYPES': ('Bearer',),
184
+ }
185
+
186
+ # ── CORS & CSRF Settings ──────────────────────────────────────────────────────
187
+ CSRF_TRUSTED_ORIGINS = [i.strip() for i in config("CSRF_TRUSTED_ORIGINS", "").split(",") if i]
188
+ CORS_ALLOWED_ORIGINS = [i.strip() for i in config("CORS_ALLOWED_ORIGINS", "").split(",") if i]
189
+ CORS_ALLOW_CREDENTIALS = True
190
+ CORS_ALLOW_HEADERS = ['accept', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with']
191
+ CORS_ALLOW_METHODS = ['DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT']
192
+
193
+ # ── Email Settings ────────────────────────────────────────────────────────────
194
+ EMAIL_USE_SSL = config('EMAIL_USE_SSL', default=True, cast=bool)
195
+ EMAIL_HOST = config('EMAIL_HOST', default='')
196
+ EMAIL_HOST_USER = config('EMAIL_HOST_USER', default='')
197
+ EMAIL_HOST_PASSWORD = config('EMAIL_HOST_PASSWORD', default='')
198
+ EMAIL_PORT = config('EMAIL_PORT', default=465, cast=int)
199
+ EMAIL_BACKEND = config('EMAIL_BACKEND', default='django.core.mail.backends.smtp.EmailBackend')
200
+
201
+ # ── Logging Configuration ─────────────────────────────────────────────────────
202
+ LOGGING = {
203
+ "version": 1,
204
+ "disable_existing_loggers": False,
205
+ "formatters": {
206
+ "verbose": {
207
+ "format": "[{levelname}] {asctime} {name}: {message}",
208
+ "style": "{",
209
+ },
210
+ },
211
+ "handlers": {
212
+ "console": {
213
+ "class": "logging.StreamHandler",
214
+ "formatter": "verbose",
215
+ },
216
+ },
217
+ "loggers": {
218
+ "django": {
219
+ "handlers": ["console"],
220
+ "level": "INFO",
221
+ },
222
+ "celery": {
223
+ "handlers": ["console"],
224
+ "level": "INFO",
225
+ },
226
+ },
227
+ }
228
+
229
+ # ── Celery & Background Tasks ─────────────────────────────────────────────────
230
+ CELERY_BROKER_URL = config("CELERY_BROKER_URL", default="redis://127.0.0.1:6379/0")
231
+ CELERY_RESULT_BACKEND = config("CELERY_RESULT_BACKEND", default="redis://127.0.0.1:6379/0")
232
+ CELERY_ACCEPT_CONTENT = ["json"]
233
+ CELERY_TASK_SERIALIZER = "json"
234
+ CELERY_RESULT_SERIALIZER = "json"
235
+ CELERY_TIMEZONE = TIME_ZONE
236
+ CELERY_BROKER_CONNECTION_RETRY_ON_STARTUP = True
237
+ CELERY_TASK_TIME_LIMIT = 5 * 60
238
+ CELERY_TASK_SOFT_TIME_LIMIT = 60
239
+ CELERY_WORKER_PREFETCH_MULTIPLIER = 1
240
+
241
+ # Celery Beat Schedule
242
+ CELERY_BEAT_SCHEDULE = {
243
+ "sample_task": {
244
+ "task": "core.tasks.sample_task",
245
+ "schedule": crontab(minute="*/15"),
246
+ },
247
+ }
248
+ """
249
+ custom_configs = custom_configs.replace("core.utils.", f"{name}.utils.")
250
+ custom_configs = custom_configs.replace("core.tasks.", f"{name}.tasks.")
251
+ content += custom_configs
252
+
253
+ with open(settings_path, "w", encoding="utf-8") as f:
254
+ f.write(content)
255
+
256
+ return secret_key
257
+
258
+
259
+ def create_directories():
260
+ os.makedirs("apps", exist_ok=True)
261
+ os.makedirs("static", exist_ok=True)
262
+ os.makedirs("media", exist_ok=True)
263
+
264
+
265
+ def install_dependencies():
266
+ packages = [
267
+ "Django",
268
+ "djangorestframework",
269
+ "djangorestframework-simplejwt",
270
+ "django-cors-headers",
271
+ "python-decouple",
272
+ "psycopg2-binary",
273
+ "Pillow",
274
+ "celery",
275
+ "redis",
276
+ "daphne",
277
+ "channels",
278
+ "channels-redis",
279
+ "whitenoise",
280
+ "drf-spectacular",
281
+ "pytest",
282
+ "pytest-django",
283
+ "pytest-cov",
284
+ "pre-commit",
285
+ "black",
286
+ "flake8",
287
+ "isort",
288
+ ]
289
+ print("[cyan]📦 Installing dependencies...[/cyan]")
290
+ result = subprocess.run(
291
+ [sys.executable, "-m", "pip", "install"] + packages,
292
+ capture_output=True, text=True
293
+ )
294
+ if result.returncode != 0:
295
+ print(f"[red]Error installing dependencies:\n{result.stderr}[/red]")
296
+ import typer
297
+ raise typer.Exit(1)
298
+ print("[green]✔ Dependencies installed.[/green]")
299
+
300
+
301
+ def generate_env_file(secret_key: str, name: str):
302
+ env_content = f"""# -----------------------------
303
+ # Django Secret Key & Debug
304
+ # -----------------------------
305
+ DEBUG=True
306
+ SECRET_KEY={secret_key}
307
+
308
+ # ALLOWED HOSTS
309
+ ALLOWED_HOSTS=localhost,127.0.0.1
310
+
311
+ # CORS SETTINGS------------------------------#
312
+ # CSRF trusted origins
313
+ CSRF_TRUSTED_ORIGINS=http://localhost:5173,http://127.0.0.1:8000
314
+
315
+ # CORS allowed origins
316
+ CORS_ALLOWED_ORIGINS=http://localhost:5173,http://127.0.0.1:8000
317
+
318
+
319
+ # -----------------------------
320
+ # Database: #'django.db.backends.postgresql', # or 'django.db.backends.mysql', # or 'django.db.backends.sqlite3'
321
+ # -----------------------------
322
+ # DB_ENGINE=django.db.backends.postgresql
323
+ # DB_NAME={name}_db
324
+ # DB_USER={name}_user
325
+ # DB_PASSWORD=your-db-password
326
+ # DB_HOST=localhost
327
+ # DB_PORT=5432
328
+ # CONN_MAX_AGE=600
329
+
330
+ # -----------------------------
331
+ # Redis Configuration
332
+ # -----------------------------
333
+ REDIS_HOST=localhost
334
+ REDIS_PORT=6379
335
+ REDIS_URL=redis://localhost:6379/1
336
+ CELERY_BROKER_URL=redis://localhost:6379/0
337
+ CELERY_RESULT_BACKEND=redis://localhost:6379/0
338
+
339
+ # -----------------------------
340
+ # Email / SMTP Settings
341
+ # -----------------------------
342
+ EMAIL_USE_SSL=True
343
+ EMAIL_HOST=smtp.gmail.com
344
+ EMAIL_HOST_USER=your-email@gmail.com
345
+ EMAIL_HOST_PASSWORD=your-app-password
346
+ EMAIL_PORT=465
347
+ EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend
348
+ """
349
+ with open(".env", "w", encoding="utf-8") as f:
350
+ f.write(env_content)
351
+
352
+
353
+ def generate_docker_files(name: str):
354
+ dockerfile_content = """FROM python:3.11-slim
355
+
356
+ ENV PYTHONDONTWRITEBYTECODE 1
357
+ ENV PYTHONUNBUFFERED 1
358
+
359
+ WORKDIR /app
360
+
361
+ RUN apt-get update \\
362
+ && apt-get install -y --no-install-recommends gcc libpq-dev \\
363
+ && apt-get clean \\
364
+ && rm -rf /var/lib/apt/lists/*
365
+
366
+ COPY requirements.txt /app/
367
+ RUN pip install --upgrade pip
368
+ RUN pip install -r requirements.txt
369
+
370
+ COPY . /app/
371
+ """
372
+ with open("Dockerfile", "w", encoding="utf-8") as f:
373
+ f.write(dockerfile_content)
374
+
375
+ docker_compose_content = f"""version: '3.8'
376
+
377
+ services:
378
+ db:
379
+ image: postgres:15
380
+ volumes:
381
+ - postgres_data:/var/lib/postgresql/data
382
+ environment:
383
+ - POSTGRES_DB={name}_db
384
+ - POSTGRES_USER={name}_user
385
+ - POSTGRES_PASSWORD={name}@1234!
386
+ ports:
387
+ - "5432:5432"
388
+
389
+ redis:
390
+ image: redis:alpine
391
+ ports:
392
+ - "6379:6379"
393
+
394
+ web:
395
+ build: .
396
+ command: daphne -b 0.0.0.0 -p 8000 {name}.asgi:application
397
+ volumes:
398
+ - .:/app
399
+ ports:
400
+ - "8000:8000"
401
+ env_file:
402
+ - .env
403
+ environment:
404
+ - DB_HOST=db
405
+ - REDIS_HOST=redis
406
+ - REDIS_URL=redis://redis:6379/1
407
+ - CELERY_BROKER_URL=redis://redis:6379/0
408
+ - CELERY_RESULT_BACKEND=redis://redis:6379/0
409
+ depends_on:
410
+ - db
411
+ - redis
412
+
413
+ celery:
414
+ build: .
415
+ command: celery -A {name} worker -l info
416
+ volumes:
417
+ - .:/app
418
+ env_file:
419
+ - .env
420
+ environment:
421
+ - DB_HOST=db
422
+ - REDIS_HOST=redis
423
+ - REDIS_URL=redis://redis:6379/1
424
+ - CELERY_BROKER_URL=redis://redis:6379/0
425
+ - CELERY_RESULT_BACKEND=redis://redis:6379/0
426
+ depends_on:
427
+ - db
428
+ - redis
429
+ - web
430
+
431
+ volumes:
432
+ postgres_data:
433
+ """
434
+ with open("docker-compose.yml", "w", encoding="utf-8") as f:
435
+ f.write(docker_compose_content)
436
+
437
+ dockerignore_content = """.env
438
+ .venv
439
+ venv/
440
+ __pycache__/
441
+ *.pyc
442
+ db.sqlite3
443
+ media/
444
+ static/
445
+ """
446
+ with open(".dockerignore", "w", encoding="utf-8") as f:
447
+ f.write(dockerignore_content)
448
+
449
+
450
+ def freeze_requirements():
451
+ print("[cyan]📄 Freezing requirements...[/cyan]")
452
+ result = subprocess.run(
453
+ [sys.executable, "-m", "pip", "freeze", "--local"],
454
+ capture_output=True, text=True
455
+ )
456
+ with open("requirements.txt", "w", encoding="utf-8") as f:
457
+ f.write(result.stdout)
458
+ print("[green]✔ requirements.txt created.[/green]")
459
+
460
+
461
+ def create_utils_file(name: str):
462
+ utils_content = """from rest_framework.views import exception_handler
463
+
464
+ # Custom Message
465
+ def custom_exception_handler(exc, context):
466
+ \"\"\"
467
+ Global DRF exception handler.
468
+ Always return only first error message.
469
+ \"\"\"
470
+
471
+ response = exception_handler(exc, context)
472
+
473
+ if response is None:
474
+ return response
475
+
476
+ data = response.data
477
+
478
+ # Case 1: {"detail": "..."}
479
+ if isinstance(data, dict) and "detail" in data:
480
+ message = data["detail"]
481
+
482
+ # Case 2: serializer errors {"field": ["error"]}
483
+ elif isinstance(data, dict):
484
+ first_error = list(data.values())[0]
485
+
486
+ if isinstance(first_error, list):
487
+ message = first_error[0]
488
+ else:
489
+ message = first_error
490
+
491
+ # Case 3: ["error"]
492
+ elif isinstance(data, list):
493
+ message = data[0]
494
+
495
+ else:
496
+ message = "Something went wrong."
497
+
498
+ # Clean up technical messages
499
+ message = str(message)
500
+ if "JSON parse error" in message:
501
+ message = "Invalid JSON format in request body."
502
+
503
+ # Standardize response to include success: false
504
+ response.data = {
505
+ "success": False,
506
+ "message": message
507
+ }
508
+
509
+ return response
510
+ """
511
+ with open(f"{name}/utils.py", "w", encoding="utf-8") as f:
512
+ f.write(utils_content)
513
+
514
+
515
+ def create_celery_file(name: str):
516
+ celery_content = f"""import os
517
+ from celery import Celery
518
+
519
+ # Set the default Django settings module for the 'celery' program.
520
+ os.environ.setdefault('DJANGO_SETTINGS_MODULE', '{name}.settings')
521
+
522
+ app = Celery('{name}')
523
+
524
+ # Using a string here means the worker doesn't have to serialize
525
+ # the configuration object to child processes.
526
+ # - namespace='CELERY' means all celery-related configuration keys
527
+ # should have a `CELERY_` prefix.
528
+ app.config_from_object('django.conf:settings', namespace='CELERY')
529
+
530
+ # Load task modules from all registered Django apps.
531
+ app.autodiscover_tasks()
532
+
533
+ @app.task(bind=True, ignore_result=True)
534
+ def debug_task(self):
535
+ print(f'Request: {{self.request!r}}')
536
+ """
537
+ with open(f"{name}/celery.py", "w", encoding="utf-8") as f:
538
+ f.write(celery_content)
539
+
540
+
541
+ def update_init_file(name: str):
542
+ init_content = """from .celery import app as celery_app
543
+
544
+ __all__ = ('celery_app',)
545
+ """
546
+ with open(f"{name}/__init__.py", "w", encoding="utf-8") as f:
547
+ f.write(init_content)
548
+
549
+
550
+ def update_urls_file(name: str):
551
+ urls_content = """from django.contrib import admin
552
+ from django.urls import path, include
553
+ from django.conf.urls.static import static
554
+ from django.conf import settings
555
+ from django.http import JsonResponse
556
+ from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView
557
+
558
+
559
+ # ── Root / Health-check endpoint ──────────────────────────────────────────────
560
+ def root_view(request):
561
+ return JsonResponse({
562
+ "message": "API Server Running",
563
+ "status": "ok"
564
+ })
565
+
566
+
567
+ # ── URL Patterns ──────────────────────────────────────────────────────────────
568
+ urlpatterns = [
569
+ # Root / Health-check
570
+ path('', root_view, name='home'),
571
+
572
+ # Django Admin Panel
573
+ # Optional: Change 'admin/' to 'em-secure-admin/' in production for security
574
+ path('admin/', admin.site.urls),
575
+
576
+ # API Documentation
577
+ path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
578
+ path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
579
+ path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),
580
+
581
+ ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
582
+ """
583
+ with open(f"{name}/urls.py", "w", encoding="utf-8") as f:
584
+ f.write(urls_content)
585
+
586
+
587
+ def generate_gitignore():
588
+ gitignore_content = """# ===========================
589
+ # Python / Django / Virtual Env
590
+ # ===========================
591
+ __pycache__/
592
+ *.py[cod]
593
+ *$py.class
594
+ *.so
595
+
596
+ env/
597
+ venv/
598
+ ENV/
599
+ .venv/
600
+ pyvenv.cfg
601
+
602
+ *.log
603
+ *.pot
604
+ *.pyc
605
+ *.pyo
606
+ *.pyd
607
+
608
+ # ===========================
609
+ # Database (PostgreSQL / MySQL)
610
+ # ===========================
611
+ *.dump
612
+ *.sql
613
+ *.backup
614
+ backups/
615
+ dumps/
616
+ db.sqlite3
617
+ db.sqlite3-journal
618
+ *.sqlite3
619
+ *.db
620
+
621
+ # ===========================
622
+ # Django migrations (optional)
623
+ # ===========================
624
+ # Ignore migrations if you handle them via CI/CD
625
+ # */migrations/*.py
626
+ # */migrations/*.pyc
627
+ # !*/migrations/__init__.py
628
+
629
+ # ===========================
630
+ # Static files / Media
631
+ # ===========================
632
+ /static/
633
+ /staticfiles/
634
+ /media/
635
+ /mediafiles/
636
+ /assets/
637
+
638
+ # ===========================
639
+ # Secrets / Environment variables
640
+ # ===========================
641
+ .env
642
+ .env.*
643
+ secret_key.txt
644
+ .secret_key
645
+ *.key
646
+ *.pem
647
+ *.crt
648
+ *.cer
649
+ credentials.json
650
+ secrets.json
651
+
652
+ # ===========================
653
+ # Caches / Temp
654
+ # ===========================
655
+ .cache/
656
+ .pytest_cache/
657
+ .mypy_cache/
658
+ django_cache/
659
+ tmp/
660
+ temp/
661
+
662
+ # ===========================
663
+ # Celery
664
+ # ===========================
665
+ celerybeat-schedule*
666
+ celerybeat-schedule
667
+ celerybeat-schedule-shm
668
+ celerybeat-schedule-wal
669
+ *.pid
670
+
671
+ # ===========================
672
+ # IDEs / Editors
673
+ # ===========================
674
+ .vscode/
675
+ .idea/
676
+ *.iml
677
+ *.sublime-project
678
+ *.sublime-workspace
679
+ *.swp
680
+ *~
681
+
682
+ # ===========================
683
+ # OS / System
684
+ # ===========================
685
+ .DS_Store
686
+ Thumbs.db
687
+ *.bak
688
+ *.orig
689
+ *.rej
690
+
691
+ # ===========================
692
+ # Docker
693
+ # ===========================
694
+ .docker/
695
+ docker-compose.override.yml
696
+
697
+ # ===========================
698
+ # Node / Frontend
699
+ # ===========================
700
+ node_modules/
701
+ dist/
702
+ build/
703
+ *.map
704
+
705
+ # ===========================
706
+ # Testing / Coverage
707
+ # ===========================
708
+ .coverage
709
+ htmlcov/
710
+ .tox/
711
+ .nox/
712
+ """
713
+ with open(".gitignore", "w", encoding="utf-8") as f:
714
+ f.write(gitignore_content)
715
+
716
+
717
+ def generate_pytest_ini(name: str):
718
+ content = f"""[pytest]
719
+ DJANGO_SETTINGS_MODULE = {name}.settings
720
+ python_files = tests.py test_*.py *_tests.py
721
+ addopts = --cov=. --cov-report=html
722
+ """
723
+ with open("pytest.ini", "w", encoding="utf-8") as f:
724
+ f.write(content)
725
+
726
+
727
+ def generate_pre_commit_config():
728
+ content = """repos:
729
+ - repo: https://github.com/pre-commit/pre-commit-hooks
730
+ rev: v4.4.0
731
+ hooks:
732
+ - id: trailing-whitespace
733
+ - id: end-of-file-fixer
734
+ - id: check-yaml
735
+ - repo: https://github.com/psf/black
736
+ rev: 23.3.0
737
+ hooks:
738
+ - id: black
739
+ - repo: https://github.com/PyCQA/isort
740
+ rev: 5.12.0
741
+ hooks:
742
+ - id: isort
743
+ - repo: https://github.com/PyCQA/flake8
744
+ rev: 6.0.0
745
+ hooks:
746
+ - id: flake8
747
+ """
748
+ with open(".pre-commit-config.yaml", "w", encoding="utf-8") as f:
749
+ f.write(content)
750
+
751
+
752
+ def generate_github_actions():
753
+ os.makedirs(".github/workflows", exist_ok=True)
754
+ content = """name: Django CI
755
+
756
+ on:
757
+ push:
758
+ branches: [ "main" ]
759
+ pull_request:
760
+ branches: [ "main" ]
761
+
762
+ jobs:
763
+ build:
764
+ runs-on: ubuntu-latest
765
+ steps:
766
+ - uses: actions/checkout@v3
767
+ - name: Set up Python 3.11
768
+ uses: actions/setup-python@v3
769
+ with:
770
+ python-version: "3.11"
771
+ - name: Install dependencies
772
+ run: |
773
+ python -m pip install --upgrade pip
774
+ pip install -r requirements.txt
775
+ - name: Lint with flake8
776
+ run: |
777
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
778
+ - name: Run Tests
779
+ run: |
780
+ pytest
781
+ """
782
+ with open(".github/workflows/main.yml", "w", encoding="utf-8") as f:
783
+ f.write(content)
784
+
785
+
786
+ def generate_gitlab_ci():
787
+ content = """image: python:3.11-slim
788
+
789
+ stages:
790
+ - test
791
+
792
+ variables:
793
+ PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
794
+
795
+ cache:
796
+ paths:
797
+ - .cache/pip
798
+
799
+ test_project:
800
+ stage: test
801
+ before_script:
802
+ - python -m pip install --upgrade pip
803
+ - pip install -r requirements.txt
804
+ script:
805
+ - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
806
+ - pytest
807
+ """
808
+ with open(".gitlab-ci.yml", "w", encoding="utf-8") as f:
809
+ f.write(content)
810
+
811
+
812
+ def create_project(name: str):
813
+ check_virtual_environment()
814
+ validate_name(name, "project name")
815
+
816
+ # Check if project already exists
817
+ if Path(name).exists():
818
+ print(f"[red]Error: A directory named '{name}' already exists. Choose a different name or delete it first.[/red]")
819
+ import typer
820
+ raise typer.Exit(1)
821
+
822
+ # Check if current directory already has a manage.py (already a Django project)
823
+ if Path("manage.py").exists():
824
+ print("[red]Error: manage.py already exists here. Are you already inside a Django project?[/red]")
825
+ import typer
826
+ raise typer.Exit(1)
827
+
828
+ print(f"[green]🚀 Creating Django project: {name}[/green]")
829
+
830
+ # Install Django first so django-admin is available
831
+ print("[cyan]📦 Installing Django first...[/cyan]")
832
+ result = subprocess.run(
833
+ [sys.executable, "-m", "pip", "install", "Django"],
834
+ capture_output=True, text=True
835
+ )
836
+ if result.returncode != 0:
837
+ print(f"[red]Failed to install Django:\n{result.stderr}[/red]")
838
+ import typer
839
+ raise typer.Exit(1)
840
+
841
+ result = subprocess.run(
842
+ [sys.executable, "-m", "django", "startproject", name, "."],
843
+ capture_output=True, text=True
844
+ )
845
+ if result.returncode != 0:
846
+ print(f"[red]Failed to create Django project:\n{result.stderr}[/red]")
847
+ import typer
848
+ raise typer.Exit(1)
849
+
850
+ secret_key = update_settings_file(f"{name}/settings.py", name)
851
+ create_utils_file(name)
852
+ create_celery_file(name)
853
+ update_init_file(name)
854
+ update_urls_file(name)
855
+ create_directories()
856
+ install_dependencies()
857
+ generate_env_file(secret_key, name)
858
+ freeze_requirements()
859
+ generate_docker_files(name)
860
+ generate_gitignore()
861
+ generate_pytest_ini(name)
862
+ generate_pre_commit_config()
863
+
864
+ # Initialize git and install pre-commit
865
+ try:
866
+ subprocess.run(["git", "init"], check=True, capture_output=True)
867
+ subprocess.run(
868
+ [sys.executable, "-m", "pre_commit", "install"],
869
+ check=True, capture_output=True
870
+ )
871
+ except Exception:
872
+ pass
873
+
874
+ print()
875
+ print(f"[bold green]✅ Project '{name}' created successfully![/bold green]")
876
+ print()
877
+ print("[cyan]Next steps:[/cyan]")
878
+ print(f" 1. Update your [bold].env[/bold] file with DB credentials")
879
+ print(f" 2. Run [bold]python manage.py migrate[/bold]")
880
+ print(f" 3. Run [bold]python manage.py runserver[/bold]")
@@ -0,0 +1,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: djboost
3
+ Version: 0.1.0
4
+ Summary: Django project generator CLI — production-ready Django projects in one command
5
+ Author: Munjur Alom
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/munjurdev/djboost
8
+ Project-URL: Repository, https://github.com/munjurdev/djboost
9
+ Project-URL: Bug Tracker, https://github.com/munjurdev/djboost/issues
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Environment :: Console
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Requires-Dist: typer>=0.9.0
23
+ Requires-Dist: rich>=13.0.0
24
+ Dynamic: license-file
25
+
26
+ # djboost 🚀
27
+
28
+ `djboost` is a CLI tool that generates a fully-configured, production-ready Django project in one command. No more repetitive boilerplate setup.
29
+
30
+ With a single command you get Django REST Framework, JWT Authentication, Celery, Redis, WebSockets (Channels), Docker, Swagger docs, and more — all pre-wired and ready to go.
31
+
32
+ ## ✨ Features
33
+
34
+ - **API Ready** — Django REST Framework + Simple JWT pre-configured
35
+ - **API Docs** — Auto-generated Swagger UI and ReDoc via `drf-spectacular`
36
+ - **Async Tasks** — Celery + Redis integrated out of the box
37
+ - **WebSockets** — Django Channels with Daphne and Redis channel layers
38
+ - **Environment Variables** — `python-decouple` with a generated `.env` file
39
+ - **Database** — Pre-configured for PostgreSQL (SQLite default for dev)
40
+ - **CORS & Security** — `django-cors-headers` + standard security headers
41
+ - **Docker** — Ready-to-use `Dockerfile` and `docker-compose.yml`
42
+ - **Static Files** — Whitenoise configured for efficient static file serving
43
+ - **Code Quality** — `pre-commit` with `black`, `flake8`, and `isort`
44
+ - **Testing** — `pytest` + `pytest-django` with coverage pre-configured
45
+ - **CI/CD** — Modular GitHub Actions and GitLab CI pipelines
46
+ - **Custom Exception Handling** — Global DRF exception handler included
47
+
48
+ ---
49
+
50
+ ## � Installation
51
+
52
+ ```bash
53
+ pip install djboost
54
+ ```
55
+
56
+ ---
57
+
58
+ ## 🚀 Quick Start
59
+
60
+ ### Step 1 — Create a virtual environment and activate it
61
+
62
+ ```bash
63
+ python -m venv env
64
+
65
+ # Windows
66
+ env\Scripts\activate
67
+
68
+ # Mac / Linux
69
+ source env/bin/activate
70
+ ```
71
+
72
+ ### Step 2 — Install djboost
73
+
74
+ ```bash
75
+ pip install djboost
76
+ ```
77
+
78
+ ### Step 3 — Navigate to an empty folder and create your project
79
+
80
+ ```bash
81
+ # Create a project with a custom name
82
+ djboost create project myproject
83
+
84
+ # Or use the default name 'core'
85
+ djboost create project
86
+ ```
87
+
88
+ This single command will automatically:
89
+
90
+ 1. Install Django and run `startproject`
91
+ 2. Update `settings.py` with all advanced configurations
92
+ 3. Generate `.env`, `Dockerfile`, `docker-compose.yml`, and `.gitignore`
93
+ 4. Generate `pytest.ini` and `.pre-commit-config.yaml`
94
+ 5. Create `/apps`, `/static`, and `/media` directories
95
+ 6. Install all required dependencies
96
+ 7. Initialize a `git` repository and set up `pre-commit` hooks
97
+ 8. Freeze dependencies into `requirements.txt`
98
+
99
+ ---
100
+
101
+ ## 🧱 Creating Apps
102
+
103
+ Apps are created inside the `apps/` directory to keep your project root clean. Settings and URLs are auto-configured.
104
+
105
+ ```bash
106
+ # Navigate to your project root first
107
+ cd myproject
108
+
109
+ # Create a new app
110
+ djboost create app users
111
+ ```
112
+
113
+ This will:
114
+ - Create `apps/users/` with standard Django app structure
115
+ - Auto-add `'apps.users'` to `INSTALLED_APPS`
116
+ - Auto-map `api/users/` in `urls.py`
117
+ - Create a starter `urls.py` inside the app
118
+
119
+ ---
120
+
121
+ ## � Managing CI/CD Pipelines
122
+
123
+ CI/CD is modular — add or remove it any time after project creation.
124
+
125
+ ### Add a pipeline
126
+
127
+ ```bash
128
+ djboost add cicd github # GitHub Actions
129
+ djboost add cicd gitlab # GitLab CI
130
+ ```
131
+
132
+ ### Remove a pipeline
133
+
134
+ ```bash
135
+ djboost remove cicd github
136
+ djboost remove cicd gitlab
137
+ ```
138
+
139
+ ---
140
+
141
+ ## 🏃 Running Your Project
142
+
143
+ ### Locally
144
+
145
+ ```bash
146
+ # Apply migrations
147
+ python manage.py migrate
148
+
149
+ # Start the dev server
150
+ python manage.py runserver
151
+ ```
152
+
153
+ ### API Documentation
154
+
155
+ Once your server is running:
156
+
157
+ | Interface | URL |
158
+ |---|---|
159
+ | Swagger UI | `http://127.0.0.1:8000/api/schema/swagger-ui/` |
160
+ | ReDoc | `http://127.0.0.1:8000/api/schema/redoc/` |
161
+ | OpenAPI Schema | `http://127.0.0.1:8000/api/schema/` |
162
+
163
+ ### Testing
164
+
165
+ ```bash
166
+ pytest
167
+ ```
168
+
169
+ ### With Docker
170
+
171
+ ```bash
172
+ docker-compose up --build
173
+ ```
174
+
175
+ This spins up PostgreSQL, Redis, Celery worker, and a Daphne ASGI server together.
176
+
177
+ ---
178
+
179
+ ## ⚙️ CLI Reference
180
+
181
+ ```
182
+ djboost --help
183
+ djboost --version
184
+
185
+ djboost create project [NAME] Create a new Django project
186
+ djboost create app NAME Create a new app inside apps/
187
+
188
+ djboost add cicd github|gitlab Add a CI/CD pipeline
189
+ djboost remove cicd github|gitlab Remove a CI/CD pipeline
190
+ ```
191
+
192
+ ---
193
+
194
+ ## 🐍 Requirements
195
+
196
+ - Python 3.10+
197
+ - Virtual environment (required — djboost will warn you if not activated)
198
+
199
+ ---
200
+
201
+ ## 📄 License
202
+
203
+ MIT
@@ -0,0 +1,14 @@
1
+ djboost/__init__.py,sha256=dP-7015-lW7jRYSq9V1Gamk6IBR_QApOw1KMCvi7WcE,8
2
+ djboost/cli.py,sha256=iQaCfkNuRKqQQMZ3m4WmUbasNJNrOXmvUFPxHa3AhiU,1159
3
+ djboost/generator.py,sha256=HJucjKldl_qSJjEYEDUT4DLnQ6JXAl6otqmnIus9Iik,26048
4
+ djboost/commands/__init__.py,sha256=dP-7015-lW7jRYSq9V1Gamk6IBR_QApOw1KMCvi7WcE,8
5
+ djboost/commands/add_cicd.py,sha256=I28rZcuZ8ZOb67w7SP7m4wOyYeG8RW181U_POVan768,759
6
+ djboost/commands/create_app.py,sha256=rr4kpm6iNq-uJcHH-6WZ5QozO-ryddQIhGWClqrdX9Q,4693
7
+ djboost/commands/create_project.py,sha256=FcNODKMGlIKYrsce94u58rRhkYkV4xZsclfNp6o2NI0,192
8
+ djboost/commands/remove_cicd.py,sha256=arkMkZUHit4YcDTDMV7BT3YoSCC3WmG7vMVYx7BVLRs,1447
9
+ djboost-0.1.0.dist-info/licenses/LICENSE,sha256=y5cRw4FX_ySuzWEy0aq14MhDvK-cKxrNsCHvkmoQ2CQ,1087
10
+ djboost-0.1.0.dist-info/METADATA,sha256=Q4701Sgx04kSsqU8LoUbENA3UhtHrUzD3eLR-97h5AY,5280
11
+ djboost-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
12
+ djboost-0.1.0.dist-info/entry_points.txt,sha256=i_aCUH4pIdn_LZDe5SQZATzpaj4pWAPkmZjA17mVL-I,44
13
+ djboost-0.1.0.dist-info/top_level.txt,sha256=mLgii1ayliYMsRglYvmbFtc-PVL2pcaDKqcmFKVkrxQ,8
14
+ djboost-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ djboost = djboost.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 munjurdev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ djboost