nextpy-framework 2.3.0__tar.gz → 2.4.0__tar.gz
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.
- nextpy_framework-2.4.0/.nextpy_framework/nextpy/cli.py +2421 -0
- nextpy_framework-2.4.0/.nextpy_framework/nextpy/config.py +68 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/core/component_renderer.py +3 -1
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/core/component_router.py +4 -2
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/main.py +5 -3
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/plugins/builtin.py +5 -4
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/server/app.py +21 -15
- nextpy_framework-2.4.0/.nextpy_framework/nextpy_framework.egg-info/PKG-INFO +1574 -0
- nextpy_framework-2.4.0/PKG-INFO +1574 -0
- nextpy_framework-2.4.0/README.md +1533 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/pyproject.toml +1 -1
- nextpy_framework-2.3.0/.nextpy_framework/nextpy/cli.py +0 -1254
- nextpy_framework-2.3.0/.nextpy_framework/nextpy/config.py +0 -75
- nextpy_framework-2.3.0/.nextpy_framework/nextpy_framework.egg-info/PKG-INFO +0 -999
- nextpy_framework-2.3.0/PKG-INFO +0 -999
- nextpy_framework-2.3.0/README.md +0 -958
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/__init__.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/auth.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/builder.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/__init__.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/debug/AutoDebug.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/debug/DebugIcon.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/debug/DebugIconFixed.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/feedback.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/form.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/head.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/hooks_provider.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/image.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/layout.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/link.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/loader.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/navigation.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/toast.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/ui.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components/visual.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/components.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/core/__init__.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/core/builder.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/core/data_fetching.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/core/demo_pages_simple.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/core/demo_router.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/core/renderer.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/core/router.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/core/sync.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/db.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/dev_server.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/dev_tools.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/errors.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/hooks.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/hooks_provider.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/hooks_provider_new.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/jsx.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/jsx_preprocessor.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/jsx_transformer.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/performance.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/plugins/__init__.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/plugins/base.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/plugins/config.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/plugins.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/py.typed +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/security.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/server/__init__.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/server/debug.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/server/middleware.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/true_jsx.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/utils/__init__.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/utils/cache.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/utils/email.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/utils/file_upload.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/utils/logging.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/utils/search.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/utils/seo.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/utils/validators.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy/websocket.py +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy_framework.egg-info/SOURCES.txt +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy_framework.egg-info/dependency_links.txt +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy_framework.egg-info/entry_points.txt +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy_framework.egg-info/requires.txt +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/.nextpy_framework/nextpy_framework.egg-info/top_level.txt +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/LICENSE +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/setup.cfg +0 -0
- {nextpy_framework-2.3.0 → nextpy_framework-2.4.0}/tests/test_routing.py +0 -0
|
@@ -0,0 +1,2421 @@
|
|
|
1
|
+
"""
|
|
2
|
+
NextPy CLI - Command-line interface for NextPy projects
|
|
3
|
+
Commands: dev, build, start
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
import asyncio
|
|
10
|
+
import signal
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
import click
|
|
15
|
+
import uvicorn
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
from watchdog.observers import Observer
|
|
19
|
+
from watchdog.events import FileSystemEventHandler
|
|
20
|
+
WATCHDOG_AVAILABLE = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
WATCHDOG_AVAILABLE = False
|
|
23
|
+
Observer = None
|
|
24
|
+
FileSystemEventHandler = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class HotReloadHandler:
|
|
28
|
+
"""Handles file system changes for hot reload with enhanced JSX support"""
|
|
29
|
+
|
|
30
|
+
def __init__(self, reload_callback, debug: bool = False):
|
|
31
|
+
self.reload_callback = reload_callback
|
|
32
|
+
self._debounce_timer = None
|
|
33
|
+
self.debug = debug
|
|
34
|
+
self.last_reload_time = 0
|
|
35
|
+
self.reload_cooldown = 0.5 # 500ms cooldown between reloads
|
|
36
|
+
|
|
37
|
+
# Enhanced file patterns for better JSX detection
|
|
38
|
+
self.file_patterns = {
|
|
39
|
+
"python": [".py"],
|
|
40
|
+
"jsx": [".py.jsx", ".jsx"],
|
|
41
|
+
"templates": [".html", ".htm", ".jinja2", ".j2"],
|
|
42
|
+
"styles": [".css", ".scss", ".sass", ".less"],
|
|
43
|
+
"scripts": [".js", ".ts", ".mjs", ".cjs"],
|
|
44
|
+
"assets": [".json", ".yaml", ".yml", ".toml", ".ini"],
|
|
45
|
+
"config": [".env", ".env.example", "requirements.txt", "package.json", "tailwind.config.js", "postcss.config.js"]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Directories to watch
|
|
49
|
+
self.watch_dirs = {
|
|
50
|
+
"pages", "components", "templates", "public",
|
|
51
|
+
"static", "assets", "styles", "scripts", ".nextpy_framework"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Files that should always trigger reload
|
|
55
|
+
self.critical_files = {
|
|
56
|
+
"main.py", "app.py", "config.py", "settings.py",
|
|
57
|
+
"requirements.txt", "package.json", "pyproject.toml"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
def _should_reload_file(self, file_path: str) -> bool:
|
|
61
|
+
"""Determine if a file change should trigger reload"""
|
|
62
|
+
file_path = Path(file_path)
|
|
63
|
+
|
|
64
|
+
# Always reload critical files
|
|
65
|
+
if file_path.name in self.critical_files:
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
# Check if file is in a watched directory
|
|
69
|
+
parent_dirs = [part.name for part in file_path.parents]
|
|
70
|
+
if not any(dir_name in self.watch_dirs for dir_name in parent_dirs):
|
|
71
|
+
# If not in watched directories, check if it's in root
|
|
72
|
+
if len(parent_dirs) == 0 or parent_dirs[-1] == ".":
|
|
73
|
+
return any(file_path.name.endswith(pattern) for patterns in self.file_patterns.values() for pattern in patterns)
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
# Check file extension against all patterns
|
|
77
|
+
all_extensions = []
|
|
78
|
+
for patterns in self.file_patterns.values():
|
|
79
|
+
all_extensions.extend(patterns)
|
|
80
|
+
|
|
81
|
+
return any(file_path.name.endswith(ext) for ext in all_extensions)
|
|
82
|
+
|
|
83
|
+
def _get_file_type(self, file_path: str) -> str:
|
|
84
|
+
"""Categorize file type for logging"""
|
|
85
|
+
file_path = Path(file_path)
|
|
86
|
+
|
|
87
|
+
for file_type, extensions in self.file_patterns.items():
|
|
88
|
+
if any(file_path.name.endswith(ext) for ext in extensions):
|
|
89
|
+
return file_type
|
|
90
|
+
|
|
91
|
+
return "unknown"
|
|
92
|
+
|
|
93
|
+
def _debounce_reload(self, file_path: str = None):
|
|
94
|
+
"""Debounce reload calls to prevent excessive reloading"""
|
|
95
|
+
current_time = time.time()
|
|
96
|
+
|
|
97
|
+
if current_time - self.last_reload_time < self.reload_cooldown:
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
self.last_reload_time = current_time
|
|
101
|
+
|
|
102
|
+
if self.debug and file_path:
|
|
103
|
+
file_type = self._get_file_type(file_path)
|
|
104
|
+
click.echo(f" 🔄 Hot reload triggered by {file_type} file: {Path(file_path).name}", dim=True)
|
|
105
|
+
|
|
106
|
+
self._trigger_reload()
|
|
107
|
+
|
|
108
|
+
def on_modified(self, event):
|
|
109
|
+
"""Handle file modification events"""
|
|
110
|
+
if event.is_directory:
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
if self._should_reload_file(event.src_path):
|
|
114
|
+
self._debounce_reload(event.src_path)
|
|
115
|
+
|
|
116
|
+
def on_created(self, event):
|
|
117
|
+
"""Handle file creation events"""
|
|
118
|
+
if not event.is_directory and self._should_reload_file(event.src_path):
|
|
119
|
+
self._debounce_reload(event.src_path)
|
|
120
|
+
|
|
121
|
+
def on_deleted(self, event):
|
|
122
|
+
"""Handle file deletion events"""
|
|
123
|
+
if not event.is_directory and self._should_reload_file(event.src_path):
|
|
124
|
+
self._debounce_reload(event.src_path)
|
|
125
|
+
|
|
126
|
+
def on_moved(self, event):
|
|
127
|
+
"""Handle file move/rename events"""
|
|
128
|
+
if not event.is_directory:
|
|
129
|
+
# Handle both source and destination
|
|
130
|
+
if hasattr(event, 'dest_path') and event.dest_path:
|
|
131
|
+
if self._should_reload_file(event.src_path) or self._should_reload_file(event.dest_path):
|
|
132
|
+
self._debounce_reload(event.dest_path or event.src_path)
|
|
133
|
+
else:
|
|
134
|
+
if self._should_reload_file(event.src_path):
|
|
135
|
+
self._debounce_reload(event.src_path)
|
|
136
|
+
|
|
137
|
+
def _trigger_reload(self):
|
|
138
|
+
"""Trigger the reload callback"""
|
|
139
|
+
if self.reload_callback:
|
|
140
|
+
self.reload_callback()
|
|
141
|
+
|
|
142
|
+
def setup_file_watcher(self, project_dir: str = "."):
|
|
143
|
+
"""Setup enhanced file watcher with specific patterns"""
|
|
144
|
+
if not WATCHDOG_AVAILABLE:
|
|
145
|
+
click.echo(" ⚠️ Watchdog not installed. Hot reload disabled.", fg="yellow")
|
|
146
|
+
click.echo(" Install with: pip install watchdog", fg="yellow")
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
observer = Observer()
|
|
150
|
+
event_handler = WatchdogHotReloadHandler(self._trigger_reload, debug=self.debug)
|
|
151
|
+
|
|
152
|
+
# Watch specific directories with recursive monitoring
|
|
153
|
+
for watch_dir in self.watch_dirs:
|
|
154
|
+
dir_path = Path(project_dir) / watch_dir
|
|
155
|
+
if dir_path.exists():
|
|
156
|
+
observer.schedule(event_handler, str(dir_path), recursive=True)
|
|
157
|
+
if self.debug:
|
|
158
|
+
click.echo(f" 📁 Watching directory: {watch_dir}", dim=True)
|
|
159
|
+
|
|
160
|
+
# Also watch root directory for critical files
|
|
161
|
+
root_path = Path(project_dir)
|
|
162
|
+
if root_path.exists():
|
|
163
|
+
observer.schedule(event_handler, str(root_path), recursive=False)
|
|
164
|
+
|
|
165
|
+
return observer
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
if WATCHDOG_AVAILABLE:
|
|
169
|
+
class WatchdogHotReloadHandler(HotReloadHandler, FileSystemEventHandler):
|
|
170
|
+
"""Enhanced watchdog handler with better file filtering"""
|
|
171
|
+
pass
|
|
172
|
+
else:
|
|
173
|
+
class WatchdogHotReloadHandler(HotReloadHandler):
|
|
174
|
+
"""Fallback handler when watchdog is not available"""
|
|
175
|
+
pass
|
|
176
|
+
|
|
177
|
+
def find_main_module():
|
|
178
|
+
# main.py is always expected at the project root for NextPy projects
|
|
179
|
+
# We ensure the current directory is in sys.path before calling uvicorn.run
|
|
180
|
+
return "main:app"
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _format_size(size_bytes: int) -> str:
|
|
184
|
+
"""Format file size in human-readable format"""
|
|
185
|
+
if size_bytes == 0:
|
|
186
|
+
return "0 B"
|
|
187
|
+
|
|
188
|
+
size_names = ["B", "KB", "MB", "GB", "TB"]
|
|
189
|
+
i = 0
|
|
190
|
+
size = float(size_bytes)
|
|
191
|
+
|
|
192
|
+
while size >= 1024.0 and i < len(size_names) - 1:
|
|
193
|
+
size /= 1024.0
|
|
194
|
+
i += 1
|
|
195
|
+
|
|
196
|
+
return f"{size:.1f} {size_names[i]}"
|
|
197
|
+
|
|
198
|
+
@click.group()
|
|
199
|
+
@click.version_option(version="1.0.0", prog_name="NextPy")
|
|
200
|
+
def cli():
|
|
201
|
+
"""NextPy - A Python web framework inspired by Next.js"""
|
|
202
|
+
pass
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@cli.command()
|
|
206
|
+
@click.option("--port", "-p", default=5000, help="Port to run the server on")
|
|
207
|
+
@click.option("--host", "-h", default="0.0.0.0", help="Host to bind to")
|
|
208
|
+
@click.option("--reload/--no-reload", default=True, help="Enable hot reload")
|
|
209
|
+
@click.option("--debug/--no-debug", default=True, help="Enable debug mode")
|
|
210
|
+
def dev(port: int, host: str, reload: bool, debug: bool):
|
|
211
|
+
"""Start the development server with enhanced hot reload"""
|
|
212
|
+
click.echo(click.style("\n NextPy Development Server", fg="cyan", bold=True))
|
|
213
|
+
click.echo(click.style(" ========================\n", fg="cyan"))
|
|
214
|
+
|
|
215
|
+
# Set debug environment variable
|
|
216
|
+
if debug:
|
|
217
|
+
os.environ["NEXTPY_DEBUG"] = "true"
|
|
218
|
+
os.environ["DEBUG"] = "true"
|
|
219
|
+
os.environ["DEVELOPMENT"] = "true"
|
|
220
|
+
else:
|
|
221
|
+
os.environ.pop("NEXTPY_DEBUG", None)
|
|
222
|
+
os.environ.pop("DEBUG", None)
|
|
223
|
+
os.environ.pop("DEVELOPMENT", None)
|
|
224
|
+
|
|
225
|
+
_ensure_project_structure()
|
|
226
|
+
|
|
227
|
+
click.echo(f" - Mode: {'Development' if debug else 'Production'}")
|
|
228
|
+
click.echo(f" - Host: {host} (accessible at http://localhost:{port})")
|
|
229
|
+
click.echo(f" - Port: {port}")
|
|
230
|
+
click.echo(f" - Reload: {'Enabled' if reload else 'Disabled'}")
|
|
231
|
+
click.echo(f" - Debug: {'Enabled' if debug else 'Disabled'}")
|
|
232
|
+
|
|
233
|
+
if reload and not WATCHDOG_AVAILABLE:
|
|
234
|
+
click.echo(click.style(" - Watchdog: Not Available (install: pip install watchdog)", fg="yellow"))
|
|
235
|
+
elif reload:
|
|
236
|
+
click.echo(f" - Watchdog: Available")
|
|
237
|
+
|
|
238
|
+
if debug:
|
|
239
|
+
click.echo(f" - Debug Icon: ✅ Auto-enabled")
|
|
240
|
+
click.echo(f" - Console Capture: ✅ Enabled")
|
|
241
|
+
click.echo(f" - Performance Monitoring: ✅ Enabled")
|
|
242
|
+
|
|
243
|
+
click.echo(f"\n ✨ Server ready at http://0.0.0.0:{port}")
|
|
244
|
+
click.echo(f" 🌐 Open http://localhost:{port} in your browser\n")
|
|
245
|
+
|
|
246
|
+
project_dir = Path('.')
|
|
247
|
+
os.chdir(project_dir)
|
|
248
|
+
|
|
249
|
+
# Ensure the current directory is in sys.path for module discovery
|
|
250
|
+
if str(project_dir.resolve()) not in sys.path:
|
|
251
|
+
sys.path.insert(0, str(project_dir.resolve()))
|
|
252
|
+
|
|
253
|
+
main_module = find_main_module()
|
|
254
|
+
|
|
255
|
+
if reload:
|
|
256
|
+
# Enhanced reload configuration with JSX support
|
|
257
|
+
reload_dirs = [
|
|
258
|
+
"pages",
|
|
259
|
+
"components",
|
|
260
|
+
"templates",
|
|
261
|
+
"public",
|
|
262
|
+
"static",
|
|
263
|
+
"styles",
|
|
264
|
+
"scripts",
|
|
265
|
+
".nextpy_framework"
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
# Filter to only existing directories
|
|
269
|
+
existing_reload_dirs = []
|
|
270
|
+
for reload_dir in reload_dirs:
|
|
271
|
+
dir_path = project_dir / reload_dir
|
|
272
|
+
if dir_path.exists():
|
|
273
|
+
existing_reload_dirs.append(reload_dir)
|
|
274
|
+
if debug:
|
|
275
|
+
click.echo(click.style(f" 📁 Watching: {reload_dir}/"))
|
|
276
|
+
|
|
277
|
+
# Enhanced reload patterns for JSX files
|
|
278
|
+
reload_includes = [
|
|
279
|
+
"*.py",
|
|
280
|
+
"*.py.jsx",
|
|
281
|
+
"*.jsx",
|
|
282
|
+
"*.html",
|
|
283
|
+
"*.htm",
|
|
284
|
+
"*.css",
|
|
285
|
+
"*.scss",
|
|
286
|
+
"*.sass",
|
|
287
|
+
"*.less",
|
|
288
|
+
"*.js",
|
|
289
|
+
"*.ts",
|
|
290
|
+
"*.json",
|
|
291
|
+
"*.yaml",
|
|
292
|
+
"*.yml",
|
|
293
|
+
"*.env",
|
|
294
|
+
"requirements.txt",
|
|
295
|
+
"package.json",
|
|
296
|
+
"tailwind.config.js",
|
|
297
|
+
"postcss.config.js"
|
|
298
|
+
]
|
|
299
|
+
|
|
300
|
+
uvicorn.run(
|
|
301
|
+
main_module,
|
|
302
|
+
host=host,
|
|
303
|
+
port=port,
|
|
304
|
+
reload=True,
|
|
305
|
+
reload_dirs=existing_reload_dirs,
|
|
306
|
+
reload_includes=reload_includes,
|
|
307
|
+
log_level="info",
|
|
308
|
+
)
|
|
309
|
+
else:
|
|
310
|
+
uvicorn.run(
|
|
311
|
+
main_module,
|
|
312
|
+
host=host,
|
|
313
|
+
port=port,
|
|
314
|
+
log_level="info",
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
@cli.command()
|
|
319
|
+
@click.option("--out", "-o", default="out", help="Output directory for static files")
|
|
320
|
+
@click.option("--clean/--no-clean", default=True, help="Clean output directory first")
|
|
321
|
+
def build(out: str, clean: bool):
|
|
322
|
+
"""Build the project for production with enhanced feedback"""
|
|
323
|
+
click.echo(click.style("\n 🔨 NextPy Static Build", fg="green", bold=True))
|
|
324
|
+
click.echo(click.style(" ===================\n", fg="green"))
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
from nextpy.core.builder import Builder
|
|
328
|
+
|
|
329
|
+
click.echo(f" 📂 Output directory: {out}/")
|
|
330
|
+
if clean:
|
|
331
|
+
click.echo(f" 🧹 Cleaning output directory...")
|
|
332
|
+
|
|
333
|
+
click.echo(f" ⚙️ Initializing builder...")
|
|
334
|
+
builder = Builder(out_dir=out)
|
|
335
|
+
|
|
336
|
+
click.echo(f" 🏗️ Building static files...")
|
|
337
|
+
|
|
338
|
+
async def run_build():
|
|
339
|
+
manifest = await builder.build(clean=clean)
|
|
340
|
+
return manifest
|
|
341
|
+
|
|
342
|
+
manifest = asyncio.run(run_build())
|
|
343
|
+
|
|
344
|
+
pages_count = len(manifest.get("pages", {}))
|
|
345
|
+
assets_count = len(manifest.get("assets", []))
|
|
346
|
+
total_size = manifest.get("total_size", 0)
|
|
347
|
+
|
|
348
|
+
click.echo()
|
|
349
|
+
click.echo(click.style(f" ✅ Build completed successfully!", fg="green", bold=True))
|
|
350
|
+
click.echo(f" 📄 Pages built: {pages_count}")
|
|
351
|
+
click.echo(f" 🎨 Assets processed: {assets_count}")
|
|
352
|
+
click.echo(f" 💾 Total size: {_format_size(total_size)}")
|
|
353
|
+
click.echo(f" 📍 Output: {out}/")
|
|
354
|
+
click.echo()
|
|
355
|
+
click.echo(click.style(f" 🚀 Ready for deployment!", fg="cyan", bold=True))
|
|
356
|
+
click.echo(f" 📖 Serve with: nextpy start --port 5000")
|
|
357
|
+
click.echo()
|
|
358
|
+
|
|
359
|
+
except Exception as e:
|
|
360
|
+
click.echo(click.style(f" ❌ Build failed: {str(e)}", fg="red"))
|
|
361
|
+
if "Builder" not in str(e):
|
|
362
|
+
click.echo(click.style(f" 💡 Make sure you're in a NextPy project directory", fg="yellow"))
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
@cli.command()
|
|
366
|
+
@click.option("--port", "-p", default=5000, help="Port to run the server on")
|
|
367
|
+
@click.option("--host", "-h", default="0.0.0.0", help="Host to bind to")
|
|
368
|
+
def start(port: int, host: str):
|
|
369
|
+
"""Start the production server with enhanced feedback"""
|
|
370
|
+
click.echo(click.style("\n 🚀 NextPy Production Server", fg="green", bold=True))
|
|
371
|
+
click.echo(click.style(" ========================\n", fg="green"))
|
|
372
|
+
|
|
373
|
+
click.echo(f" 🏭 Mode: Production")
|
|
374
|
+
click.echo(f" 🌐 Host: {host} (accessible at http://localhost:{port})")
|
|
375
|
+
click.echo(f" 🔌 Port: {port}")
|
|
376
|
+
click.echo(f" 👥 Workers: 4 (multi-process)")
|
|
377
|
+
click.echo(f" 📝 Logging: Warning level only")
|
|
378
|
+
|
|
379
|
+
click.echo(f"\n ✨ Production server ready at http://0.0.0.0:{port}")
|
|
380
|
+
click.echo(f" 🌐 Open http://localhost:{port} in your browser\n")
|
|
381
|
+
click.echo(click.style(f" 💡 Press Ctrl+C to stop the server", fg="yellow"))
|
|
382
|
+
click.echo()
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
os.chdir(Path.cwd())
|
|
386
|
+
|
|
387
|
+
uvicorn.run(
|
|
388
|
+
"main:app",
|
|
389
|
+
host=host,
|
|
390
|
+
port=port,
|
|
391
|
+
workers=4,
|
|
392
|
+
log_level="warning",
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
except KeyboardInterrupt:
|
|
396
|
+
click.echo(click.style("\n 👋 Server stopped gracefully", fg="cyan"))
|
|
397
|
+
except Exception as e:
|
|
398
|
+
click.echo(click.style(f"\n ❌ Server error: {str(e)}", fg="red"))
|
|
399
|
+
click.echo(click.style(f" 💡 Make sure you have a main.py file with an app instance", fg="yellow"))
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@cli.command()
|
|
403
|
+
@click.argument("name")
|
|
404
|
+
def create(name: str):
|
|
405
|
+
"""Create a new NextPy project with True JSX support"""
|
|
406
|
+
click.echo(click.style(f"\n 🚀 Creating NextPy project: {name}", fg="cyan", bold=True))
|
|
407
|
+
click.echo(click.style(" " + "=" * (25 + len(name)) + "\n", fg="cyan"))
|
|
408
|
+
|
|
409
|
+
project_dir = Path(name)
|
|
410
|
+
|
|
411
|
+
if project_dir.exists():
|
|
412
|
+
click.echo(click.style(f" ❌ Error: Directory '{name}' already exists", fg="red"))
|
|
413
|
+
click.echo(click.style(f" 💡 Try a different name or remove the existing directory", fg="yellow"))
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
click.echo(f" 📁 Creating project structure...")
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
_create_project_structure(project_dir)
|
|
420
|
+
|
|
421
|
+
click.echo(click.style(f" ✅ Project successfully created!", fg="green", bold=True))
|
|
422
|
+
click.echo(f"\n 📍 Location: {project_dir.absolute()}")
|
|
423
|
+
click.echo(f"\n 🎯 Next steps:")
|
|
424
|
+
click.echo(f" 1️⃣ cd {name}")
|
|
425
|
+
click.echo(f" 2️⃣ pip install -r requirements.txt")
|
|
426
|
+
click.echo(f" 3️⃣ nextpy dev")
|
|
427
|
+
click.echo(f"\n 🌐 Your app will be available at: http://localhost:5000")
|
|
428
|
+
click.echo(f"\n 📚 Documentation: https://github.com/IBRAHIMFONYUY/nextpy-framework")
|
|
429
|
+
click.echo()
|
|
430
|
+
|
|
431
|
+
except Exception as e:
|
|
432
|
+
click.echo(click.style(f" ❌ Failed to create project: {str(e)}", fg="red"))
|
|
433
|
+
click.echo(click.style(f" 💡 Check the error message above for more details", fg="yellow"))
|
|
434
|
+
# Clean up partial creation
|
|
435
|
+
|
|
436
|
+
# Clean up partial creation
|
|
437
|
+
if project_dir.exists():
|
|
438
|
+
import shutil
|
|
439
|
+
shutil.rmtree(project_dir, ignore_errors=True)
|
|
440
|
+
click.echo(click.style(f" 🧹 Cleaned up partial files", fg="yellow"))
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@cli.command()
|
|
444
|
+
def routes():
|
|
445
|
+
"""Display all registered routes with detailed information"""
|
|
446
|
+
click.echo(click.style("\n 🛣️ NextPy Routes Overview", fg="cyan", bold=True))
|
|
447
|
+
click.echo(click.style(" =====================\n", fg="cyan"))
|
|
448
|
+
|
|
449
|
+
try:
|
|
450
|
+
from nextpy.core.router import Router
|
|
451
|
+
|
|
452
|
+
router = Router()
|
|
453
|
+
router.scan_pages()
|
|
454
|
+
|
|
455
|
+
page_routes = [r for r in router.routes if not r.is_api]
|
|
456
|
+
api_routes = router.api_routes
|
|
457
|
+
|
|
458
|
+
click.echo(click.style(f" 📄 Page Routes ({len(page_routes)} total)", fg="blue", bold=True))
|
|
459
|
+
if page_routes:
|
|
460
|
+
for i, route in enumerate(page_routes, 1):
|
|
461
|
+
dynamic = " 🔀" if route.is_dynamic else " 📄"
|
|
462
|
+
file_info = f"({route.file_path})"
|
|
463
|
+
click.echo(f" {i:2d}. {dynamic} {route.path:<30} {file_info}")
|
|
464
|
+
else:
|
|
465
|
+
click.echo(f" ℹ️ No page routes found")
|
|
466
|
+
|
|
467
|
+
click.echo()
|
|
468
|
+
click.echo(click.style(f" 🔌 API Routes ({len(api_routes)} total)", fg="green", bold=True))
|
|
469
|
+
if api_routes:
|
|
470
|
+
for i, route in enumerate(api_routes, 1):
|
|
471
|
+
dynamic = " 🔀" if route.is_dynamic else " 🔌"
|
|
472
|
+
file_info = f"({route.file_path})"
|
|
473
|
+
methods = "[GET, POST, PUT, DELETE]" if hasattr(route, 'handler') else "[GET]"
|
|
474
|
+
click.echo(f" {i:2d}. {dynamic} {route.path:<30} {methods:<20} {file_info}")
|
|
475
|
+
else:
|
|
476
|
+
click.echo(f" ℹ️ No API routes found")
|
|
477
|
+
|
|
478
|
+
click.echo()
|
|
479
|
+
click.echo(click.style(f" 📊 Summary:", fg="yellow", bold=True))
|
|
480
|
+
click.echo(f" Total Routes: {len(page_routes + api_routes)}")
|
|
481
|
+
click.echo(f" Dynamic Routes: {len([r for r in page_routes + api_routes if r.is_dynamic])}")
|
|
482
|
+
click.echo(f" Static Routes: {len([r for r in page_routes + api_routes if not r.is_dynamic])}")
|
|
483
|
+
click.echo()
|
|
484
|
+
|
|
485
|
+
except Exception as e:
|
|
486
|
+
click.echo(click.style(f" ❌ Error scanning routes: {str(e)}", fg="red"))
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
@cli.command()
|
|
490
|
+
@click.option("--out", "-o", default="out", help="Output directory for static files")
|
|
491
|
+
def export(out: str):
|
|
492
|
+
"""Export static files with enhanced feedback"""
|
|
493
|
+
click.echo(click.style("\n 📦 NextPy Export", fg="green", bold=True))
|
|
494
|
+
click.echo(click.style(" =============\n", fg="green"))
|
|
495
|
+
|
|
496
|
+
try:
|
|
497
|
+
from nextpy.core.builder import Builder
|
|
498
|
+
|
|
499
|
+
click.echo(f" 📂 Output directory: {out}/")
|
|
500
|
+
click.echo(f" ⚙️ Initializing exporter...")
|
|
501
|
+
|
|
502
|
+
builder = Builder(out_dir=out)
|
|
503
|
+
|
|
504
|
+
click.echo(f" 📤 Exporting static files...")
|
|
505
|
+
|
|
506
|
+
async def run_export():
|
|
507
|
+
manifest = await builder.export_static()
|
|
508
|
+
return manifest
|
|
509
|
+
|
|
510
|
+
manifest = asyncio.run(run_export())
|
|
511
|
+
|
|
512
|
+
files_count = len(manifest.get("files", []))
|
|
513
|
+
total_size = manifest.get("total_size", 0)
|
|
514
|
+
|
|
515
|
+
click.echo()
|
|
516
|
+
click.echo(click.style(f" ✅ Export completed successfully!", fg="green", bold=True))
|
|
517
|
+
click.echo(f" 📁 Files exported: {files_count}")
|
|
518
|
+
click.echo(f" 💾 Total size: {_format_size(total_size)}")
|
|
519
|
+
click.echo(f" 📍 Output: {out}/")
|
|
520
|
+
click.echo()
|
|
521
|
+
click.echo(click.style(f" 🚀 Ready for static hosting!", fg="cyan", bold=True))
|
|
522
|
+
click.echo()
|
|
523
|
+
|
|
524
|
+
except Exception as e:
|
|
525
|
+
click.echo(click.style(f" ❌ Export failed: {str(e)}", fg="red"))
|
|
526
|
+
click.echo(click.style(f" 💡 Make sure you're in a NextPy project directory", fg="yellow"))
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
@cli.command()
|
|
530
|
+
def version():
|
|
531
|
+
"""Show version and system information"""
|
|
532
|
+
click.echo(click.style("\n 📋 NextPy Framework Info", fg="cyan", bold=True))
|
|
533
|
+
click.echo(click.style(" ===================\n", fg="cyan"))
|
|
534
|
+
|
|
535
|
+
click.echo(f" 🏷️ Version: 2.4.0")
|
|
536
|
+
click.echo(f" 🐍 Python: {sys.version.split()[0]}")
|
|
537
|
+
click.echo(f" ⚡ Framework: NextPy")
|
|
538
|
+
click.echo(f" 🎨 Architecture: True JSX")
|
|
539
|
+
click.echo(f" 🖥️ Development Server: uvicorn")
|
|
540
|
+
click.echo(f" 🔄 Hot Reload: Available")
|
|
541
|
+
click.echo(f" 📁 Static Files: Available")
|
|
542
|
+
click.echo(f" 🔌 API Routes: Available")
|
|
543
|
+
click.echo(f" 📄 Page Routes: Available")
|
|
544
|
+
click.echo(f" 🧩 Component Routes: Available")
|
|
545
|
+
click.echo(f" 📚 Component Library: Available")
|
|
546
|
+
click.echo(f" 👨💻 Developer: Ibrahim Fonyuy")
|
|
547
|
+
click.echo(f" 📜 License: MIT")
|
|
548
|
+
click.echo(f" 🐙 GitHub: https://github.com/IBRAHIMFONYUY/nextpy-framework")
|
|
549
|
+
click.echo(f" 📖 Documentation: https://nextpy.org/docs")
|
|
550
|
+
click.echo(f" 🆘 Support: https://github.com/IBRAHIMFONYUY/nextpy-framework/issues")
|
|
551
|
+
|
|
552
|
+
click.echo()
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
@cli.command()
|
|
556
|
+
def info():
|
|
557
|
+
"""Show comprehensive framework and system information"""
|
|
558
|
+
click.echo(click.style("\n 🖥️ NextPy System Information", fg="cyan", bold=True))
|
|
559
|
+
click.echo(click.style(" ==========================\n", fg="cyan"))
|
|
560
|
+
|
|
561
|
+
# Framework info
|
|
562
|
+
click.echo(click.style(" 📦 Framework Details:", fg="blue", bold=True))
|
|
563
|
+
click.echo(f" Version: 2.0.0")
|
|
564
|
+
click.echo(f" Architecture: True JSX")
|
|
565
|
+
click.echo(f" Python: {sys.version.split()[0]}")
|
|
566
|
+
|
|
567
|
+
# Feature status
|
|
568
|
+
click.echo(click.style("\n ⚡ Feature Status:", fg="green", bold=True))
|
|
569
|
+
watchdog_status = "✅ Available" if WATCHDOG_AVAILABLE else "❌ Not Available (pip install watchdog)"
|
|
570
|
+
click.echo(f" Hot Reload: {watchdog_status}")
|
|
571
|
+
click.echo(f" Static Files: ✅ Available")
|
|
572
|
+
click.echo(f" API Routes: ✅ Available")
|
|
573
|
+
click.echo(f" Page Routes: ✅ Available")
|
|
574
|
+
click.echo(f" Component Library: ✅ Available")
|
|
575
|
+
|
|
576
|
+
# Project structure check
|
|
577
|
+
click.echo(click.style("\n 📁 Project Structure:", fg="yellow", bold=True))
|
|
578
|
+
required_dirs = ["pages", "components", "templates", "public"]
|
|
579
|
+
for dir_name in required_dirs:
|
|
580
|
+
status = "✅" if Path(dir_name).exists() else "❌"
|
|
581
|
+
click.echo(f" {dir_name}/: {status}")
|
|
582
|
+
|
|
583
|
+
# Available commands
|
|
584
|
+
click.echo(click.style("\n 🛠️ Available Commands:", fg="purple", bold=True))
|
|
585
|
+
commands = [
|
|
586
|
+
("nextpy dev", "Start development server"),
|
|
587
|
+
("nextpy build", "Build for production"),
|
|
588
|
+
("nextpy start", "Start production server"),
|
|
589
|
+
("nextpy create <name>", "Create new project"),
|
|
590
|
+
("nextpy generate <type> <name>", "Generate components/pages/APIs"),
|
|
591
|
+
("nextpy routes", "Show all routes"),
|
|
592
|
+
("nextpy export", "Export static files"),
|
|
593
|
+
("nextpy version", "Show version info"),
|
|
594
|
+
("nextpy info", "Show this information")
|
|
595
|
+
]
|
|
596
|
+
for cmd, desc in commands:
|
|
597
|
+
click.echo(f" {cmd:<25} - {desc}")
|
|
598
|
+
|
|
599
|
+
click.echo()
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
@cli.command()
|
|
603
|
+
@click.argument("type", type=click.Choice(["page", "api", "component"]))
|
|
604
|
+
@click.argument("name")
|
|
605
|
+
def generate(type: str, name: str):
|
|
606
|
+
"""Generate new page, API endpoint, or component"""
|
|
607
|
+
click.echo(click.style(f"\n Generating {type}: {name}", fg="cyan", bold=True))
|
|
608
|
+
click.echo(click.style(" " + "=" * (20 + len(name) + len(type)) + "\n", fg="cyan"))
|
|
609
|
+
|
|
610
|
+
if type == "page":
|
|
611
|
+
_generate_page(name)
|
|
612
|
+
elif type == "api":
|
|
613
|
+
_generate_api(name)
|
|
614
|
+
elif type == "component":
|
|
615
|
+
_generate_component(name)
|
|
616
|
+
|
|
617
|
+
click.echo(click.style(f"\n {type.title()} '{name}' created successfully!\n", fg="green", bold=True))
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
@cli.group()
|
|
621
|
+
def plugin():
|
|
622
|
+
"""Plugin management commands"""
|
|
623
|
+
pass
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
@plugin.command()
|
|
627
|
+
def list():
|
|
628
|
+
"""List all available plugins"""
|
|
629
|
+
click.echo(click.style("\n 🔌 NextPy Plugins", fg="cyan", bold=True))
|
|
630
|
+
click.echo(click.style(" ================\n", fg="cyan"))
|
|
631
|
+
|
|
632
|
+
try:
|
|
633
|
+
from nextpy.plugins import plugin_manager
|
|
634
|
+
|
|
635
|
+
plugin_info = plugin_manager.get_plugin_info()
|
|
636
|
+
|
|
637
|
+
click.echo(click.style(f" 📊 Overview:", fg="blue", bold=True))
|
|
638
|
+
click.echo(f" Total plugins: {plugin_info['total_plugins']}")
|
|
639
|
+
click.echo(f" Enabled: {plugin_info['enabled_plugins']}")
|
|
640
|
+
click.echo(f" Disabled: {plugin_info['total_plugins'] - plugin_info['enabled_plugins']}")
|
|
641
|
+
|
|
642
|
+
click.echo()
|
|
643
|
+
click.echo(click.style(f" 📋 Plugin Details:", fg="green", bold=True))
|
|
644
|
+
|
|
645
|
+
for plugin in plugin_info['plugins']:
|
|
646
|
+
status = "✅" if plugin['enabled'] else "❌"
|
|
647
|
+
priority = plugin['priority']
|
|
648
|
+
click.echo(f" {status} {plugin['name']:<15} v{plugin['version']:<8} (Priority: {priority})")
|
|
649
|
+
|
|
650
|
+
if plugin['dependencies']:
|
|
651
|
+
click.echo(f" Dependencies: {', '.join(plugin['dependencies'])}")
|
|
652
|
+
|
|
653
|
+
click.echo()
|
|
654
|
+
|
|
655
|
+
except ImportError:
|
|
656
|
+
click.echo(click.style(" ❌ Plugin system not available", fg="red"))
|
|
657
|
+
click.echo(click.style(" 💡 Install with: pip install nextpy[plugins]", fg="yellow"))
|
|
658
|
+
except Exception as e:
|
|
659
|
+
click.echo(click.style(f" ❌ Error: {str(e)}", fg="red"))
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
@plugin.command()
|
|
663
|
+
@click.argument("name")
|
|
664
|
+
@click.option("--enable/--disable", default=True, help="Enable or disable the plugin")
|
|
665
|
+
def enable(name: str, enable: bool):
|
|
666
|
+
"""Enable or disable a plugin"""
|
|
667
|
+
action = "Enabling" if enable else "Disabling"
|
|
668
|
+
click.echo(click.style(f"\n {action} plugin: {name}", fg="cyan", bold=True))
|
|
669
|
+
click.echo(click.style(" " + "=" * (20 + len(name)) + "\n", fg="cyan"))
|
|
670
|
+
|
|
671
|
+
try:
|
|
672
|
+
from nextpy.plugins import plugin_manager
|
|
673
|
+
|
|
674
|
+
if enable:
|
|
675
|
+
plugin_manager.enable_plugin(name)
|
|
676
|
+
click.echo(click.style(f" ✅ Plugin '{name}' enabled successfully", fg="green"))
|
|
677
|
+
else:
|
|
678
|
+
plugin_manager.disable_plugin(name)
|
|
679
|
+
click.echo(click.style(f" ❌ Plugin '{name}' disabled", fg="yellow"))
|
|
680
|
+
|
|
681
|
+
click.echo()
|
|
682
|
+
|
|
683
|
+
except ImportError:
|
|
684
|
+
click.echo(click.style(" ❌ Plugin system not available", fg="red"))
|
|
685
|
+
except Exception as e:
|
|
686
|
+
click.echo(click.style(f" ❌ Error: {str(e)}", fg="red"))
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
@plugin.command()
|
|
690
|
+
@click.argument("name")
|
|
691
|
+
@click.option("--config", help="Plugin configuration as JSON string")
|
|
692
|
+
def configure(name: str, config: str):
|
|
693
|
+
"""Configure a plugin"""
|
|
694
|
+
click.echo(click.style(f"\n ⚙️ Configuring plugin: {name}", fg="cyan", bold=True))
|
|
695
|
+
click.echo(click.style(" " + "=" * (20 + len(name)) + "\n", fg="cyan"))
|
|
696
|
+
|
|
697
|
+
try:
|
|
698
|
+
from nextpy.plugins import plugin_manager
|
|
699
|
+
import json
|
|
700
|
+
|
|
701
|
+
if config:
|
|
702
|
+
try:
|
|
703
|
+
config_dict = json.loads(config)
|
|
704
|
+
except json.JSONDecodeError:
|
|
705
|
+
click.echo(click.style(" ❌ Invalid JSON configuration", fg="red"))
|
|
706
|
+
return
|
|
707
|
+
else:
|
|
708
|
+
config_dict = {}
|
|
709
|
+
|
|
710
|
+
plugin_manager.configure_plugin(name, config_dict)
|
|
711
|
+
click.echo(click.style(f" ✅ Plugin '{name}' configured successfully", fg="green"))
|
|
712
|
+
|
|
713
|
+
if config_dict:
|
|
714
|
+
click.echo(f" Configuration: {json.dumps(config_dict, indent=2)}")
|
|
715
|
+
|
|
716
|
+
click.echo()
|
|
717
|
+
|
|
718
|
+
except ImportError:
|
|
719
|
+
click.echo(click.style(" ❌ Plugin system not available", fg="red"))
|
|
720
|
+
except Exception as e:
|
|
721
|
+
click.echo(click.style(f" ❌ Error: {str(e)}", fg="red"))
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
@plugin.command()
|
|
725
|
+
@click.argument("file_path", type=click.Path(exists=True))
|
|
726
|
+
def load(file_path: str):
|
|
727
|
+
"""Load a plugin from file"""
|
|
728
|
+
click.echo(click.style(f"\n 📦 Loading plugin from: {file_path}", fg="cyan", bold=True))
|
|
729
|
+
click.echo(click.style(" " + "=" * (25 + len(file_path)) + "\n", fg="cyan"))
|
|
730
|
+
|
|
731
|
+
try:
|
|
732
|
+
from nextpy.plugins import plugin_manager
|
|
733
|
+
from pathlib import Path
|
|
734
|
+
|
|
735
|
+
plugin = plugin_manager.load_plugin_from_file(Path(file_path))
|
|
736
|
+
plugin_manager.register_plugin(plugin)
|
|
737
|
+
|
|
738
|
+
click.echo(click.style(f" ✅ Plugin '{plugin.name}' loaded successfully", fg="green"))
|
|
739
|
+
click.echo(f" Version: {plugin.version}")
|
|
740
|
+
click.echo(f" Priority: {plugin.priority.value}")
|
|
741
|
+
|
|
742
|
+
click.echo()
|
|
743
|
+
|
|
744
|
+
except ImportError:
|
|
745
|
+
click.echo(click.style(" ❌ Plugin system not available", fg="red"))
|
|
746
|
+
except Exception as e:
|
|
747
|
+
click.echo(click.style(f" ❌ Error: {str(e)}", fg="red"))
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def _generate_page(name: str):
|
|
751
|
+
"""Generate a new page"""
|
|
752
|
+
page_path = Path(f"pages/{name}.py")
|
|
753
|
+
page_path.parent.mkdir(parents=True, exist_ok=True)
|
|
754
|
+
|
|
755
|
+
content = f'''"""Generated {name} page"""
|
|
756
|
+
|
|
757
|
+
def {name.title()}(props = None):
|
|
758
|
+
"""{name.title()} page component"""
|
|
759
|
+
props = props or {{}}
|
|
760
|
+
|
|
761
|
+
title = props.get("title", "{name.title()} Page")
|
|
762
|
+
|
|
763
|
+
return (
|
|
764
|
+
<div class="max-w-4xl px-4 py-12 mx-auto">
|
|
765
|
+
<h1 class="mb-6 text-4xl font-bold text-gray-900">{{title}}</h1>
|
|
766
|
+
<p class="text-lg text-gray-600">
|
|
767
|
+
This is the {name} page generated by NextPy.
|
|
768
|
+
</p>
|
|
769
|
+
</div>
|
|
770
|
+
)
|
|
771
|
+
|
|
772
|
+
def getServerSideProps(context):
|
|
773
|
+
return {{
|
|
774
|
+
"props": {{
|
|
775
|
+
"title": "{name.title()} Page"
|
|
776
|
+
}}
|
|
777
|
+
}}
|
|
778
|
+
|
|
779
|
+
default = {name.title()}
|
|
780
|
+
'''
|
|
781
|
+
|
|
782
|
+
page_path.write_text(content)
|
|
783
|
+
click.echo(f" Created: {page_path}")
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def _generate_api(name: str):
|
|
787
|
+
"""Generate a new API endpoint"""
|
|
788
|
+
api_path = Path(f"pages/api/{name}.py")
|
|
789
|
+
api_path.parent.mkdir(parents=True, exist_ok=True)
|
|
790
|
+
|
|
791
|
+
content = f'''"""Generated {name} API endpoint"""
|
|
792
|
+
|
|
793
|
+
async def get(request):
|
|
794
|
+
"""GET /api/{name}"""
|
|
795
|
+
return {{
|
|
796
|
+
"message": "Hello from {name} API!",
|
|
797
|
+
"endpoint": "/api/{name}",
|
|
798
|
+
"method": "GET"
|
|
799
|
+
}}
|
|
800
|
+
|
|
801
|
+
async def post(request):
|
|
802
|
+
"""POST /api/{name}"""
|
|
803
|
+
body = await request.json()
|
|
804
|
+
return {{
|
|
805
|
+
"message": "POST request received",
|
|
806
|
+
"data": body,
|
|
807
|
+
"endpoint": "/api/{name}",
|
|
808
|
+
"method": "POST"
|
|
809
|
+
}}
|
|
810
|
+
'''
|
|
811
|
+
|
|
812
|
+
api_path.write_text(content)
|
|
813
|
+
click.echo(f" Created: {api_path}")
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
def _generate_component(name: str):
|
|
817
|
+
"""Generate a new component"""
|
|
818
|
+
component_path = Path(f"components/{name}.py")
|
|
819
|
+
component_path.parent.mkdir(parents=True, exist_ok=True)
|
|
820
|
+
|
|
821
|
+
content = f'''"""Generated {name} component"""
|
|
822
|
+
|
|
823
|
+
def {name.title()}(props = None):
|
|
824
|
+
"""{name.title()} component"""
|
|
825
|
+
props = props or {{}}
|
|
826
|
+
|
|
827
|
+
children = props.get("children", "")
|
|
828
|
+
className = props.get("className", "")
|
|
829
|
+
|
|
830
|
+
return (
|
|
831
|
+
<div class="{name.lower()}-component " + className>
|
|
832
|
+
{{children}}
|
|
833
|
+
</div>
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
default = {name.title()}
|
|
837
|
+
'''
|
|
838
|
+
|
|
839
|
+
component_path.write_text(content)
|
|
840
|
+
click.echo(f" Created: {component_path}")
|
|
841
|
+
|
|
842
|
+
|
|
843
|
+
def _ensure_project_structure():
|
|
844
|
+
"""Ensure the basic project structure exists"""
|
|
845
|
+
dirs = ["pages", "pages/api", "templates", "public", "public/css", "public/js"]
|
|
846
|
+
|
|
847
|
+
for dir_path in dirs:
|
|
848
|
+
Path(dir_path).mkdir(parents=True, exist_ok=True)
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def _create_project_structure(project_dir: Path):
|
|
852
|
+
"""Create a complete NextPy project structure with all integrations"""
|
|
853
|
+
dirs = [
|
|
854
|
+
"pages",
|
|
855
|
+
"pages/api",
|
|
856
|
+
"pages/blog",
|
|
857
|
+
"pages/api/users",
|
|
858
|
+
"components",
|
|
859
|
+
"components/ui",
|
|
860
|
+
"components/layout",
|
|
861
|
+
"templates",
|
|
862
|
+
"public",
|
|
863
|
+
"public/css",
|
|
864
|
+
"public/js",
|
|
865
|
+
"public/images",
|
|
866
|
+
"styles",
|
|
867
|
+
"models",
|
|
868
|
+
"utils",
|
|
869
|
+
"hooks",
|
|
870
|
+
"middleware",
|
|
871
|
+
"tests",
|
|
872
|
+
"docs",
|
|
873
|
+
".vscode"
|
|
874
|
+
]
|
|
875
|
+
|
|
876
|
+
for dir_path in dirs:
|
|
877
|
+
(project_dir / dir_path).mkdir(parents=True, exist_ok=True)
|
|
878
|
+
click.echo(f" Created: {dir_path}/")
|
|
879
|
+
|
|
880
|
+
# Create styles.css with Tailwind directives
|
|
881
|
+
(project_dir / "styles.css").write_text('''/* NextPy Styles */
|
|
882
|
+
@tailwind base;
|
|
883
|
+
@tailwind components;
|
|
884
|
+
@tailwind utilities;
|
|
885
|
+
|
|
886
|
+
/* Custom styles */
|
|
887
|
+
body {
|
|
888
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
.debug-icon {
|
|
892
|
+
position: fixed;
|
|
893
|
+
bottom: 20px;
|
|
894
|
+
right: 20px;
|
|
895
|
+
background: #3b82f6;
|
|
896
|
+
color: white;
|
|
897
|
+
padding: 8px 12px;
|
|
898
|
+
border-radius: 8px;
|
|
899
|
+
font-size: 12px;
|
|
900
|
+
font-weight: bold;
|
|
901
|
+
z-index: 9999;
|
|
902
|
+
cursor: pointer;
|
|
903
|
+
transition: all 0.3s ease;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.debug-icon:hover {
|
|
907
|
+
background: #2563eb;
|
|
908
|
+
transform: scale(1.05);
|
|
909
|
+
}
|
|
910
|
+
''')
|
|
911
|
+
click.echo(" Created: styles.css")
|
|
912
|
+
|
|
913
|
+
# Create tailwind.config.js with Python support
|
|
914
|
+
(project_dir / "tailwind.config.js").write_text('''module.exports = {
|
|
915
|
+
content: [
|
|
916
|
+
'./pages/**/*.{js,ts,jsx,tsx,mdx,py}',
|
|
917
|
+
'./components/**/*.{js,ts,jsx,tsx,mdx,py}',
|
|
918
|
+
'./templates/**/*.{html,htm}',
|
|
919
|
+
'./app/**/*.{js,ts,jsx,tsx,mdx,py}',
|
|
920
|
+
],
|
|
921
|
+
theme: {
|
|
922
|
+
extend: {
|
|
923
|
+
colors: {
|
|
924
|
+
blue: {
|
|
925
|
+
50: '#eff6ff',
|
|
926
|
+
100: '#dbeafe',
|
|
927
|
+
200: '#bfdbfe',
|
|
928
|
+
300: '#93c5fd',
|
|
929
|
+
400: '#60a5fa',
|
|
930
|
+
500: '#3b82f6',
|
|
931
|
+
600: '#2563eb',
|
|
932
|
+
700: '#1d4ed8',
|
|
933
|
+
800: '#1e40af',
|
|
934
|
+
900: '#1e3a8a',
|
|
935
|
+
},
|
|
936
|
+
red: {
|
|
937
|
+
50: '#fef2f2',
|
|
938
|
+
100: '#fee2e2',
|
|
939
|
+
200: '#fecaca',
|
|
940
|
+
300: '#fca5a5',
|
|
941
|
+
400: '#f87171',
|
|
942
|
+
500: '#ef4444',
|
|
943
|
+
600: '#dc2626',
|
|
944
|
+
700: '#b91c1c',
|
|
945
|
+
800: '#991b1b',
|
|
946
|
+
900: '#7f1d1d',
|
|
947
|
+
},
|
|
948
|
+
green: {
|
|
949
|
+
50: '#f0fdf4',
|
|
950
|
+
100: '#dcfce7',
|
|
951
|
+
200: '#bbf7d0',
|
|
952
|
+
300: '#86efac',
|
|
953
|
+
400: '#4ade80',
|
|
954
|
+
500: '#22c55e',
|
|
955
|
+
600: '#16a34a',
|
|
956
|
+
700: '#15803d',
|
|
957
|
+
800: '#166534',
|
|
958
|
+
900: '#14532d',
|
|
959
|
+
},
|
|
960
|
+
purple: {
|
|
961
|
+
50: '#faf5ff',
|
|
962
|
+
100: '#f3e8ff',
|
|
963
|
+
200: '#e9d5ff',
|
|
964
|
+
300: '#d8b4fe',
|
|
965
|
+
400: '#c084fc',
|
|
966
|
+
500: '#a855f7',
|
|
967
|
+
600: '#9333ea',
|
|
968
|
+
700: '#7c3aed',
|
|
969
|
+
800: '#6b21a8',
|
|
970
|
+
900: '#581c87',
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
},
|
|
975
|
+
plugins: [],
|
|
976
|
+
};''')
|
|
977
|
+
click.echo(" Created: tailwind.config.js")
|
|
978
|
+
|
|
979
|
+
# Create postcss.config.js with new Tailwind plugin
|
|
980
|
+
(project_dir / "postcss.config.js").write_text('''module.exports = {
|
|
981
|
+
plugins: {
|
|
982
|
+
'@tailwindcss/postcss': {},
|
|
983
|
+
autoprefixer: {},
|
|
984
|
+
},
|
|
985
|
+
};''')
|
|
986
|
+
click.echo(" Created: postcss.config.js")
|
|
987
|
+
|
|
988
|
+
# Create package.json with Node.js dependencies
|
|
989
|
+
(project_dir / "package.json").write_text('''{
|
|
990
|
+
"name": "nextpy-app",
|
|
991
|
+
"version": "1.0.0",
|
|
992
|
+
"description": "A NextPy application with True JSX and Tailwind CSS",
|
|
993
|
+
"main": "index.js",
|
|
994
|
+
"scripts": {
|
|
995
|
+
"dev": "nextpy dev",
|
|
996
|
+
"build": "nextpy build",
|
|
997
|
+
"start": "nextpy start"
|
|
998
|
+
},
|
|
999
|
+
"keywords": ["python", "nextpy", "jsx", "tailwind"],
|
|
1000
|
+
"author": "",
|
|
1001
|
+
"license": "MIT",
|
|
1002
|
+
"devDependencies": {
|
|
1003
|
+
"autoprefixer": "^10.4.22",
|
|
1004
|
+
"postcss": "^8.5.6",
|
|
1005
|
+
"tailwindcss": "^4.1.17"
|
|
1006
|
+
},
|
|
1007
|
+
"dependencies": {
|
|
1008
|
+
"@tailwindcss/postcss": "^4.1.18",
|
|
1009
|
+
"postcss-cli": "^11.0.1"
|
|
1010
|
+
}
|
|
1011
|
+
}''')
|
|
1012
|
+
click.echo(" Created: package.json")
|
|
1013
|
+
|
|
1014
|
+
# Create interactive homepage with advanced features
|
|
1015
|
+
(project_dir / "pages" / "index.py").write_text('''"""Interactive Homepage with True JSX"""
|
|
1016
|
+
|
|
1017
|
+
def Home(props=None):
|
|
1018
|
+
props = props or {}
|
|
1019
|
+
title = props.get("title", "Welcome to NextPy!")
|
|
1020
|
+
message = props.get("message", "Build amazing web apps with Python and True JSX")
|
|
1021
|
+
|
|
1022
|
+
return (
|
|
1023
|
+
<div class="min-h-screen bg-gradient-to-br from-blue-600 via-purple-600 to-pink-600">
|
|
1024
|
+
{/* Navigation */}
|
|
1025
|
+
<nav class="bg-white border-b border-white bg-opacity-10 backdrop-blur-md border-opacity-20">
|
|
1026
|
+
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
|
1027
|
+
<div class="flex items-center justify-between h-16">
|
|
1028
|
+
<div class="flex items-center">
|
|
1029
|
+
<h1 class="text-xl font-bold text-white">NextPy</h1>
|
|
1030
|
+
</div>
|
|
1031
|
+
<div class="flex space-x-4">
|
|
1032
|
+
<a href="/about" class="px-3 py-2 text-sm font-medium text-white transition-colors rounded-md hover:text-blue-200">
|
|
1033
|
+
About
|
|
1034
|
+
</a>
|
|
1035
|
+
<a href="/features" class="px-3 py-2 text-sm font-medium text-white transition-colors rounded-md hover:text-blue-200">
|
|
1036
|
+
Features
|
|
1037
|
+
</a>
|
|
1038
|
+
<a href="/docs" class="px-3 py-2 text-sm font-medium text-white transition-colors rounded-md hover:text-blue-200">
|
|
1039
|
+
Docs
|
|
1040
|
+
</a>
|
|
1041
|
+
</div>
|
|
1042
|
+
</div>
|
|
1043
|
+
</div>
|
|
1044
|
+
</nav>
|
|
1045
|
+
|
|
1046
|
+
{/* Hero Section */}
|
|
1047
|
+
<div class="relative overflow-hidden">
|
|
1048
|
+
<div class="mx-auto max-w-7xl">
|
|
1049
|
+
<div class="relative z-10 pb-8 bg-transparent sm:pb-16 md:pb-20 lg:pb-28 xl:pb-32">
|
|
1050
|
+
<main class="mx-auto mt-10 max-w-7xl sm:mt-12 sm:px-6 lg:mt-16 lg:px-8 xl:mt-20">
|
|
1051
|
+
<div class="text-center">
|
|
1052
|
+
<h1 class="text-4xl font-extrabold tracking-tight text-white sm:text-5xl md:text-6xl">
|
|
1053
|
+
<span class="block xl:inline">Build amazing web apps with</span>
|
|
1054
|
+
<span class="block text-blue-200 xl:inline">Python and True JSX</span>
|
|
1055
|
+
</h1>
|
|
1056
|
+
<p class="max-w-lg mx-auto mt-6 text-xl text-blue-100 sm:text-2xl">
|
|
1057
|
+
{message}
|
|
1058
|
+
</p>
|
|
1059
|
+
<div class="flex justify-center mt-10">
|
|
1060
|
+
<a href="/getting-started" class="inline-flex items-center justify-center px-8 py-3 text-base font-medium text-blue-600 transition-all duration-300 transform bg-white border border-transparent rounded-md hover:bg-blue-50 md:py-4 md:text-lg md:px-10 hover:scale-105">
|
|
1061
|
+
Get Started
|
|
1062
|
+
</a>
|
|
1063
|
+
<a href="https://github.com/nextpy/nextpy" class="inline-flex items-center justify-center px-8 py-3 ml-4 text-base font-medium text-white transition-all duration-300 transform bg-blue-500 border border-transparent rounded-md hover:bg-blue-600 md:py-4 md:text-lg md:px-10 hover:scale-105">
|
|
1064
|
+
GitHub
|
|
1065
|
+
</a>
|
|
1066
|
+
</div>
|
|
1067
|
+
</div>
|
|
1068
|
+
</main>
|
|
1069
|
+
</div>
|
|
1070
|
+
</div>
|
|
1071
|
+
|
|
1072
|
+
{/* Background decoration */}
|
|
1073
|
+
<div class="absolute inset-0 -z-10">
|
|
1074
|
+
<div class="absolute top-0 transform -translate-x-1/2 left-1/2 blur-3xl opacity-20">
|
|
1075
|
+
<div class="rounded-full aspect-square w-96 h-96 bg-gradient-to-r from-blue-400 to-purple-600"></div>
|
|
1076
|
+
</div>
|
|
1077
|
+
<div class="absolute top-0 transform translate-x-1/2 right-1/2 blur-3xl opacity-20">
|
|
1078
|
+
<div class="rounded-full aspect-square w-96 h-96 bg-gradient-to-r from-purple-400 to-pink-600"></div>
|
|
1079
|
+
</div>
|
|
1080
|
+
</div>
|
|
1081
|
+
</div>
|
|
1082
|
+
|
|
1083
|
+
{/* Features Section */}
|
|
1084
|
+
<div class="py-12 bg-white">
|
|
1085
|
+
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
|
1086
|
+
<div class="lg:text-center">
|
|
1087
|
+
<h2 class="text-base font-semibold tracking-wide text-blue-600 uppercase">
|
|
1088
|
+
Features
|
|
1089
|
+
</h2>
|
|
1090
|
+
<p class="mt-2 text-3xl font-extrabold tracking-tight text-gray-900 sm:text-4xl">
|
|
1091
|
+
Everything you need to build amazing apps
|
|
1092
|
+
</p>
|
|
1093
|
+
</div>
|
|
1094
|
+
|
|
1095
|
+
<div class="mt-10">
|
|
1096
|
+
<div class="space-y-10 md:space-y-0 md:grid md:grid-cols-2 md:gap-x-8 md:gap-y-10 lg:grid-cols-3">
|
|
1097
|
+
{/* Feature 1 */}
|
|
1098
|
+
<div class="relative">
|
|
1099
|
+
<div class="absolute flex items-center justify-center w-12 h-12 text-white bg-blue-500 rounded-md">
|
|
1100
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1101
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7m0 0v7l9-11h-7z" />
|
|
1102
|
+
</svg>
|
|
1103
|
+
</div>
|
|
1104
|
+
<p class="ml-16 text-lg font-medium leading-6 text-gray-900">
|
|
1105
|
+
True JSX Components
|
|
1106
|
+
</p>
|
|
1107
|
+
<p class="mt-2 ml-16 text-base text-gray-500">
|
|
1108
|
+
Write React-like components directly in Python with full JSX support.
|
|
1109
|
+
</p>
|
|
1110
|
+
<a href="/jsx-demo" class="mt-4 ml-16 text-base font-medium text-blue-600 hover:text-blue-500">
|
|
1111
|
+
Learn more →
|
|
1112
|
+
</a>
|
|
1113
|
+
</div>
|
|
1114
|
+
|
|
1115
|
+
{/* Feature 2 */}
|
|
1116
|
+
<div class="relative">
|
|
1117
|
+
<div class="absolute flex items-center justify-center w-12 h-12 text-white bg-purple-500 rounded-md">
|
|
1118
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1119
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7" />
|
|
1120
|
+
</svg>
|
|
1121
|
+
</div>
|
|
1122
|
+
<p class="ml-16 text-lg font-medium leading-6 text-gray-900">
|
|
1123
|
+
Tailwind CSS Integration
|
|
1124
|
+
</p>
|
|
1125
|
+
<p class="mt-2 ml-16 text-base text-gray-500">
|
|
1126
|
+
Built-in Tailwind CSS v4 with PostCSS compilation and utility classes.
|
|
1127
|
+
</p>
|
|
1128
|
+
<a href="/tailwind-demo" class="mt-4 ml-16 text-base font-medium text-blue-600 hover:text-blue-500">
|
|
1129
|
+
Learn more →
|
|
1130
|
+
</a>
|
|
1131
|
+
</div>
|
|
1132
|
+
|
|
1133
|
+
{/* Feature 3 */}
|
|
1134
|
+
<div class="relative">
|
|
1135
|
+
<div class="absolute flex items-center justify-center w-12 h-12 text-white bg-green-500 rounded-md">
|
|
1136
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1137
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 2 9 9 0 0118 0z" />
|
|
1138
|
+
</svg>
|
|
1139
|
+
</div>
|
|
1140
|
+
<p class="ml-16 text-lg font-medium leading-6 text-gray-900">
|
|
1141
|
+
File-based Routing
|
|
1142
|
+
</p>
|
|
1143
|
+
<p class="mt-2 ml-16 text-base text-gray-500">
|
|
1144
|
+
Automatic route discovery with support for dynamic routes and API endpoints.
|
|
1145
|
+
</p>
|
|
1146
|
+
<a href="/routing-demo" class="mt-4 ml-16 text-base font-medium text-blue-600 hover:text-blue-500">
|
|
1147
|
+
Learn more →
|
|
1148
|
+
</a>
|
|
1149
|
+
</div>
|
|
1150
|
+
</div>
|
|
1151
|
+
</div>
|
|
1152
|
+
</div>
|
|
1153
|
+
</div>
|
|
1154
|
+
|
|
1155
|
+
{/* Interactive Demo Section */}
|
|
1156
|
+
<div class="py-12 bg-gray-50">
|
|
1157
|
+
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
|
1158
|
+
<div class="text-center">
|
|
1159
|
+
<h2 class="text-3xl font-extrabold text-gray-900">
|
|
1160
|
+
Try It Yourself
|
|
1161
|
+
</h2>
|
|
1162
|
+
<p class="max-w-2xl mt-4 text-xl text-gray-500">
|
|
1163
|
+
Interactive demos showing NextPy capabilities
|
|
1164
|
+
</p>
|
|
1165
|
+
</div>
|
|
1166
|
+
|
|
1167
|
+
<div class="grid grid-cols-1 gap-8 mt-12 sm:grid-cols-2 lg:grid-cols-3">
|
|
1168
|
+
{/* Interactive Counter */}
|
|
1169
|
+
<div class="p-6 bg-white rounded-lg shadow-lg">
|
|
1170
|
+
<h3 class="text-lg font-medium text-gray-900">Live Counter</h3>
|
|
1171
|
+
<p class="mt-2 text-sm text-gray-500">Interactive state management demo</p>
|
|
1172
|
+
<div class="mt-4">
|
|
1173
|
+
<button class="px-4 py-2 text-white transition-colors bg-blue-500 rounded hover:bg-blue-600">
|
|
1174
|
+
Click me!
|
|
1175
|
+
</button>
|
|
1176
|
+
<span class="ml-4 text-lg font-semibold">Count: 0</span>
|
|
1177
|
+
</div>
|
|
1178
|
+
</div>
|
|
1179
|
+
|
|
1180
|
+
{/* Form Demo */}
|
|
1181
|
+
<div class="p-6 bg-white rounded-lg shadow-lg">
|
|
1182
|
+
<h3 class="text-lg font-medium text-gray-900">Form Handling</h3>
|
|
1183
|
+
<p class="mt-2 text-sm text-gray-500">Server-side form processing</p>
|
|
1184
|
+
<div class="mt-4">
|
|
1185
|
+
<input type="text" placeholder="Type something..." class="w-full px-3 py-2 border border-gray-300 rounded-md" />
|
|
1186
|
+
<button class="w-full px-4 py-2 mt-2 text-white transition-colors bg-green-500 rounded hover:bg-green-600">
|
|
1187
|
+
Submit
|
|
1188
|
+
</button>
|
|
1189
|
+
</div>
|
|
1190
|
+
</div>
|
|
1191
|
+
|
|
1192
|
+
{/* API Demo */}
|
|
1193
|
+
<div class="p-6 bg-white rounded-lg shadow-lg">
|
|
1194
|
+
<h3 class="text-lg font-medium text-gray-900">API Integration</h3>
|
|
1195
|
+
<p class="mt-2 text-sm text-gray-500">RESTful API endpoints</p>
|
|
1196
|
+
<div class="mt-4">
|
|
1197
|
+
<button class="w-full px-4 py-2 text-white transition-colors bg-purple-500 rounded hover:bg-purple-600">
|
|
1198
|
+
Call API
|
|
1199
|
+
</button>
|
|
1200
|
+
<div class="mt-2 text-xs text-gray-600">Response will appear here</div>
|
|
1201
|
+
</div>
|
|
1202
|
+
</div>
|
|
1203
|
+
</div>
|
|
1204
|
+
</div>
|
|
1205
|
+
</div>
|
|
1206
|
+
|
|
1207
|
+
{/* Footer */}
|
|
1208
|
+
<footer class="bg-gray-900">
|
|
1209
|
+
<div class="px-4 py-12 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
|
1210
|
+
<div class="flex flex-col items-center space-y-4">
|
|
1211
|
+
<p class="text-base text-center text-gray-400">
|
|
1212
|
+
Built with ❤️ using NextPy Framework
|
|
1213
|
+
</p>
|
|
1214
|
+
<div class="flex space-x-6">
|
|
1215
|
+
<a href="/about" class="text-gray-400 hover:text-gray-300">About</a>
|
|
1216
|
+
<a href="/docs" class="text-gray-400 hover:text-gray-300">Documentation</a>
|
|
1217
|
+
<a href="https://github.com/nextpy/nextpy" class="text-gray-400 hover:text-gray-300">GitHub</a>
|
|
1218
|
+
</div>
|
|
1219
|
+
</div>
|
|
1220
|
+
</div>
|
|
1221
|
+
</footer>
|
|
1222
|
+
</div>
|
|
1223
|
+
)
|
|
1224
|
+
|
|
1225
|
+
def getServerSideProps(context):
|
|
1226
|
+
return {
|
|
1227
|
+
"props": {
|
|
1228
|
+
"title": "Welcome to NextPy!",
|
|
1229
|
+
"message": "Build amazing web apps with Python and True JSX"
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
default = Home
|
|
1234
|
+
''')
|
|
1235
|
+
click.echo(" Created: pages/index.py (interactive homepage)")
|
|
1236
|
+
|
|
1237
|
+
# Create enhanced about page with interactive features
|
|
1238
|
+
(project_dir / "pages" / "about.py").write_text('''"""Enhanced About page with True JSX"""
|
|
1239
|
+
|
|
1240
|
+
def About(props=None):
|
|
1241
|
+
"""About page component with interactive features"""
|
|
1242
|
+
props = props or {}
|
|
1243
|
+
|
|
1244
|
+
title = props.get("title", "About NextPy")
|
|
1245
|
+
description = props.get("description", "The Python web framework that brings React-like development to Python")
|
|
1246
|
+
|
|
1247
|
+
return (
|
|
1248
|
+
<div class="min-h-screen bg-gray-50">
|
|
1249
|
+
{/* Hero Section */}
|
|
1250
|
+
<div class="text-white bg-gradient-to-r from-blue-600 to-purple-600">
|
|
1251
|
+
<div class="px-4 py-16 mx-auto max-w-7xl sm:py-24 sm:px-6 lg:px-8">
|
|
1252
|
+
<div class="text-center">
|
|
1253
|
+
<h1 class="text-4xl font-extrabold tracking-tight sm:text-5xl lg:text-6xl">
|
|
1254
|
+
{title}
|
|
1255
|
+
</h1>
|
|
1256
|
+
<p class="max-w-2xl mx-auto mt-6 text-xl text-blue-100">
|
|
1257
|
+
{description}
|
|
1258
|
+
</p>
|
|
1259
|
+
<div class="flex justify-center mt-10 space-x-4">
|
|
1260
|
+
<a href="/features" class="inline-flex items-center justify-center px-8 py-3 text-base font-medium text-blue-600 bg-white border border-transparent rounded-md hover:bg-blue-50 md:py-4 md:text-lg md:px-10">
|
|
1261
|
+
Explore Features
|
|
1262
|
+
</a>
|
|
1263
|
+
<a href="/getting-started" class="inline-flex items-center justify-center px-8 py-3 text-base font-medium text-white bg-blue-500 border border-transparent rounded-md hover:bg-blue-600 md:py-4 md:text-lg md:px-10">
|
|
1264
|
+
Get Started
|
|
1265
|
+
</a>
|
|
1266
|
+
</div>
|
|
1267
|
+
</div>
|
|
1268
|
+
</div>
|
|
1269
|
+
</div>
|
|
1270
|
+
|
|
1271
|
+
{/* Features Grid */}
|
|
1272
|
+
<div class="py-12 bg-white">
|
|
1273
|
+
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
|
1274
|
+
<div class="lg:text-center">
|
|
1275
|
+
<h2 class="text-base font-semibold tracking-wide text-blue-600 uppercase">
|
|
1276
|
+
Why Choose NextPy?
|
|
1277
|
+
</h2>
|
|
1278
|
+
<p class="mt-2 text-3xl font-extrabold tracking-tight text-gray-900 sm:text-4xl">
|
|
1279
|
+
Everything you need to build modern web applications
|
|
1280
|
+
</p>
|
|
1281
|
+
</div>
|
|
1282
|
+
|
|
1283
|
+
<div class="mt-10">
|
|
1284
|
+
<div class="gap-8 space-y-10 md:space-y-0 md:grid md:grid-cols-2 lg:grid-cols-3">
|
|
1285
|
+
{/* Feature 1 */}
|
|
1286
|
+
<div class="relative">
|
|
1287
|
+
<div class="absolute flex items-center justify-center w-12 h-12 text-white bg-blue-500 rounded-md">
|
|
1288
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1289
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7m0 0v7l9-11h-7z" />
|
|
1290
|
+
</svg>
|
|
1291
|
+
</div>
|
|
1292
|
+
<h3 class="ml-16 text-lg font-medium leading-6 text-gray-900">True JSX Support</h3>
|
|
1293
|
+
<p class="mt-2 ml-16 text-base text-gray-500">
|
|
1294
|
+
Write React-like components with JSX syntax directly in Python. No transpilation needed.
|
|
1295
|
+
</p>
|
|
1296
|
+
</div>
|
|
1297
|
+
|
|
1298
|
+
{/* Feature 2 */}
|
|
1299
|
+
<div class="relative">
|
|
1300
|
+
<div class="absolute flex items-center justify-center w-12 h-12 text-white bg-purple-500 rounded-md">
|
|
1301
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1302
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7" />
|
|
1303
|
+
</svg>
|
|
1304
|
+
</div>
|
|
1305
|
+
<h3 class="ml-16 text-lg font-medium leading-6 text-gray-900">File-based Routing</h3>
|
|
1306
|
+
<p class="mt-2 ml-16 text-base text-gray-500">
|
|
1307
|
+
Automatic route discovery with support for dynamic routes and API endpoints.
|
|
1308
|
+
</p>
|
|
1309
|
+
</div>
|
|
1310
|
+
|
|
1311
|
+
{/* Feature 3 */}
|
|
1312
|
+
<div class="relative">
|
|
1313
|
+
<div class="absolute flex items-center justify-center w-12 h-12 text-white bg-green-500 rounded-md">
|
|
1314
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1315
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 2 9 9 0 0118 0z" />
|
|
1316
|
+
</svg>
|
|
1317
|
+
</div>
|
|
1318
|
+
<h3 class="ml-16 text-lg font-medium leading-6 text-gray-900">Tailwind CSS Integration</h3>
|
|
1319
|
+
<p class="mt-2 ml-16 text-base text-gray-500">
|
|
1320
|
+
Built-in Tailwind CSS v4 with PostCSS compilation and utility classes.
|
|
1321
|
+
</p>
|
|
1322
|
+
</div>
|
|
1323
|
+
|
|
1324
|
+
{/* Feature 4 */}
|
|
1325
|
+
<div class="relative">
|
|
1326
|
+
<div class="absolute flex items-center justify-center w-12 h-12 text-white bg-red-500 rounded-md">
|
|
1327
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1328
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7m0 0v7l9-11h-7z" />
|
|
1329
|
+
</svg>
|
|
1330
|
+
</div>
|
|
1331
|
+
<h3 class="ml-16 text-lg font-medium leading-6 text-gray-900">Server-side Rendering</h3>
|
|
1332
|
+
<p class="mt-2 ml-16 text-base text-gray-500">
|
|
1333
|
+
Fast initial page loads with server-side rendering and data fetching.
|
|
1334
|
+
</p>
|
|
1335
|
+
</div>
|
|
1336
|
+
|
|
1337
|
+
{/* Feature 5 */}
|
|
1338
|
+
<div class="relative">
|
|
1339
|
+
<div class="absolute flex items-center justify-center w-12 h-12 text-white bg-yellow-500 rounded-md">
|
|
1340
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1341
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 00-1.065 2.572c1.756.426 1.756 2.924 0 3.35-1.756a1.724 1.724 0 00-1.066-2.573c1.756-.426 1.756-2.924 0-3.35 1.756a1.724 1.724 0 00-2.573-1.066c-1.756.426-1.756-2.924 0-3.35 1.756A1.724 1.724 0 006.573 2.572C3.31 7.76 1.574 8.686 4.317 8.686a1.724 1.724 0 00-1.066-2.572c1.756-.426 1.756-2.924 0-3.35 1.756a1.724 1.724 0 00-2.573-1.066c-1.756.426-1.756-2.924 0-3.35 1.756A1.724 1.724 0 001.066 2.572c1.756.426 1.756 2.924 0 3.35-1.756a1.724 1.724 0 002.573 1.066z" />
|
|
1342
|
+
</svg>
|
|
1343
|
+
</div>
|
|
1344
|
+
<h3 class="ml-16 text-lg font-medium leading-6 text-gray-900">Hot Reload Development</h3>
|
|
1345
|
+
<p class="mt-2 ml-16 text-base text-gray-500">
|
|
1346
|
+
Instant hot reload when saving files with Watchdog integration.
|
|
1347
|
+
</p>
|
|
1348
|
+
</div>
|
|
1349
|
+
|
|
1350
|
+
{/* Feature 6 */}
|
|
1351
|
+
<div class="relative">
|
|
1352
|
+
<div class="absolute flex items-center justify-center w-12 h-12 text-white bg-indigo-500 rounded-md">
|
|
1353
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1354
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
1355
|
+
</svg>
|
|
1356
|
+
</div>
|
|
1357
|
+
<h3 class="ml-16 text-lg font-medium leading-6 text-gray-900">API Routes</h3>
|
|
1358
|
+
<p class="mt-2 ml-16 text-base text-gray-500">
|
|
1359
|
+
Built-in FastAPI support for creating RESTful API endpoints.
|
|
1360
|
+
</p>
|
|
1361
|
+
</div>
|
|
1362
|
+
</div>
|
|
1363
|
+
</div>
|
|
1364
|
+
</div>
|
|
1365
|
+
</div>
|
|
1366
|
+
|
|
1367
|
+
{/* Interactive Demo Section */}
|
|
1368
|
+
<div class="py-12 bg-gray-50">
|
|
1369
|
+
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
|
1370
|
+
<div class="text-center">
|
|
1371
|
+
<h2 class="text-3xl font-extrabold text-gray-900">
|
|
1372
|
+
See It In Action
|
|
1373
|
+
</h2>
|
|
1374
|
+
<p class="max-w-2xl mt-4 text-xl text-gray-500">
|
|
1375
|
+
Try these interactive demos to experience NextPy capabilities
|
|
1376
|
+
</p>
|
|
1377
|
+
</div>
|
|
1378
|
+
|
|
1379
|
+
<div class="grid grid-cols-1 gap-8 mt-12 sm:grid-cols-2 lg:grid-cols-3">
|
|
1380
|
+
{/* JSX Demo */}
|
|
1381
|
+
<div class="p-6 transition-shadow bg-white rounded-lg shadow-lg hover:shadow-xl">
|
|
1382
|
+
<div class="text-center">
|
|
1383
|
+
<div class="flex items-center justify-center w-12 h-12 mx-auto mb-4 text-white bg-blue-500 rounded-md">
|
|
1384
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1385
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7m0 0v7l9-11h-7z" />
|
|
1386
|
+
</svg>
|
|
1387
|
+
</div>
|
|
1388
|
+
<h3 class="mb-2 text-lg font-medium text-gray-900">JSX Components</h3>
|
|
1389
|
+
<p class="mb-4 text-sm text-gray-500">Interactive component demo</p>
|
|
1390
|
+
<button onclick="alert('Hello from JSX!')" class="w-full px-4 py-2 text-white transition-colors bg-blue-500 rounded hover:bg-blue-600">
|
|
1391
|
+
Try JSX Demo
|
|
1392
|
+
</button>
|
|
1393
|
+
</div>
|
|
1394
|
+
|
|
1395
|
+
{/* Tailwind Demo */}
|
|
1396
|
+
<div class="p-6 transition-shadow bg-white rounded-lg shadow-lg hover:shadow-xl">
|
|
1397
|
+
<div class="text-center">
|
|
1398
|
+
<div class="flex items-center justify-center w-12 h-12 mx-auto mb-4 text-white bg-purple-500 rounded-md">
|
|
1399
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1400
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7" />
|
|
1401
|
+
</svg>
|
|
1402
|
+
</div>
|
|
1403
|
+
<h3 class="mb-2 text-lg font-medium text-gray-900">Tailwind CSS</h3>
|
|
1404
|
+
<p class="mb-4 text-sm text-gray-500">Beautiful styling with utility classes</p>
|
|
1405
|
+
<button class="w-full px-4 py-2 text-white transition-colors bg-purple-500 rounded hover:bg-purple-600">
|
|
1406
|
+
Try Tailwind Demo
|
|
1407
|
+
</button>
|
|
1408
|
+
</div>
|
|
1409
|
+
|
|
1410
|
+
{/* API Demo */}
|
|
1411
|
+
<div class="p-6 transition-shadow bg-white rounded-lg shadow-lg hover:shadow-xl">
|
|
1412
|
+
<div class="text-center">
|
|
1413
|
+
<div class="flex items-center justify-center w-12 h-12 mx-auto mb-4 text-white bg-green-500 rounded-md">
|
|
1414
|
+
<svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1415
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 2 9 9 0 0118 0z" />
|
|
1416
|
+
</svg>
|
|
1417
|
+
</div>
|
|
1418
|
+
<h3 class="mb-2 text-lg font-medium text-gray-900">API Integration</h3>
|
|
1419
|
+
<p class="mb-4 text-sm text-gray-500">RESTful API endpoints</p>
|
|
1420
|
+
<button class="w-full px-4 py-2 text-white transition-colors bg-green-500 rounded hover:bg-green-600">
|
|
1421
|
+
Try API Demo
|
|
1422
|
+
</button>
|
|
1423
|
+
</div>
|
|
1424
|
+
</div>
|
|
1425
|
+
</div>
|
|
1426
|
+
</div>
|
|
1427
|
+
</div>
|
|
1428
|
+
|
|
1429
|
+
{/* Stats Section */}
|
|
1430
|
+
<div class="bg-blue-600">
|
|
1431
|
+
<div class="px-4 py-12 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
|
1432
|
+
<div class="grid grid-cols-2 gap-8 lg:grid-cols-4">
|
|
1433
|
+
<div class="text-center">
|
|
1434
|
+
<div class="text-3xl font-extrabold text-white">10x</div>
|
|
1435
|
+
<div class="mt-2 text-lg text-blue-200">Faster Development</div>
|
|
1436
|
+
</div>
|
|
1437
|
+
<div class="text-center">
|
|
1438
|
+
<div class="text-3xl font-extrabold text-white">100%</div>
|
|
1439
|
+
<div class="mt-2 text-lg text-blue-200">Python Compatible</div>
|
|
1440
|
+
</div>
|
|
1441
|
+
<div class="text-center">
|
|
1442
|
+
<div class="text-3xl font-extrabold text-white">JSX</div>
|
|
1443
|
+
<div class="mt-2 text-lg text-blue-200">Modern Syntax</div>
|
|
1444
|
+
</div>
|
|
1445
|
+
<div class="text-center">
|
|
1446
|
+
<div class="text-3xl font-extrabold text-white">∞</div>
|
|
1447
|
+
<div class="mt-2 text-lg text-blue-200">Infinite Possibilities</div>
|
|
1448
|
+
</div>
|
|
1449
|
+
</div>
|
|
1450
|
+
</div>
|
|
1451
|
+
</div>
|
|
1452
|
+
|
|
1453
|
+
{/* Footer */}
|
|
1454
|
+
<footer class="bg-gray-900">
|
|
1455
|
+
<div class="px-4 py-8 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
|
1456
|
+
<div class="flex flex-col items-center space-y-4">
|
|
1457
|
+
<p class="text-base text-center text-gray-400">
|
|
1458
|
+
Built with ❤️ using NextPy Framework
|
|
1459
|
+
</p>
|
|
1460
|
+
<div class="flex space-x-6">
|
|
1461
|
+
<a href="/" class="text-gray-400 hover:text-gray-300">Home</a>
|
|
1462
|
+
<a href="/features" class="text-gray-400 hover:text-gray-300">Features</a>
|
|
1463
|
+
<a href="/docs" class="text-gray-400 hover:text-gray-300">Documentation</a>
|
|
1464
|
+
<a href="https://github.com/nextpy/nextpy" class="text-gray-400 hover:text-gray-300">GitHub</a>
|
|
1465
|
+
</div>
|
|
1466
|
+
</div>
|
|
1467
|
+
</div>
|
|
1468
|
+
</footer>
|
|
1469
|
+
</div>
|
|
1470
|
+
)
|
|
1471
|
+
|
|
1472
|
+
def getServerSideProps(context):
|
|
1473
|
+
return {
|
|
1474
|
+
"props": {
|
|
1475
|
+
"title": "About NextPy",
|
|
1476
|
+
"description": "The Python web framework that brings React-like development to Python"
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
default = About
|
|
1481
|
+
''')
|
|
1482
|
+
click.echo(" Created: pages/about.py (enhanced interactive page)")
|
|
1483
|
+
|
|
1484
|
+
# Create interactive demo pages
|
|
1485
|
+
(project_dir / "pages" / "interactive.py").write_text('''"""Interactive Demo Page"""
|
|
1486
|
+
|
|
1487
|
+
def InteractiveDemo(props=None):
|
|
1488
|
+
"""Interactive demo showcasing NextPy capabilities"""
|
|
1489
|
+
return (
|
|
1490
|
+
<div class="min-h-screen py-12 bg-gradient-to-br from-indigo-50 to-purple-100">
|
|
1491
|
+
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
|
1492
|
+
<h1 class="mb-12 text-4xl font-extrabold text-center text-gray-900">
|
|
1493
|
+
Interactive NextPy Demos
|
|
1494
|
+
</h1>
|
|
1495
|
+
|
|
1496
|
+
<div class="grid grid-cols-1 gap-8 md:grid-cols-2 lg:grid-cols-3">
|
|
1497
|
+
{/* Counter Demo */}
|
|
1498
|
+
<div class="p-6 bg-white shadow-lg rounded-xl">
|
|
1499
|
+
<h2 class="mb-4 text-2xl font-bold text-gray-900">Live Counter</h2>
|
|
1500
|
+
<div class="text-center">
|
|
1501
|
+
<div class="mb-4 text-6xl font-bold text-blue-600" id="counter">0</div>
|
|
1502
|
+
<div class="space-x-4">
|
|
1503
|
+
<button onclick="updateCounter(-1)" class="px-6 py-3 text-white transition-colors bg-red-500 rounded-lg hover:bg-red-600">
|
|
1504
|
+
-
|
|
1505
|
+
</button>
|
|
1506
|
+
<button onclick="updateCounter(1)" class="px-6 py-3 text-white transition-colors bg-green-500 rounded-lg hover:bg-green-600">
|
|
1507
|
+
+
|
|
1508
|
+
</button>
|
|
1509
|
+
<button onclick="resetCounter()" class="px-6 py-3 text-white transition-colors bg-gray-500 rounded-lg hover:bg-gray-600">
|
|
1510
|
+
Reset
|
|
1511
|
+
</button>
|
|
1512
|
+
</div>
|
|
1513
|
+
</div>
|
|
1514
|
+
</div>
|
|
1515
|
+
|
|
1516
|
+
{/* Todo List Demo */}
|
|
1517
|
+
<div class="p-6 bg-white shadow-lg rounded-xl">
|
|
1518
|
+
<h2 class="mb-4 text-2xl font-bold text-gray-900">Todo List</h2>
|
|
1519
|
+
<div class="space-y-4">
|
|
1520
|
+
<div class="flex space-x-2">
|
|
1521
|
+
<input type="text" id="todoInput" placeholder="Add a new todo..." class="flex-1 px-4 py-2 border border-gray-300 rounded-lg" />
|
|
1522
|
+
<button onclick="addTodo()" class="px-6 py-2 text-white transition-colors bg-blue-500 rounded-lg hover:bg-blue-600">
|
|
1523
|
+
Add
|
|
1524
|
+
</button>
|
|
1525
|
+
</div>
|
|
1526
|
+
<ul id="todoList" class="space-y-2">
|
|
1527
|
+
{/* Todos will be added here dynamically */}
|
|
1528
|
+
</ul>
|
|
1529
|
+
</div>
|
|
1530
|
+
</div>
|
|
1531
|
+
|
|
1532
|
+
{/* Color Picker Demo */}
|
|
1533
|
+
<div class="p-6 bg-white shadow-lg rounded-xl">
|
|
1534
|
+
<h2 class="mb-4 text-2xl font-bold text-gray-900">Color Picker</h2>
|
|
1535
|
+
<div class="space-y-4">
|
|
1536
|
+
<input type="color" id="colorPicker" class="w-full h-20 rounded-lg cursor-pointer" />
|
|
1537
|
+
<div id="colorDisplay" class="p-4 font-mono text-lg text-center bg-gray-100 rounded-lg">
|
|
1538
|
+
Selected: #3B82F6
|
|
1539
|
+
</div>
|
|
1540
|
+
</div>
|
|
1541
|
+
</div>
|
|
1542
|
+
|
|
1543
|
+
{/* Form Validation Demo */}
|
|
1544
|
+
<div class="p-6 bg-white shadow-lg rounded-xl">
|
|
1545
|
+
<h2 class="mb-4 text-2xl font-bold text-gray-900">Form Validation</h2>
|
|
1546
|
+
<form onsubmit="validateForm(event)" class="space-y-4">
|
|
1547
|
+
<div>
|
|
1548
|
+
<label class="block mb-2 text-sm font-medium text-gray-700">Email</label>
|
|
1549
|
+
<input type="email" id="email" required class="w-full px-4 py-2 border border-gray-300 rounded-lg" placeholder="you@example.com" />
|
|
1550
|
+
</div>
|
|
1551
|
+
<div>
|
|
1552
|
+
<label class="block mb-2 text-sm font-medium text-gray-700">Password</label>
|
|
1553
|
+
<input type="password" id="password" required minlength="6" class="w-full px-4 py-2 border border-gray-300 rounded-lg" placeholder="•••••••••" />
|
|
1554
|
+
</div>
|
|
1555
|
+
<button type="submit" class="w-full px-6 py-3 text-white transition-colors bg-blue-500 rounded-lg hover:bg-blue-600">
|
|
1556
|
+
Validate & Submit
|
|
1557
|
+
</button>
|
|
1558
|
+
</form>
|
|
1559
|
+
<div id="validationResult" class="hidden p-4 mt-4 rounded-lg">
|
|
1560
|
+
{/* Validation results will appear here */}
|
|
1561
|
+
</div>
|
|
1562
|
+
</div>
|
|
1563
|
+
</div>
|
|
1564
|
+
</div>
|
|
1565
|
+
</div>
|
|
1566
|
+
)
|
|
1567
|
+
|
|
1568
|
+
def getServerSideProps(context):
|
|
1569
|
+
return {"props": {}}
|
|
1570
|
+
|
|
1571
|
+
default = InteractiveDemo
|
|
1572
|
+
''')
|
|
1573
|
+
click.echo(" Created: pages/interactive.py (interactive demos)")
|
|
1574
|
+
|
|
1575
|
+
# Create features page
|
|
1576
|
+
(project_dir / "pages" / "features.py").write_text('''"""Features Page"""
|
|
1577
|
+
|
|
1578
|
+
def Features(props=None):
|
|
1579
|
+
"""Comprehensive features showcase"""
|
|
1580
|
+
return (
|
|
1581
|
+
<div class="min-h-screen bg-gray-50">
|
|
1582
|
+
<div class="px-4 py-16 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
|
1583
|
+
<div class="mb-16 text-center">
|
|
1584
|
+
<h1 class="text-4xl font-extrabold text-gray-900">
|
|
1585
|
+
NextPy Features
|
|
1586
|
+
</h1>
|
|
1587
|
+
<p class="mt-4 text-xl text-gray-600">
|
|
1588
|
+
Everything you need to build modern web applications
|
|
1589
|
+
</p>
|
|
1590
|
+
</div>
|
|
1591
|
+
|
|
1592
|
+
<div class="grid grid-cols-1 gap-12 md:grid-cols-2">
|
|
1593
|
+
<div class="space-y-12">
|
|
1594
|
+
{/* Core Features */}
|
|
1595
|
+
<div>
|
|
1596
|
+
<h2 class="mb-6 text-2xl font-bold text-gray-900">Core Features</h2>
|
|
1597
|
+
<div class="space-y-6">
|
|
1598
|
+
<div class="flex items-start space-x-4">
|
|
1599
|
+
<div class="flex-shrink-0 w-6 h-6 text-green-500">
|
|
1600
|
+
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1601
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
|
1602
|
+
</svg>
|
|
1603
|
+
</div>
|
|
1604
|
+
<div>
|
|
1605
|
+
<h3 class="text-lg font-medium text-gray-900">True JSX Components</h3>
|
|
1606
|
+
<p class="mt-2 text-gray-600">Write React-like components with JSX syntax directly in Python</p>
|
|
1607
|
+
</div>
|
|
1608
|
+
</div>
|
|
1609
|
+
|
|
1610
|
+
<div class="flex items-start space-x-4">
|
|
1611
|
+
<div class="flex-shrink-0 w-6 h-6 text-blue-500">
|
|
1612
|
+
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1613
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7" />
|
|
1614
|
+
</svg>
|
|
1615
|
+
</div>
|
|
1616
|
+
<div>
|
|
1617
|
+
<h3 class="text-lg font-medium text-gray-900">File-based Routing</h3>
|
|
1618
|
+
<p class="mt-2 text-gray-600">Automatic route discovery with dynamic routes support</p>
|
|
1619
|
+
</div>
|
|
1620
|
+
</div>
|
|
1621
|
+
|
|
1622
|
+
<div class="flex items-start space-x-4">
|
|
1623
|
+
<div class="flex-shrink-0 w-6 h-6 text-purple-500">
|
|
1624
|
+
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1625
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 2 9 9 0 0118 0z" />
|
|
1626
|
+
</svg>
|
|
1627
|
+
</div>
|
|
1628
|
+
<div>
|
|
1629
|
+
<h3 class="text-lg font-medium text-gray-900">Tailwind CSS Integration</h3>
|
|
1630
|
+
<p class="mt-2 text-gray-600">Built-in Tailwind CSS v4 with PostCSS compilation</p>
|
|
1631
|
+
</div>
|
|
1632
|
+
</div>
|
|
1633
|
+
</div>
|
|
1634
|
+
</div>
|
|
1635
|
+
|
|
1636
|
+
{/* Development Features */}
|
|
1637
|
+
<div>
|
|
1638
|
+
<h2 class="mb-6 text-2xl font-bold text-gray-900">Development Experience</h2>
|
|
1639
|
+
<div class="space-y-6">
|
|
1640
|
+
<div class="flex items-start space-x-4">
|
|
1641
|
+
<div class="flex-shrink-0 w-6 h-6 text-red-500">
|
|
1642
|
+
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1643
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v16h16V4H4z" />
|
|
1644
|
+
</svg>
|
|
1645
|
+
</div>
|
|
1646
|
+
<div>
|
|
1647
|
+
<h3 class="text-lg font-medium text-gray-900">Hot Reload</h3>
|
|
1648
|
+
<p class="mt-2 text-gray-600">Instant hot reload when saving files with Watchdog</p>
|
|
1649
|
+
</div>
|
|
1650
|
+
</div>
|
|
1651
|
+
|
|
1652
|
+
<div class="flex items-start space-x-4">
|
|
1653
|
+
<div class="flex-shrink-0 w-6 h-6 text-yellow-500">
|
|
1654
|
+
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1655
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.75 5H6.25v13l4.5 4.5z" />
|
|
1656
|
+
</svg>
|
|
1657
|
+
</div>
|
|
1658
|
+
<div>
|
|
1659
|
+
<h3 class="text-lg font-medium text-gray-900">Debug Mode</h3>
|
|
1660
|
+
<p class="mt-2 text-gray-600">Comprehensive debugging with detailed error pages</p>
|
|
1661
|
+
</div>
|
|
1662
|
+
</div>
|
|
1663
|
+
|
|
1664
|
+
<div class="flex items-start space-x-4">
|
|
1665
|
+
<div class="flex-shrink-0 w-6 h-6 text-indigo-500">
|
|
1666
|
+
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
1667
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
1668
|
+
</svg>
|
|
1669
|
+
</div>
|
|
1670
|
+
<div>
|
|
1671
|
+
<h3 class="text-lg font-medium text-gray-900">VS Code Integration</h3>
|
|
1672
|
+
<p class="mt-2 text-gray-600">Full VS Code support with extensions and IntelliSense</p>
|
|
1673
|
+
</div>
|
|
1674
|
+
</div>
|
|
1675
|
+
</div>
|
|
1676
|
+
</div>
|
|
1677
|
+
</div>
|
|
1678
|
+
</div>
|
|
1679
|
+
</div>
|
|
1680
|
+
)
|
|
1681
|
+
|
|
1682
|
+
def getServerSideProps(context):
|
|
1683
|
+
return {"props": {}}
|
|
1684
|
+
|
|
1685
|
+
default = Features
|
|
1686
|
+
''')
|
|
1687
|
+
click.echo(" Created: pages/features.py (features showcase)")
|
|
1688
|
+
|
|
1689
|
+
# Create getting started guide
|
|
1690
|
+
(project_dir / "pages" / "getting-started.py").write_text('''"""Getting Started Guide"""
|
|
1691
|
+
|
|
1692
|
+
def GettingStarted(props=None):
|
|
1693
|
+
"""Comprehensive getting started guide"""
|
|
1694
|
+
return (
|
|
1695
|
+
<div class="min-h-screen bg-white">
|
|
1696
|
+
<div class="max-w-4xl px-4 py-16 mx-auto sm:px-6 lg:px-8">
|
|
1697
|
+
<div class="mb-16 text-center">
|
|
1698
|
+
<h1 class="text-4xl font-extrabold text-gray-900">
|
|
1699
|
+
Getting Started with NextPy
|
|
1700
|
+
</h1>
|
|
1701
|
+
<p class="mt-4 text-xl text-gray-600">
|
|
1702
|
+
Your journey to building amazing web apps starts here
|
|
1703
|
+
</p>
|
|
1704
|
+
</div>
|
|
1705
|
+
|
|
1706
|
+
<div class="space-y-16">
|
|
1707
|
+
{/* Step 1 */}
|
|
1708
|
+
<div class="p-8 rounded-lg bg-blue-50">
|
|
1709
|
+
<div class="flex items-center mb-4">
|
|
1710
|
+
<div class="flex items-center justify-center flex-shrink-0 w-8 h-8 font-bold text-white bg-blue-500 rounded-full">
|
|
1711
|
+
1
|
|
1712
|
+
</div>
|
|
1713
|
+
<h2 class="ml-4 text-2xl font-bold text-gray-900">Installation</h2>
|
|
1714
|
+
</div>
|
|
1715
|
+
<div class="ml-12 space-y-4">
|
|
1716
|
+
<div class="p-4 bg-white border-l-4 border-blue-500 rounded">
|
|
1717
|
+
<h3 class="mb-2 font-semibold text-gray-900">Install NextPy</h3>
|
|
1718
|
+
<code class="block p-2 text-sm bg-gray-100 rounded">pip install nextpy-framework</code>
|
|
1719
|
+
</div>
|
|
1720
|
+
<div class="p-4 bg-white border-l-4 border-blue-500 rounded">
|
|
1721
|
+
<h3 class="mb-2 font-semibold text-gray-900">Create New Project</h3>
|
|
1722
|
+
<code class="block p-2 text-sm bg-gray-100 rounded">nextpy create my-app</code>
|
|
1723
|
+
</div>
|
|
1724
|
+
</div>
|
|
1725
|
+
</div>
|
|
1726
|
+
|
|
1727
|
+
{/* Step 2 */}
|
|
1728
|
+
<div class="p-8 rounded-lg bg-green-50">
|
|
1729
|
+
<div class="flex items-center mb-4">
|
|
1730
|
+
<div class="flex items-center justify-center flex-shrink-0 w-8 h-8 font-bold text-white bg-green-500 rounded-full">
|
|
1731
|
+
2
|
|
1732
|
+
</div>
|
|
1733
|
+
<h2 class="ml-4 text-2xl font-bold text-gray-900">Project Structure</h2>
|
|
1734
|
+
</div>
|
|
1735
|
+
<div class="ml-12">
|
|
1736
|
+
<div class="p-4 bg-white border-l-4 border-green-500 rounded">
|
|
1737
|
+
<h3 class="mb-2 font-semibold text-gray-900">Navigate to Your Project</h3>
|
|
1738
|
+
<code class="block p-2 text-sm bg-gray-100 rounded">cd my-app</code>
|
|
1739
|
+
</div>
|
|
1740
|
+
<div class="p-4 mt-4 bg-white border-l-4 border-green-500 rounded">
|
|
1741
|
+
<h3 class="mb-2 font-semibold text-gray-900">Project Structure</h3>
|
|
1742
|
+
<pre class="p-4 overflow-x-auto text-sm bg-gray-100 rounded">
|
|
1743
|
+
{`my-app/
|
|
1744
|
+
├── pages/ # Your pages and API routes
|
|
1745
|
+
├── components/ # Reusable components
|
|
1746
|
+
├── templates/ # HTML templates
|
|
1747
|
+
├── public/ # Static assets
|
|
1748
|
+
├── styles.css # Tailwind CSS
|
|
1749
|
+
├── main.py # Application entry point
|
|
1750
|
+
└── requirements.txt # Python dependencies`}</pre>
|
|
1751
|
+
</div>
|
|
1752
|
+
</div>
|
|
1753
|
+
</div>
|
|
1754
|
+
|
|
1755
|
+
{/* Step 3 */}
|
|
1756
|
+
<div class="p-8 rounded-lg bg-purple-50">
|
|
1757
|
+
<div class="flex items-center mb-4">
|
|
1758
|
+
<div class="flex items-center justify-center flex-shrink-0 w-8 h-8 font-bold text-white bg-purple-500 rounded-full">
|
|
1759
|
+
3
|
|
1760
|
+
</div>
|
|
1761
|
+
<h2 class="ml-4 text-2xl font-bold text-gray-900">Development</h2>
|
|
1762
|
+
</div>
|
|
1763
|
+
<div class="ml-12 space-y-4">
|
|
1764
|
+
<div class="p-4 bg-white border-l-4 border-purple-500 rounded">
|
|
1765
|
+
<h3 class="mb-2 font-semibold text-gray-900">Start Development Server</h3>
|
|
1766
|
+
<code class="block p-2 text-sm bg-gray-100 rounded">nextpy dev</code>
|
|
1767
|
+
</div>
|
|
1768
|
+
<div class="p-4 bg-white border-l-4 border-purple-500 rounded">
|
|
1769
|
+
<h3 class="mb-2 font-semibold text-gray-900">Open Your Browser</h3>
|
|
1770
|
+
<code class="block p-2 text-sm bg-gray-100 rounded">http://localhost:8000</code>
|
|
1771
|
+
</div>
|
|
1772
|
+
</div>
|
|
1773
|
+
</div>
|
|
1774
|
+
|
|
1775
|
+
{/* Step 4 */}
|
|
1776
|
+
<div class="p-8 rounded-lg bg-yellow-50">
|
|
1777
|
+
<div class="flex items-center mb-4">
|
|
1778
|
+
<div class="flex items-center justify-center flex-shrink-0 w-8 h-8 font-bold text-white bg-yellow-500 rounded-full">
|
|
1779
|
+
4
|
|
1780
|
+
</div>
|
|
1781
|
+
<h2 class="ml-4 text-2xl font-bold text-gray-900">Build Your First Component</h2>
|
|
1782
|
+
</div>
|
|
1783
|
+
<div class="ml-12">
|
|
1784
|
+
<div class="p-4 bg-white border-l-4 border-yellow-500 rounded">
|
|
1785
|
+
<h3 class="mb-2 font-semibold text-gray-900">Create a Component</h3>
|
|
1786
|
+
<p class="mb-2 text-gray-600">Edit pages/index.py to create your first JSX component:</p>
|
|
1787
|
+
<pre class="p-4 overflow-x-auto text-sm text-green-400 bg-gray-900 rounded">
|
|
1788
|
+
{`def Home(props=None):
|
|
1789
|
+
return (
|
|
1790
|
+
<div class="flex items-center justify-center min-h-screen bg-blue-500">
|
|
1791
|
+
<h1 class="text-3xl font-bold text-white">
|
|
1792
|
+
Hello, NextPy!
|
|
1793
|
+
</h1>
|
|
1794
|
+
</div>
|
|
1795
|
+
)
|
|
1796
|
+
|
|
1797
|
+
default = Home`}</pre>
|
|
1798
|
+
</div>
|
|
1799
|
+
</div>
|
|
1800
|
+
</div>
|
|
1801
|
+
</div>
|
|
1802
|
+
</div>
|
|
1803
|
+
)
|
|
1804
|
+
|
|
1805
|
+
def getServerSideProps(context):
|
|
1806
|
+
return {"props": {}}
|
|
1807
|
+
|
|
1808
|
+
default = GettingStarted
|
|
1809
|
+
''')
|
|
1810
|
+
click.echo(" Created: pages/getting-started.py (comprehensive guide)")
|
|
1811
|
+
(project_dir / "components" / "ui" / "Button.py").write_text('''"""Button component"""
|
|
1812
|
+
|
|
1813
|
+
def Button(props = None):
|
|
1814
|
+
"""Reusable Button component"""
|
|
1815
|
+
props = props or {}
|
|
1816
|
+
|
|
1817
|
+
variant = props.get("variant", "default")
|
|
1818
|
+
children = props.get("children", "Button")
|
|
1819
|
+
className = props.get("className", "")
|
|
1820
|
+
|
|
1821
|
+
if variant == "primary":
|
|
1822
|
+
variant_class = "bg-blue-600 text-white hover:bg-blue-700 transform hover:scale-105 transition-all duration-200"
|
|
1823
|
+
elif variant == "secondary":
|
|
1824
|
+
variant_class = "bg-gray-200 text-gray-900 hover:bg-gray-300 transform hover:scale-105 transition-all duration-200"
|
|
1825
|
+
elif variant == "success":
|
|
1826
|
+
variant_class = "bg-green-600 text-white hover:bg-green-700 transform hover:scale-105 transition-all duration-200"
|
|
1827
|
+
elif variant == "danger":
|
|
1828
|
+
variant_class = "bg-red-600 text-white hover:bg-red-700 transform hover:scale-105 transition-all duration-200"
|
|
1829
|
+
else:
|
|
1830
|
+
variant_class = "bg-gray-600 text-white hover:bg-gray-700 transform hover:scale-105 transition-all duration-200"
|
|
1831
|
+
|
|
1832
|
+
class_attr = f"px-6 py-3 rounded-lg font-medium transition-all duration-200 transform hover:scale-105 {variant_class} {className}"
|
|
1833
|
+
|
|
1834
|
+
return (
|
|
1835
|
+
<button class={class_attr}
|
|
1836
|
+
id={props.get("id")}
|
|
1837
|
+
disabled={props.get("disabled", False)}
|
|
1838
|
+
onclick={props.get("onClick", "")}>
|
|
1839
|
+
{children}
|
|
1840
|
+
</button>
|
|
1841
|
+
)
|
|
1842
|
+
|
|
1843
|
+
default = Button
|
|
1844
|
+
''')
|
|
1845
|
+
click.echo(" Created: components/ui/Button.py (enhanced interactive)")
|
|
1846
|
+
|
|
1847
|
+
|
|
1848
|
+
# Create a Layout component
|
|
1849
|
+
(project_dir / "components" / "layout" / "Layout.py").write_text('''"""Layout component"""
|
|
1850
|
+
|
|
1851
|
+
def Layout(props = None):
|
|
1852
|
+
"""Layout component wrapper"""
|
|
1853
|
+
props = props or {}
|
|
1854
|
+
|
|
1855
|
+
title = props.get("title", "NextPy App")
|
|
1856
|
+
children = props.get("children", "")
|
|
1857
|
+
|
|
1858
|
+
return (
|
|
1859
|
+
<div class="flex flex-col min-h-screen">
|
|
1860
|
+
<header class="bg-white shadow-sm">
|
|
1861
|
+
<div class="px-4 py-4 mx-auto max-w-7xl">
|
|
1862
|
+
<div class="flex items-center justify-between">
|
|
1863
|
+
<h1 class="text-2xl font-bold text-gray-900">{title}</h1>
|
|
1864
|
+
<nav class="flex space-x-4">
|
|
1865
|
+
<a href="/" class="text-gray-600 hover:text-gray-900">Home</a>
|
|
1866
|
+
<a href="/about" class="text-gray-600 hover:text-gray-900">About</a>
|
|
1867
|
+
</nav>
|
|
1868
|
+
</div>
|
|
1869
|
+
</div>
|
|
1870
|
+
</header>
|
|
1871
|
+
<main class="flex-1">
|
|
1872
|
+
{children}
|
|
1873
|
+
</main>
|
|
1874
|
+
<footer class="mt-auto bg-gray-100">
|
|
1875
|
+
<div class="px-4 py-6 mx-auto text-center text-gray-600 max-w-7xl">
|
|
1876
|
+
<p>© 2025 NextPy Framework. All rights reserved.</p>
|
|
1877
|
+
</div>
|
|
1878
|
+
</footer>
|
|
1879
|
+
</div>
|
|
1880
|
+
)
|
|
1881
|
+
|
|
1882
|
+
default = Layout
|
|
1883
|
+
''')
|
|
1884
|
+
click.echo(" Created: components/layout/Layout.py")
|
|
1885
|
+
|
|
1886
|
+
# Create VS Code configuration for JSX support
|
|
1887
|
+
(project_dir / ".vscode").mkdir(exist_ok=True)
|
|
1888
|
+
(project_dir / ".vscode" / "settings.json").write_text('''{
|
|
1889
|
+
"files.associations": {
|
|
1890
|
+
"*.py": "python",
|
|
1891
|
+
"*.py.jsx": "python",
|
|
1892
|
+
"*.jsx": "javascriptreact"
|
|
1893
|
+
},
|
|
1894
|
+
"emmet.includeLanguages": {
|
|
1895
|
+
"python": "html",
|
|
1896
|
+
"javascriptreact": "html",
|
|
1897
|
+
"typescriptreact": "html"
|
|
1898
|
+
},
|
|
1899
|
+
"emmet.triggerExpansionOnTab": true,
|
|
1900
|
+
"typescript.preferences.includePackageJsonAutoImports": "on",
|
|
1901
|
+
"editor.quickSuggestions": {
|
|
1902
|
+
"strings": true
|
|
1903
|
+
},
|
|
1904
|
+
"editor.suggestSelection": "first",
|
|
1905
|
+
"editor.wordBasedSuggestions": true,
|
|
1906
|
+
"editor.snippetSuggestions": "top",
|
|
1907
|
+
"editor.parameterHints": {
|
|
1908
|
+
"enabled": true
|
|
1909
|
+
},
|
|
1910
|
+
"editor.snippetSuggestions": "top",
|
|
1911
|
+
"html.autoClosingTags": true,
|
|
1912
|
+
"css.autoClosingTags": true,
|
|
1913
|
+
"javascript.autoClosingTags": true,
|
|
1914
|
+
"typescript.autoClosingTags": true,
|
|
1915
|
+
"editor.autoClosingBrackets": "always",
|
|
1916
|
+
"editor.autoClosingQuotes": "always",
|
|
1917
|
+
"editor.formatOnSave": true,
|
|
1918
|
+
"editor.codeActionsOnSave": {
|
|
1919
|
+
"source.fixAll.eslint": true,
|
|
1920
|
+
"source.organizeImports": true
|
|
1921
|
+
},
|
|
1922
|
+
"emmet.preferences": {
|
|
1923
|
+
"css.property.endWithSemicolon": true,
|
|
1924
|
+
"css.value.unit": "rem"
|
|
1925
|
+
},
|
|
1926
|
+
"files.exclude": {
|
|
1927
|
+
"**/__pycache__": true,
|
|
1928
|
+
"**/*.pyc": true,
|
|
1929
|
+
"**/node_modules": true,
|
|
1930
|
+
"**/out": true,
|
|
1931
|
+
"**/.next": true,
|
|
1932
|
+
"**/.pytest_cache": true,
|
|
1933
|
+
"**/.mypy_cache": true
|
|
1934
|
+
},
|
|
1935
|
+
"search.exclude": {
|
|
1936
|
+
"**/node_modules": true,
|
|
1937
|
+
"**/out": true,
|
|
1938
|
+
"**/.next": true,
|
|
1939
|
+
"**/__pycache__": true,
|
|
1940
|
+
"**/.pytest_cache": true,
|
|
1941
|
+
"**/.mypy_cache": true
|
|
1942
|
+
},
|
|
1943
|
+
"python.linting.enabled": true,
|
|
1944
|
+
"python.linting.pylintEnabled": false,
|
|
1945
|
+
"python.linting.flake8Enabled": false,
|
|
1946
|
+
"python.linting.pylintArgs": [
|
|
1947
|
+
"--disable=C0114,C0115,C0116,E1132,E1131,E1130"
|
|
1948
|
+
],
|
|
1949
|
+
"python.formatting.provider": "black",
|
|
1950
|
+
"[python]": {
|
|
1951
|
+
"editor.defaultFormatter": "ms-python.black-formatter",
|
|
1952
|
+
"editor.formatOnSave": true,
|
|
1953
|
+
"editor.rulers": [88],
|
|
1954
|
+
"editor.tabSize": 4,
|
|
1955
|
+
"editor.insertSpaces": true
|
|
1956
|
+
}
|
|
1957
|
+
}''')
|
|
1958
|
+
click.echo(" Created: .vscode/settings.json")
|
|
1959
|
+
|
|
1960
|
+
(project_dir / ".vscode" / "extensions.json").write_text('''{
|
|
1961
|
+
"recommendations": [
|
|
1962
|
+
"ms-python.python",
|
|
1963
|
+
"ms-python.vscode-pylance",
|
|
1964
|
+
"bradlc.vscode-tailwindcss",
|
|
1965
|
+
"esbenp.prettier-vscode",
|
|
1966
|
+
"ms-vscode.vscode-json",
|
|
1967
|
+
"formulahendry.auto-rename-tag",
|
|
1968
|
+
"christian-kohler.path-intellisense",
|
|
1969
|
+
"ms-vscode.vscode-html-css-class-completion",
|
|
1970
|
+
"ms-vscode.vscode-emmet",
|
|
1971
|
+
"ms-vscode.vscode-eslint",
|
|
1972
|
+
"dbaeumer.vscode-eslint",
|
|
1973
|
+
"ms-vscode.vscode-typescript-next",
|
|
1974
|
+
"ritwickdey.liveserver",
|
|
1975
|
+
"ms-vscode.vscode-jest",
|
|
1976
|
+
"esbenp.prettier-vscode",
|
|
1977
|
+
"streetsidesoftware.code-spell-checker",
|
|
1978
|
+
"gruntfuggly.todo-tree",
|
|
1979
|
+
"ms-vscode.vscode-git-graph",
|
|
1980
|
+
"eamodio.gitlens",
|
|
1981
|
+
"ms-vscode.vscode-docker",
|
|
1982
|
+
"ms-vscode.remote-explorer",
|
|
1983
|
+
"ms-vscode-remote.remote-containers",
|
|
1984
|
+
"ms-vscode.vscode-remote-wsl",
|
|
1985
|
+
"redhat.vscode-yaml",
|
|
1986
|
+
"ms-vscode.vscode-markdown",
|
|
1987
|
+
"yzhang.markdown-all-in-one",
|
|
1988
|
+
"shd101wyy.markdown-preview-enhanced",
|
|
1989
|
+
"ms-vscode.vscode-python",
|
|
1990
|
+
"kevinrose.vsc-python-indent",
|
|
1991
|
+
"ms-python.black-formatter",
|
|
1992
|
+
"ms-python.isort",
|
|
1993
|
+
"ms-python.flake8",
|
|
1994
|
+
"ms-python.mypy-type-checker"
|
|
1995
|
+
]
|
|
1996
|
+
}''')
|
|
1997
|
+
click.echo(" Created: .vscode/extensions.json")
|
|
1998
|
+
|
|
1999
|
+
# Create comprehensive API examples
|
|
2000
|
+
(project_dir / "pages" / "api" / "hello.py").write_text('''"""API example - Hello endpoint"""
|
|
2001
|
+
|
|
2002
|
+
from fastapi import Request
|
|
2003
|
+
|
|
2004
|
+
async def get(request: Request):
|
|
2005
|
+
"""GET /api/hello"""
|
|
2006
|
+
return {"message": "Hello from NextPy API!", "status": "success"}
|
|
2007
|
+
|
|
2008
|
+
async def post(request: Request):
|
|
2009
|
+
"""POST /api/hello"""
|
|
2010
|
+
data = await request.json()
|
|
2011
|
+
return {"message": "POST request received", "data": data, "status": "success"}
|
|
2012
|
+
''')
|
|
2013
|
+
click.echo(" Created: pages/api/hello.py")
|
|
2014
|
+
|
|
2015
|
+
(project_dir / "pages" / "api" / "users" / "index.py").write_text('''"""API example - Users index"""
|
|
2016
|
+
|
|
2017
|
+
from fastapi import Request
|
|
2018
|
+
|
|
2019
|
+
async def get(request: Request):
|
|
2020
|
+
"""GET /api/users - List all users"""
|
|
2021
|
+
users = [
|
|
2022
|
+
{"id": 1, "name": "John Doe", "email": "john@example.com"},
|
|
2023
|
+
{"id": 2, "name": "Jane Smith", "email": "jane@example.com"},
|
|
2024
|
+
]
|
|
2025
|
+
return {"users": users, "total": len(users)}
|
|
2026
|
+
|
|
2027
|
+
async def post(request: Request):
|
|
2028
|
+
"""POST /api/users - Create new user"""
|
|
2029
|
+
data = await request.json()
|
|
2030
|
+
# In a real app, you'd save to database
|
|
2031
|
+
new_user = {
|
|
2032
|
+
"id": 3,
|
|
2033
|
+
"name": data.get("name"),
|
|
2034
|
+
"email": data.get("email")
|
|
2035
|
+
}
|
|
2036
|
+
return {"user": new_user, "message": "User created successfully"}
|
|
2037
|
+
''')
|
|
2038
|
+
click.echo(" Created: pages/api/users/index.py")
|
|
2039
|
+
|
|
2040
|
+
(project_dir / "pages" / "api" / "users" / "[id].py").write_text('''"""API example - Dynamic user route"""
|
|
2041
|
+
|
|
2042
|
+
from fastapi import Request
|
|
2043
|
+
|
|
2044
|
+
async def get(request: Request, id: int):
|
|
2045
|
+
"""GET /api/users/{id} - Get user by ID"""
|
|
2046
|
+
users = {
|
|
2047
|
+
1: {"id": 1, "name": "John Doe", "email": "john@example.com"},
|
|
2048
|
+
2: {"id": 2, "name": "Jane Smith", "email": "jane@example.com"},
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
if id in users:
|
|
2052
|
+
return {"user": users[id]}
|
|
2053
|
+
else:
|
|
2054
|
+
return {"error": "User not found"}, 404
|
|
2055
|
+
|
|
2056
|
+
async def put(request: Request, id: int):
|
|
2057
|
+
"""PUT /api/users/{id} - Update user"""
|
|
2058
|
+
data = await request.json()
|
|
2059
|
+
return {"message": f"User {id} updated", "data": data}
|
|
2060
|
+
|
|
2061
|
+
async def delete(request: Request, id: int):
|
|
2062
|
+
"""DELETE /api/users/{id} - Delete user"""
|
|
2063
|
+
return {"message": f"User {id} deleted successfully"}
|
|
2064
|
+
''')
|
|
2065
|
+
click.echo(" Created: pages/api/users/[id].py")
|
|
2066
|
+
|
|
2067
|
+
# Create database models
|
|
2068
|
+
(project_dir / "models" / "User.py").write_text('''"""User model example"""
|
|
2069
|
+
|
|
2070
|
+
from sqlalchemy import Column, Integer, String, DateTime, Boolean
|
|
2071
|
+
from sqlalchemy.ext.declarative import declarative_base
|
|
2072
|
+
from datetime import datetime
|
|
2073
|
+
|
|
2074
|
+
Base = declarative_base()
|
|
2075
|
+
|
|
2076
|
+
class User(Base):
|
|
2077
|
+
__tablename__ = "users"
|
|
2078
|
+
|
|
2079
|
+
id = Column(Integer, primary_key=True, index=True)
|
|
2080
|
+
name = Column(String(100), nullable=False)
|
|
2081
|
+
email = Column(String(100), unique=True, nullable=False)
|
|
2082
|
+
created_at = Column(DateTime, default=datetime.utcnow)
|
|
2083
|
+
is_active = Column(Boolean, default=True)
|
|
2084
|
+
|
|
2085
|
+
def __repr__(self):
|
|
2086
|
+
return f"<User(id={self.id}, name='{self.name}', email='{self.email}')>"
|
|
2087
|
+
''')
|
|
2088
|
+
click.echo(" Created: models/User.py")
|
|
2089
|
+
|
|
2090
|
+
# Create utility functions
|
|
2091
|
+
(project_dir / "utils" / "helpers.py").write_text('''"""Utility helper functions"""
|
|
2092
|
+
|
|
2093
|
+
import hashlib
|
|
2094
|
+
import secrets
|
|
2095
|
+
from datetime import datetime
|
|
2096
|
+
|
|
2097
|
+
def generate_secret_key(length: int = 32) -> str:
|
|
2098
|
+
"""Generate a secure secret key"""
|
|
2099
|
+
return secrets.token_urlsafe(length)
|
|
2100
|
+
|
|
2101
|
+
def hash_password(password: str) -> str:
|
|
2102
|
+
"""Hash a password using SHA-256"""
|
|
2103
|
+
return hashlib.sha256(password.encode()).hexdigest()
|
|
2104
|
+
|
|
2105
|
+
def format_date(date: datetime) -> str:
|
|
2106
|
+
"""Format datetime for display"""
|
|
2107
|
+
return date.strftime("%B %d, %Y at %I:%M %p")
|
|
2108
|
+
|
|
2109
|
+
def slugify(text: str) -> str:
|
|
2110
|
+
"""Convert text to URL-friendly slug"""
|
|
2111
|
+
return text.lower().replace(" ", "-").replace("_", "-")
|
|
2112
|
+
''')
|
|
2113
|
+
click.echo(" Created: utils/helpers.py")
|
|
2114
|
+
|
|
2115
|
+
# Create custom hooks
|
|
2116
|
+
(project_dir / "hooks" / "use_auth.py").write_text('''"""Authentication hook example"""
|
|
2117
|
+
|
|
2118
|
+
def use_auth(request):
|
|
2119
|
+
"""Example authentication hook"""
|
|
2120
|
+
# In a real app, you'd check tokens, sessions, etc.
|
|
2121
|
+
auth_header = request.headers.get("authorization")
|
|
2122
|
+
|
|
2123
|
+
if auth_header and auth_header.startswith("Bearer "):
|
|
2124
|
+
token = auth_header.split(" ")[1]
|
|
2125
|
+
# Validate token here
|
|
2126
|
+
return {"user": {"id": 1, "name": "Authenticated User"}, "token": token}
|
|
2127
|
+
|
|
2128
|
+
return {"user": None, "error": "No authentication provided"}
|
|
2129
|
+
''')
|
|
2130
|
+
click.echo(" Created: hooks/use_auth.py")
|
|
2131
|
+
|
|
2132
|
+
# Create middleware example
|
|
2133
|
+
(project_dir / "middleware" / "cors.py").write_text('''"""CORS middleware example"""
|
|
2134
|
+
|
|
2135
|
+
from fastapi import Request, Response
|
|
2136
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
2137
|
+
|
|
2138
|
+
def add_cors_middleware(app):
|
|
2139
|
+
"""Add CORS middleware to the app"""
|
|
2140
|
+
app.add_middleware(
|
|
2141
|
+
CORSMiddleware,
|
|
2142
|
+
allow_origins=["*"], # Configure appropriately for production
|
|
2143
|
+
allow_credentials=True,
|
|
2144
|
+
allow_methods=["*"],
|
|
2145
|
+
allow_headers=["*"],
|
|
2146
|
+
)
|
|
2147
|
+
return app
|
|
2148
|
+
''')
|
|
2149
|
+
click.echo(" Created: middleware/cors.py")
|
|
2150
|
+
|
|
2151
|
+
# Create test files
|
|
2152
|
+
(project_dir / "tests" / "test_api.py").write_text('''"""API tests example"""
|
|
2153
|
+
|
|
2154
|
+
import pytest
|
|
2155
|
+
from fastapi.testclient import TestClient
|
|
2156
|
+
from main import app
|
|
2157
|
+
|
|
2158
|
+
client = TestClient(app)
|
|
2159
|
+
|
|
2160
|
+
def test_hello_api():
|
|
2161
|
+
"""Test the hello API endpoint"""
|
|
2162
|
+
response = client.get("/api/hello")
|
|
2163
|
+
assert response.status_code == 200
|
|
2164
|
+
data = response.json()
|
|
2165
|
+
assert data["message"] == "Hello from NextPy API!"
|
|
2166
|
+
assert data["status"] == "success"
|
|
2167
|
+
|
|
2168
|
+
def test_users_api():
|
|
2169
|
+
"""Test the users API endpoint"""
|
|
2170
|
+
response = client.get("/api/users")
|
|
2171
|
+
assert response.status_code == 200
|
|
2172
|
+
data = response.json()
|
|
2173
|
+
assert "users" in data
|
|
2174
|
+
assert "total" in data
|
|
2175
|
+
assert len(data["users"]) == data["total"]
|
|
2176
|
+
''')
|
|
2177
|
+
click.echo(" Created: tests/test_api.py")
|
|
2178
|
+
|
|
2179
|
+
# Create documentation
|
|
2180
|
+
(project_dir / "docs" / "README.md").write_text('''# Project Documentation
|
|
2181
|
+
|
|
2182
|
+
## Overview
|
|
2183
|
+
This is a NextPy application with True JSX, Tailwind CSS, and comprehensive API support.
|
|
2184
|
+
|
|
2185
|
+
## Features
|
|
2186
|
+
- ✅ True JSX components in Python
|
|
2187
|
+
- ✅ Tailwind CSS integration
|
|
2188
|
+
- ✅ File-based routing
|
|
2189
|
+
- ✅ API routes with FastAPI
|
|
2190
|
+
- ✅ Database models with SQLAlchemy
|
|
2191
|
+
- ✅ Authentication hooks
|
|
2192
|
+
- ✅ CORS middleware
|
|
2193
|
+
- ✅ Comprehensive testing
|
|
2194
|
+
|
|
2195
|
+
## Project Structure
|
|
2196
|
+
```
|
|
2197
|
+
├── pages/ # File-based routing
|
|
2198
|
+
│ ├── api/ # API routes
|
|
2199
|
+
│ └── *.py # Page components
|
|
2200
|
+
├── components/ # Reusable components
|
|
2201
|
+
├── templates/ # HTML templates
|
|
2202
|
+
├── models/ # Database models
|
|
2203
|
+
├── utils/ # Utility functions
|
|
2204
|
+
├── hooks/ # Custom hooks
|
|
2205
|
+
├── middleware/ # Custom middleware
|
|
2206
|
+
├── tests/ # Test files
|
|
2207
|
+
├── public/ # Static assets
|
|
2208
|
+
├── styles/ # CSS files
|
|
2209
|
+
└── docs/ # Documentation
|
|
2210
|
+
```
|
|
2211
|
+
|
|
2212
|
+
## Getting Started
|
|
2213
|
+
1. Install dependencies: `pip install -r requirements.txt`
|
|
2214
|
+
2. Install Node.js deps: `npm install`
|
|
2215
|
+
3. Run development server: `nextpy dev`
|
|
2216
|
+
4. Open http://localhost:8000
|
|
2217
|
+
|
|
2218
|
+
## API Endpoints
|
|
2219
|
+
- `GET /api/hello` - Hello message
|
|
2220
|
+
- `GET /api/users` - List users
|
|
2221
|
+
- `POST /api/users` - Create user
|
|
2222
|
+
- `GET /api/users/{id}` - Get user by ID
|
|
2223
|
+
- `PUT /api/users/{id}` - Update user
|
|
2224
|
+
- `DELETE /api/users/{id}` - Delete user
|
|
2225
|
+
''')
|
|
2226
|
+
click.echo(" Created: docs/README.md")
|
|
2227
|
+
|
|
2228
|
+
(project_dir / "requirements.txt").write_text('''fastapi>=0.100.0
|
|
2229
|
+
uvicorn>=0.23.0
|
|
2230
|
+
jinja2>=3.1.0
|
|
2231
|
+
pydantic>=2.0.0
|
|
2232
|
+
pydantic-settings>=2.0.0
|
|
2233
|
+
click>=8.1.0
|
|
2234
|
+
watchdog>=3.0.0
|
|
2235
|
+
python-multipart>=0.0.6
|
|
2236
|
+
pillow>=10.0.0
|
|
2237
|
+
aiofiles>=23.0.0
|
|
2238
|
+
httpx>=0.24.0
|
|
2239
|
+
sqlalchemy>=2.0.0
|
|
2240
|
+
python-dotenv>=1.0.0
|
|
2241
|
+
pyjwt>=2.8.0
|
|
2242
|
+
markdown>=3.0.0 # Added markdown for documentation rendering
|
|
2243
|
+
''')
|
|
2244
|
+
click.echo(" Created: requirements.txt")
|
|
2245
|
+
|
|
2246
|
+
# Create main.py with Tailwind compilation (for pip-installed NextPy)
|
|
2247
|
+
(project_dir / "main.py").write_text('''"""NextPy ASGI Application Entry Point"""
|
|
2248
|
+
|
|
2249
|
+
import os
|
|
2250
|
+
import sys
|
|
2251
|
+
import subprocess
|
|
2252
|
+
from pathlib import Path
|
|
2253
|
+
|
|
2254
|
+
print(f"DEBUG: Current working directory: {Path.cwd()}")
|
|
2255
|
+
print(f"DEBUG: sys.path before modification: {sys.path}")
|
|
2256
|
+
|
|
2257
|
+
# Compile Tailwind CSS using PostCSS
|
|
2258
|
+
try:
|
|
2259
|
+
print("Compiling Tailwind CSS...")
|
|
2260
|
+
# Use PostCSS with new Tailwind plugin
|
|
2261
|
+
result = subprocess.run(
|
|
2262
|
+
["./node_modules/.bin/postcss", "styles.css", "-o", "public/tailwind.css"],
|
|
2263
|
+
capture_output=True,
|
|
2264
|
+
text=True,
|
|
2265
|
+
check=True
|
|
2266
|
+
)
|
|
2267
|
+
print("Tailwind CSS compiled successfully.")
|
|
2268
|
+
if result.stdout:
|
|
2269
|
+
print(f"CSS Output: {result.stdout[:200]}...")
|
|
2270
|
+
except subprocess.CalledProcessError as e:
|
|
2271
|
+
print(f"Error compiling Tailwind CSS: {e}")
|
|
2272
|
+
if e.stderr:
|
|
2273
|
+
print(f"CSS Error: {e.stderr}")
|
|
2274
|
+
except FileNotFoundError:
|
|
2275
|
+
print("Error: PostCSS not found. Make sure Node.js and Tailwind CSS are installed.")
|
|
2276
|
+
print("Install with: npm install postcss-cli @tailwindcss/postcss")
|
|
2277
|
+
|
|
2278
|
+
# Import NextPy modules (works when installed via pip)
|
|
2279
|
+
from nextpy.server.app import create_app
|
|
2280
|
+
from nextpy.db import init_db
|
|
2281
|
+
from nextpy.config import settings
|
|
2282
|
+
|
|
2283
|
+
# Initialize database
|
|
2284
|
+
try:
|
|
2285
|
+
init_db(settings["database_url"])
|
|
2286
|
+
print("Database initialized successfully.")
|
|
2287
|
+
except Exception as e:
|
|
2288
|
+
print(f"Warning: Database initialization failed: {e}")
|
|
2289
|
+
|
|
2290
|
+
# Create NextPy app
|
|
2291
|
+
app = create_app(
|
|
2292
|
+
pages_dir="pages",
|
|
2293
|
+
templates_dir="templates",
|
|
2294
|
+
public_dir="public",
|
|
2295
|
+
out_dir="out",
|
|
2296
|
+
debug=settings["debug"],
|
|
2297
|
+
)
|
|
2298
|
+
|
|
2299
|
+
# Add health check endpoint
|
|
2300
|
+
@app.get("/health")
|
|
2301
|
+
async def health_check():
|
|
2302
|
+
return {"status": "healthy", "framework": "NextPy"}
|
|
2303
|
+
|
|
2304
|
+
# Root endpoint
|
|
2305
|
+
@app.get("/")
|
|
2306
|
+
async def root():
|
|
2307
|
+
return {"message": "NextPy is running!", "docs": "/docs"}
|
|
2308
|
+
|
|
2309
|
+
if __name__ == "__main__":
|
|
2310
|
+
import uvicorn
|
|
2311
|
+
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|
|
2312
|
+
''')
|
|
2313
|
+
click.echo(" Created: main.py (pip-compatible)")
|
|
2314
|
+
|
|
2315
|
+
# Create .env file for development
|
|
2316
|
+
(project_dir / ".env").write_text('''# NextPy Development Environment
|
|
2317
|
+
DEVELOPMENT=true
|
|
2318
|
+
DEBUG=true
|
|
2319
|
+
NEXTPY_DEBUG=true
|
|
2320
|
+
|
|
2321
|
+
# Server Configuration
|
|
2322
|
+
HOST=0.0.0.0
|
|
2323
|
+
PORT=8000
|
|
2324
|
+
|
|
2325
|
+
# Database (if needed)
|
|
2326
|
+
DATABASE_URL=sqlite:///./app.db
|
|
2327
|
+
|
|
2328
|
+
# Secret Key
|
|
2329
|
+
SECRET_KEY=your-secret-key-here
|
|
2330
|
+
|
|
2331
|
+
# NextPy Settings
|
|
2332
|
+
NEXTPY_DEBUG_ICON=true
|
|
2333
|
+
NEXTPY_HOT_RELOAD=true
|
|
2334
|
+
NEXTPY_LOG_LEVEL=info
|
|
2335
|
+
''')
|
|
2336
|
+
click.echo(" Created: .env")
|
|
2337
|
+
|
|
2338
|
+
# Install Node.js dependencies
|
|
2339
|
+
try:
|
|
2340
|
+
import subprocess
|
|
2341
|
+
import sys
|
|
2342
|
+
|
|
2343
|
+
click.echo(click.style(" 📦 Installing Node.js dependencies...", fg="blue"))
|
|
2344
|
+
result = subprocess.run(
|
|
2345
|
+
["npm", "install"],
|
|
2346
|
+
cwd=project_dir,
|
|
2347
|
+
capture_output=True,
|
|
2348
|
+
text=True
|
|
2349
|
+
)
|
|
2350
|
+
|
|
2351
|
+
if result.returncode == 0:
|
|
2352
|
+
click.echo(click.style(" ✅ Node.js dependencies installed", fg="green"))
|
|
2353
|
+
else:
|
|
2354
|
+
click.echo(click.style(" ⚠️ npm install failed", fg="yellow"))
|
|
2355
|
+
click.echo(" 💡 Run manually: npm install")
|
|
2356
|
+
|
|
2357
|
+
except FileNotFoundError:
|
|
2358
|
+
click.echo(click.style(" ⚠️ npm not found", fg="yellow"))
|
|
2359
|
+
click.echo(" 💡 Install Node.js: https://nodejs.org/")
|
|
2360
|
+
except Exception as e:
|
|
2361
|
+
click.echo(click.style(f" ⚠️ Could not install Node.js deps: {e}", fg="yellow"))
|
|
2362
|
+
|
|
2363
|
+
# Install Python dependencies
|
|
2364
|
+
try:
|
|
2365
|
+
click.echo(click.style(" 🐍 Installing Python dependencies...", fg="blue"))
|
|
2366
|
+
result = subprocess.run(
|
|
2367
|
+
[sys.executable, "-m", "pip", "install", "-r", "requirements.txt"],
|
|
2368
|
+
cwd=project_dir,
|
|
2369
|
+
capture_output=True,
|
|
2370
|
+
text=True
|
|
2371
|
+
)
|
|
2372
|
+
|
|
2373
|
+
if result.returncode == 0:
|
|
2374
|
+
click.echo(click.style(" ✅ Python dependencies installed", fg="green"))
|
|
2375
|
+
else:
|
|
2376
|
+
click.echo(click.style(" ⚠️ pip install failed", fg="yellow"))
|
|
2377
|
+
click.echo(" 💡 Run manually: pip install -r requirements.txt")
|
|
2378
|
+
|
|
2379
|
+
except Exception as e:
|
|
2380
|
+
click.echo(click.style(f" ⚠️ Could not install Python deps: {e}", fg="yellow"))
|
|
2381
|
+
try:
|
|
2382
|
+
import sys
|
|
2383
|
+
import subprocess
|
|
2384
|
+
from pathlib import Path
|
|
2385
|
+
|
|
2386
|
+
# Check if VS Code is available
|
|
2387
|
+
result = subprocess.run([sys.executable, "-c", "import vscode"],
|
|
2388
|
+
capture_output=True, text=True)
|
|
2389
|
+
|
|
2390
|
+
if result.returncode == 0:
|
|
2391
|
+
extension_id = "nextpy.nextpy-vscode"
|
|
2392
|
+
|
|
2393
|
+
# Check if extension is already installed
|
|
2394
|
+
check_cmd = ["code", "--list-extensions", "--show-versions", extension_id]
|
|
2395
|
+
check_result = subprocess.run(check_cmd, capture_output=True, text=True)
|
|
2396
|
+
|
|
2397
|
+
if extension_id not in check_result.stdout:
|
|
2398
|
+
click.echo(click.style(" 🔌 Installing NextPy VS Code extension...", fg="blue"))
|
|
2399
|
+
|
|
2400
|
+
# Try to install from marketplace
|
|
2401
|
+
install_cmd = ["code", "--install-extension", extension_id]
|
|
2402
|
+
install_result = subprocess.run(install_cmd, capture_output=True, text=True)
|
|
2403
|
+
|
|
2404
|
+
if install_result.returncode == 0:
|
|
2405
|
+
click.echo(click.style(" ✅ NextPy VS Code extension installed!", fg="green"))
|
|
2406
|
+
click.echo(click.style(" 📝 Restart VS Code to activate", fg="yellow"))
|
|
2407
|
+
else:
|
|
2408
|
+
click.echo(click.style(" ⚠️ Extension installation failed", fg="yellow"))
|
|
2409
|
+
click.echo(" 💡 Install manually: code --install-extension nextpy.nextpy-vscode")
|
|
2410
|
+
else:
|
|
2411
|
+
click.echo(click.style(" ✅ NextPy VS Code extension already installed", fg="green"))
|
|
2412
|
+
else:
|
|
2413
|
+
click.echo(click.style(" ⚠️ VS Code not available", fg="yellow"))
|
|
2414
|
+
except Exception as e:
|
|
2415
|
+
click.echo(click.style(f" ⚠️ Could not install VS Code extension: {e}", fg="yellow"))
|
|
2416
|
+
|
|
2417
|
+
|
|
2418
|
+
|
|
2419
|
+
|
|
2420
|
+
if __name__ == "__main__":
|
|
2421
|
+
cli()
|