nextpy-framework 2.0.0__tar.gz → 2.1.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.
Files changed (81) hide show
  1. nextpy_framework-2.1.0/.nextpy_framework/nextpy/cli.py +1155 -0
  2. nextpy_framework-2.1.0/.nextpy_framework/nextpy/components/debug/AutoDebug.py +779 -0
  3. nextpy_framework-2.1.0/.nextpy_framework/nextpy/components/debug/DebugIcon.py +244 -0
  4. nextpy_framework-2.1.0/.nextpy_framework/nextpy/components/debug/DebugIconFixed.py +244 -0
  5. nextpy_framework-2.1.0/.nextpy_framework/nextpy/core/component_renderer.py +385 -0
  6. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/core/component_router.py +187 -27
  7. nextpy_framework-2.1.0/.nextpy_framework/nextpy/jsx_preprocessor.py +329 -0
  8. nextpy_framework-2.1.0/.nextpy_framework/nextpy/main.py +45 -0
  9. nextpy_framework-2.1.0/.nextpy_framework/nextpy/plugins/__init__.py +24 -0
  10. nextpy_framework-2.1.0/.nextpy_framework/nextpy/plugins/base.py +371 -0
  11. nextpy_framework-2.1.0/.nextpy_framework/nextpy/plugins/builtin.py +412 -0
  12. nextpy_framework-2.1.0/.nextpy_framework/nextpy/plugins/config.py +146 -0
  13. nextpy_framework-2.1.0/.nextpy_framework/nextpy/server/app.py +917 -0
  14. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy_framework.egg-info/PKG-INFO +1 -1
  15. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy_framework.egg-info/SOURCES.txt +8 -0
  16. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/PKG-INFO +1 -1
  17. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/pyproject.toml +1 -1
  18. nextpy_framework-2.0.0/.nextpy_framework/nextpy/cli.py +0 -1079
  19. nextpy_framework-2.0.0/.nextpy_framework/nextpy/core/component_renderer.py +0 -213
  20. nextpy_framework-2.0.0/.nextpy_framework/nextpy/jsx_preprocessor.py +0 -123
  21. nextpy_framework-2.0.0/.nextpy_framework/nextpy/server/app.py +0 -389
  22. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/__init__.py +0 -0
  23. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/auth.py +0 -0
  24. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/builder.py +0 -0
  25. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/__init__.py +0 -0
  26. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/feedback.py +0 -0
  27. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/form.py +0 -0
  28. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/head.py +0 -0
  29. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/hooks_provider.py +0 -0
  30. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/image.py +0 -0
  31. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/layout.py +0 -0
  32. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/link.py +0 -0
  33. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/loader.py +0 -0
  34. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/navigation.py +0 -0
  35. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/toast.py +0 -0
  36. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/ui.py +0 -0
  37. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components/visual.py +0 -0
  38. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/components.py +0 -0
  39. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/config.py +0 -0
  40. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/core/__init__.py +0 -0
  41. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/core/builder.py +0 -0
  42. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/core/data_fetching.py +0 -0
  43. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/core/demo_pages_simple.py +0 -0
  44. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/core/demo_router.py +0 -0
  45. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/core/renderer.py +0 -0
  46. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/core/router.py +0 -0
  47. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/core/sync.py +0 -0
  48. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/db.py +0 -0
  49. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/dev_server.py +0 -0
  50. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/dev_tools.py +0 -0
  51. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/errors.py +0 -0
  52. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/hooks.py +0 -0
  53. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/hooks_provider.py +0 -0
  54. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/hooks_provider_new.py +0 -0
  55. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/jsx.py +0 -0
  56. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/jsx_transformer.py +0 -0
  57. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/performance.py +0 -0
  58. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/plugins.py +0 -0
  59. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/py.typed +0 -0
  60. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/security.py +0 -0
  61. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/server/__init__.py +0 -0
  62. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/server/debug.py +0 -0
  63. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/server/middleware.py +0 -0
  64. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/true_jsx.py +0 -0
  65. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/utils/__init__.py +0 -0
  66. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/utils/cache.py +0 -0
  67. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/utils/email.py +0 -0
  68. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/utils/file_upload.py +0 -0
  69. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/utils/logging.py +0 -0
  70. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/utils/search.py +0 -0
  71. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/utils/seo.py +0 -0
  72. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/utils/validators.py +0 -0
  73. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy/websocket.py +0 -0
  74. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy_framework.egg-info/dependency_links.txt +0 -0
  75. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy_framework.egg-info/entry_points.txt +0 -0
  76. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy_framework.egg-info/requires.txt +0 -0
  77. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/.nextpy_framework/nextpy_framework.egg-info/top_level.txt +0 -0
  78. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/LICENSE +0 -0
  79. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/README.md +0 -0
  80. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/setup.cfg +0 -0
  81. {nextpy_framework-2.0.0 → nextpy_framework-2.1.0}/tests/test_routing.py +0 -0
@@ -0,0 +1,1155 @@
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(f" - 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(f" 📁 Watching: {reload_dir}/", dim=True)
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
+ # Clean up partial creation
434
+ if project_dir.exists():
435
+ import shutil
436
+ shutil.rmtree(project_dir, ignore_errors=True)
437
+ click.echo(click.style(f" 🧹 Cleaned up partial files", fg="yellow"))
438
+
439
+
440
+ @cli.command()
441
+ def routes():
442
+ """Display all registered routes with detailed information"""
443
+ click.echo(click.style("\n 🛣️ NextPy Routes Overview", fg="cyan", bold=True))
444
+ click.echo(click.style(" =====================\n", fg="cyan"))
445
+
446
+ try:
447
+ from nextpy.core.router import Router
448
+
449
+ router = Router()
450
+ router.scan_pages()
451
+
452
+ page_routes = [r for r in router.routes if not r.is_api]
453
+ api_routes = router.api_routes
454
+
455
+ click.echo(click.style(f" 📄 Page Routes ({len(page_routes)} total)", fg="blue", bold=True))
456
+ if page_routes:
457
+ for i, route in enumerate(page_routes, 1):
458
+ dynamic = " 🔀" if route.is_dynamic else " 📄"
459
+ file_info = f"({route.file_path})"
460
+ click.echo(f" {i:2d}. {dynamic} {route.path:<30} {file_info}")
461
+ else:
462
+ click.echo(f" ℹ️ No page routes found")
463
+
464
+ click.echo()
465
+ click.echo(click.style(f" 🔌 API Routes ({len(api_routes)} total)", fg="green", bold=True))
466
+ if api_routes:
467
+ for i, route in enumerate(api_routes, 1):
468
+ dynamic = " 🔀" if route.is_dynamic else " 🔌"
469
+ file_info = f"({route.file_path})"
470
+ methods = "[GET, POST, PUT, DELETE]" if hasattr(route, 'handler') else "[GET]"
471
+ click.echo(f" {i:2d}. {dynamic} {route.path:<30} {methods:<20} {file_info}")
472
+ else:
473
+ click.echo(f" ℹ️ No API routes found")
474
+
475
+ click.echo()
476
+ click.echo(click.style(f" 📊 Summary:", fg="yellow", bold=True))
477
+ click.echo(f" Total Routes: {len(page_routes + api_routes)}")
478
+ click.echo(f" Dynamic Routes: {len([r for r in page_routes + api_routes if r.is_dynamic])}")
479
+ click.echo(f" Static Routes: {len([r for r in page_routes + api_routes if not r.is_dynamic])}")
480
+ click.echo()
481
+
482
+ except Exception as e:
483
+ click.echo(click.style(f" ❌ Error scanning routes: {str(e)}", fg="red"))
484
+
485
+
486
+ @cli.command()
487
+ @click.option("--out", "-o", default="out", help="Output directory for static files")
488
+ def export(out: str):
489
+ """Export static files with enhanced feedback"""
490
+ click.echo(click.style("\n 📦 NextPy Export", fg="green", bold=True))
491
+ click.echo(click.style(" =============\n", fg="green"))
492
+
493
+ try:
494
+ from nextpy.core.builder import Builder
495
+
496
+ click.echo(f" 📂 Output directory: {out}/")
497
+ click.echo(f" ⚙️ Initializing exporter...")
498
+
499
+ builder = Builder(out_dir=out)
500
+
501
+ click.echo(f" 📤 Exporting static files...")
502
+
503
+ async def run_export():
504
+ manifest = await builder.export_static()
505
+ return manifest
506
+
507
+ manifest = asyncio.run(run_export())
508
+
509
+ files_count = len(manifest.get("files", []))
510
+ total_size = manifest.get("total_size", 0)
511
+
512
+ click.echo()
513
+ click.echo(click.style(f" ✅ Export completed successfully!", fg="green", bold=True))
514
+ click.echo(f" 📁 Files exported: {files_count}")
515
+ click.echo(f" 💾 Total size: {_format_size(total_size)}")
516
+ click.echo(f" 📍 Output: {out}/")
517
+ click.echo()
518
+ click.echo(click.style(f" 🚀 Ready for static hosting!", fg="cyan", bold=True))
519
+ click.echo()
520
+
521
+ except Exception as e:
522
+ click.echo(click.style(f" ❌ Export failed: {str(e)}", fg="red"))
523
+ click.echo(click.style(f" 💡 Make sure you're in a NextPy project directory", fg="yellow"))
524
+
525
+
526
+ @cli.command()
527
+ def version():
528
+ """Show version and system information"""
529
+ click.echo(click.style("\n 📋 NextPy Framework Info", fg="cyan", bold=True))
530
+ click.echo(click.style(" ===================\n", fg="cyan"))
531
+
532
+ click.echo(f" 🏷️ Version: 2.0.0")
533
+ click.echo(f" 🐍 Python: {sys.version.split()[0]}")
534
+ click.echo(f" ⚡ Framework: NextPy")
535
+ click.echo(f" 🎨 Architecture: True JSX")
536
+ click.echo(f" 🖥️ Development Server: uvicorn")
537
+ click.echo(f" 🔄 Hot Reload: Available")
538
+ click.echo(f" 📁 Static Files: Available")
539
+ click.echo(f" 🔌 API Routes: Available")
540
+ click.echo(f" 📄 Page Routes: Available")
541
+ click.echo(f" 🧩 Component Routes: Available")
542
+ click.echo(f" 📚 Component Library: Available")
543
+ click.echo(f" 👨‍💻 Developer: Ibrahim Fonyuy")
544
+ click.echo(f" 📜 License: MIT")
545
+ click.echo(f" 🐙 GitHub: https://github.com/IBRAHIMFONYUY/nextpy-framework")
546
+ click.echo(f" 📖 Documentation: https://nextpy.org/docs")
547
+ click.echo(f" 🆘 Support: https://github.com/IBRAHIMFONYUY/nextpy-framework/issues")
548
+
549
+ click.echo()
550
+
551
+
552
+ @cli.command()
553
+ def info():
554
+ """Show comprehensive framework and system information"""
555
+ click.echo(click.style("\n 🖥️ NextPy System Information", fg="cyan", bold=True))
556
+ click.echo(click.style(" ==========================\n", fg="cyan"))
557
+
558
+ # Framework info
559
+ click.echo(click.style(" 📦 Framework Details:", fg="blue", bold=True))
560
+ click.echo(f" Version: 2.0.0")
561
+ click.echo(f" Architecture: True JSX")
562
+ click.echo(f" Python: {sys.version.split()[0]}")
563
+
564
+ # Feature status
565
+ click.echo(click.style("\n ⚡ Feature Status:", fg="green", bold=True))
566
+ watchdog_status = "✅ Available" if WATCHDOG_AVAILABLE else "❌ Not Available (pip install watchdog)"
567
+ click.echo(f" Hot Reload: {watchdog_status}")
568
+ click.echo(f" Static Files: ✅ Available")
569
+ click.echo(f" API Routes: ✅ Available")
570
+ click.echo(f" Page Routes: ✅ Available")
571
+ click.echo(f" Component Library: ✅ Available")
572
+
573
+ # Project structure check
574
+ click.echo(click.style("\n 📁 Project Structure:", fg="yellow", bold=True))
575
+ required_dirs = ["pages", "components", "templates", "public"]
576
+ for dir_name in required_dirs:
577
+ status = "✅" if Path(dir_name).exists() else "❌"
578
+ click.echo(f" {dir_name}/: {status}")
579
+
580
+ # Available commands
581
+ click.echo(click.style("\n 🛠️ Available Commands:", fg="purple", bold=True))
582
+ commands = [
583
+ ("nextpy dev", "Start development server"),
584
+ ("nextpy build", "Build for production"),
585
+ ("nextpy start", "Start production server"),
586
+ ("nextpy create <name>", "Create new project"),
587
+ ("nextpy generate <type> <name>", "Generate components/pages/APIs"),
588
+ ("nextpy routes", "Show all routes"),
589
+ ("nextpy export", "Export static files"),
590
+ ("nextpy version", "Show version info"),
591
+ ("nextpy info", "Show this information")
592
+ ]
593
+ for cmd, desc in commands:
594
+ click.echo(f" {cmd:<25} - {desc}")
595
+
596
+ click.echo()
597
+
598
+
599
+ @cli.command()
600
+ @click.argument("type", type=click.Choice(["page", "api", "component"]))
601
+ @click.argument("name")
602
+ def generate(type: str, name: str):
603
+ """Generate new page, API endpoint, or component"""
604
+ click.echo(click.style(f"\n Generating {type}: {name}", fg="cyan", bold=True))
605
+ click.echo(click.style(" " + "=" * (20 + len(name) + len(type)) + "\n", fg="cyan"))
606
+
607
+ if type == "page":
608
+ _generate_page(name)
609
+ elif type == "api":
610
+ _generate_api(name)
611
+ elif type == "component":
612
+ _generate_component(name)
613
+
614
+ click.echo(click.style(f"\n {type.title()} '{name}' created successfully!\n", fg="green", bold=True))
615
+
616
+
617
+ @cli.group()
618
+ def plugin():
619
+ """Plugin management commands"""
620
+ pass
621
+
622
+
623
+ @plugin.command()
624
+ def list():
625
+ """List all available plugins"""
626
+ click.echo(click.style("\n 🔌 NextPy Plugins", fg="cyan", bold=True))
627
+ click.echo(click.style(" ================\n", fg="cyan"))
628
+
629
+ try:
630
+ from nextpy.plugins import plugin_manager
631
+
632
+ plugin_info = plugin_manager.get_plugin_info()
633
+
634
+ click.echo(click.style(f" 📊 Overview:", fg="blue", bold=True))
635
+ click.echo(f" Total plugins: {plugin_info['total_plugins']}")
636
+ click.echo(f" Enabled: {plugin_info['enabled_plugins']}")
637
+ click.echo(f" Disabled: {plugin_info['total_plugins'] - plugin_info['enabled_plugins']}")
638
+
639
+ click.echo()
640
+ click.echo(click.style(f" 📋 Plugin Details:", fg="green", bold=True))
641
+
642
+ for plugin in plugin_info['plugins']:
643
+ status = "✅" if plugin['enabled'] else "❌"
644
+ priority = plugin['priority']
645
+ click.echo(f" {status} {plugin['name']:<15} v{plugin['version']:<8} (Priority: {priority})")
646
+
647
+ if plugin['dependencies']:
648
+ click.echo(f" Dependencies: {', '.join(plugin['dependencies'])}")
649
+
650
+ click.echo()
651
+
652
+ except ImportError:
653
+ click.echo(click.style(" ❌ Plugin system not available", fg="red"))
654
+ click.echo(click.style(" 💡 Install with: pip install nextpy[plugins]", fg="yellow"))
655
+ except Exception as e:
656
+ click.echo(click.style(f" ❌ Error: {str(e)}", fg="red"))
657
+
658
+
659
+ @plugin.command()
660
+ @click.argument("name")
661
+ @click.option("--enable/--disable", default=True, help="Enable or disable the plugin")
662
+ def enable(name: str, enable: bool):
663
+ """Enable or disable a plugin"""
664
+ action = "Enabling" if enable else "Disabling"
665
+ click.echo(click.style(f"\n {action} plugin: {name}", fg="cyan", bold=True))
666
+ click.echo(click.style(" " + "=" * (20 + len(name)) + "\n", fg="cyan"))
667
+
668
+ try:
669
+ from nextpy.plugins import plugin_manager
670
+
671
+ if enable:
672
+ plugin_manager.enable_plugin(name)
673
+ click.echo(click.style(f" ✅ Plugin '{name}' enabled successfully", fg="green"))
674
+ else:
675
+ plugin_manager.disable_plugin(name)
676
+ click.echo(click.style(f" ❌ Plugin '{name}' disabled", fg="yellow"))
677
+
678
+ click.echo()
679
+
680
+ except ImportError:
681
+ click.echo(click.style(" ❌ Plugin system not available", fg="red"))
682
+ except Exception as e:
683
+ click.echo(click.style(f" ❌ Error: {str(e)}", fg="red"))
684
+
685
+
686
+ @plugin.command()
687
+ @click.argument("name")
688
+ @click.option("--config", help="Plugin configuration as JSON string")
689
+ def configure(name: str, config: str):
690
+ """Configure a plugin"""
691
+ click.echo(click.style(f"\n ⚙️ Configuring plugin: {name}", fg="cyan", bold=True))
692
+ click.echo(click.style(" " + "=" * (20 + len(name)) + "\n", fg="cyan"))
693
+
694
+ try:
695
+ from nextpy.plugins import plugin_manager
696
+ import json
697
+
698
+ if config:
699
+ try:
700
+ config_dict = json.loads(config)
701
+ except json.JSONDecodeError:
702
+ click.echo(click.style(" ❌ Invalid JSON configuration", fg="red"))
703
+ return
704
+ else:
705
+ config_dict = {}
706
+
707
+ plugin_manager.configure_plugin(name, config_dict)
708
+ click.echo(click.style(f" ✅ Plugin '{name}' configured successfully", fg="green"))
709
+
710
+ if config_dict:
711
+ click.echo(f" Configuration: {json.dumps(config_dict, indent=2)}")
712
+
713
+ click.echo()
714
+
715
+ except ImportError:
716
+ click.echo(click.style(" ❌ Plugin system not available", fg="red"))
717
+ except Exception as e:
718
+ click.echo(click.style(f" ❌ Error: {str(e)}", fg="red"))
719
+
720
+
721
+ @plugin.command()
722
+ @click.argument("file_path", type=click.Path(exists=True))
723
+ def load(file_path: str):
724
+ """Load a plugin from file"""
725
+ click.echo(click.style(f"\n 📦 Loading plugin from: {file_path}", fg="cyan", bold=True))
726
+ click.echo(click.style(" " + "=" * (25 + len(file_path)) + "\n", fg="cyan"))
727
+
728
+ try:
729
+ from nextpy.plugins import plugin_manager
730
+ from pathlib import Path
731
+
732
+ plugin = plugin_manager.load_plugin_from_file(Path(file_path))
733
+ plugin_manager.register_plugin(plugin)
734
+
735
+ click.echo(click.style(f" ✅ Plugin '{plugin.name}' loaded successfully", fg="green"))
736
+ click.echo(f" Version: {plugin.version}")
737
+ click.echo(f" Priority: {plugin.priority.value}")
738
+
739
+ click.echo()
740
+
741
+ except ImportError:
742
+ click.echo(click.style(" ❌ Plugin system not available", fg="red"))
743
+ except Exception as e:
744
+ click.echo(click.style(f" ❌ Error: {str(e)}", fg="red"))
745
+
746
+
747
+ def _generate_page(name: str):
748
+ """Generate a new page"""
749
+ page_path = Path(f"pages/{name}.py")
750
+ page_path.parent.mkdir(parents=True, exist_ok=True)
751
+
752
+ content = f'''"""Generated {name} page"""
753
+
754
+ def {name.title()}(props = None):
755
+ """{name.title()} page component"""
756
+ props = props or {{}}
757
+
758
+ title = props.get("title", "{name.title()} Page")
759
+
760
+ return (
761
+ <div class="max-w-4xl mx-auto px-4 py-12">
762
+ <h1 class="mb-6 text-4xl font-bold text-gray-900">{{title}}</h1>
763
+ <p class="text-lg text-gray-600">
764
+ This is the {name} page generated by NextPy.
765
+ </p>
766
+ </div>
767
+ )
768
+
769
+ def getServerSideProps(context):
770
+ return {{
771
+ "props": {{
772
+ "title": "{name.title()} Page"
773
+ }}
774
+ }}
775
+
776
+ default = {name.title()}
777
+ '''
778
+
779
+ page_path.write_text(content)
780
+ click.echo(f" Created: {page_path}")
781
+
782
+
783
+ def _generate_api(name: str):
784
+ """Generate a new API endpoint"""
785
+ api_path = Path(f"pages/api/{name}.py")
786
+ api_path.parent.mkdir(parents=True, exist_ok=True)
787
+
788
+ content = f'''"""Generated {name} API endpoint"""
789
+
790
+ async def get(request):
791
+ """GET /api/{name}"""
792
+ return {{
793
+ "message": "Hello from {name} API!",
794
+ "endpoint": "/api/{name}",
795
+ "method": "GET"
796
+ }}
797
+
798
+ async def post(request):
799
+ """POST /api/{name}"""
800
+ body = await request.json()
801
+ return {{
802
+ "message": "POST request received",
803
+ "data": body,
804
+ "endpoint": "/api/{name}",
805
+ "method": "POST"
806
+ }}
807
+ '''
808
+
809
+ api_path.write_text(content)
810
+ click.echo(f" Created: {api_path}")
811
+
812
+
813
+ def _generate_component(name: str):
814
+ """Generate a new component"""
815
+ component_path = Path(f"components/{name}.py")
816
+ component_path.parent.mkdir(parents=True, exist_ok=True)
817
+
818
+ content = f'''"""Generated {name} component"""
819
+
820
+ def {name.title()}(props = None):
821
+ """{name.title()} component"""
822
+ props = props or {{}}
823
+
824
+ children = props.get("children", "")
825
+ className = props.get("className", "")
826
+
827
+ return (
828
+ <div class="{name.lower()}-component " + className>
829
+ {{children}}
830
+ </div>
831
+ )
832
+
833
+ default = {name.title()}
834
+ '''
835
+
836
+ component_path.write_text(content)
837
+ click.echo(f" Created: {component_path}")
838
+
839
+
840
+ def _ensure_project_structure():
841
+ """Ensure the basic project structure exists"""
842
+ dirs = ["pages", "pages/api", "templates", "public", "public/css", "public/js"]
843
+
844
+ for dir_path in dirs:
845
+ Path(dir_path).mkdir(parents=True, exist_ok=True)
846
+
847
+
848
+ def _create_project_structure(project_dir: Path):
849
+ """Create a new project structure with True JSX architecture"""
850
+ dirs = [
851
+ "pages",
852
+ "pages/api",
853
+ "components",
854
+ "components/ui",
855
+ "components/layout",
856
+ "pages/blog",
857
+ "pages/api/users",
858
+ "public",
859
+ "public/css",
860
+ "public/js",
861
+ "public/images",
862
+ "models",
863
+ ]
864
+
865
+ for dir_path in dirs:
866
+ (project_dir / dir_path).mkdir(parents=True, exist_ok=True)
867
+ click.echo(f" Created: {dir_path}/")
868
+
869
+ # Create JSX-based homepage with .py extension
870
+ (project_dir / "pages" / "index.py").write_text('''"""Homepage with True JSX"""
871
+
872
+ def Home(props = None):
873
+ """Homepage component"""
874
+ props = props or {}
875
+
876
+ title = props.get("title", "Welcome to NextPy")
877
+ message = props.get("message", "Your Python-powered web framework with True JSX")
878
+
879
+ return (
880
+ <div class="flex items-center justify-center min-h-screen bg-gradient-to-br from-blue-500 to-purple-600">
881
+ <div class="text-center text-white">
882
+ <h1 class="mb-4 text-5xl font-bold">{title}</h1>
883
+ <p class="text-xl">{message}</p>
884
+ <a href="https://github.com/nextpy/nextpy-framework" target="_blank"
885
+ class="inline-block px-6 py-3 mt-8 font-semibold text-blue-600 transition-all duration-300 transform bg-white rounded-lg shadow-lg hover:bg-gray-100 hover:text-blue-700 hover:scale-105">
886
+ Explore NextPy Framework
887
+ </a>
888
+ </div>
889
+ </div>
890
+ )
891
+
892
+ def getServerSideProps(context):
893
+ return {
894
+ "props": {
895
+ "title": "Welcome to NextPy",
896
+ "message": "Your Python-powered web framework with True JSX"
897
+ }
898
+ }
899
+
900
+ default = Home
901
+ ''')
902
+ click.echo(" Created: pages/index.py")
903
+
904
+ # Create about page with True JSX
905
+ (project_dir / "pages" / "about.py").write_text('''"""About page with True JSX"""
906
+
907
+ def About(props = None):
908
+ """About page component"""
909
+ props = props or {}
910
+
911
+ title = props.get("title", "About NextPy")
912
+ description = props.get("description", "Learn about the NextPy framework")
913
+
914
+ return (
915
+ <div class="max-w-4xl mx-auto px-4 py-12">
916
+ <h1 class="mb-6 text-4xl font-bold text-gray-900">{title}</h1>
917
+ <p class="text-lg text-gray-600 mb-4">{description}</p>
918
+ <p class="text-gray-700">
919
+ NextPy is a Python web framework inspired by Next.js, offering file-based routing,
920
+ server-side rendering, static site generation, and a modern True JSX component-based architecture.
921
+ </p>
922
+ </div>
923
+ )
924
+
925
+ def getServerSideProps(context):
926
+ return {
927
+ "props": {
928
+ "title": "About NextPy",
929
+ "description": "Learn about the NextPy framework"
930
+ }
931
+ }
932
+
933
+ default = About
934
+ ''')
935
+ click.echo(" Created: pages/about.py")
936
+
937
+ # Create a reusable Button component
938
+ (project_dir / "components" / "ui" / "Button.py").write_text('''"""Button component"""
939
+
940
+ def Button(props = None):
941
+ """Reusable Button component"""
942
+ props = props or {}
943
+
944
+ variant = props.get("variant", "default")
945
+ children = props.get("children", "Button")
946
+ className = props.get("className", "")
947
+
948
+ if variant == "primary":
949
+ variant_class = "bg-blue-600 text-white hover:bg-blue-700"
950
+ elif variant == "secondary":
951
+ variant_class = "bg-gray-200 text-gray-900 hover:bg-gray-300"
952
+ else:
953
+ variant_class = "bg-gray-600 text-white hover:bg-gray-700"
954
+
955
+ class_attr = f"px-4 py-2 rounded-lg font-medium transition-colors {variant_class} {className}"
956
+
957
+ return (
958
+ <button className={class_attr}
959
+ id={props.get("id")}
960
+ disabled={props.get("disabled", False)}>
961
+ {children}
962
+ </button>
963
+ )
964
+
965
+ default = Button
966
+ ''')
967
+ click.echo(" Created: components/ui/Button.py")
968
+
969
+ # Create a Layout component
970
+ (project_dir / "components" / "layout" / "Layout.py").write_text('''"""Layout component"""
971
+
972
+ def Layout(props = None):
973
+ """Layout component wrapper"""
974
+ props = props or {}
975
+
976
+ title = props.get("title", "NextPy App")
977
+ children = props.get("children", "")
978
+
979
+ return (
980
+ <div class="min-h-screen flex flex-col">
981
+ <header class="bg-white shadow-sm">
982
+ <div class="max-w-7xl mx-auto px-4 py-4">
983
+ <div class="flex items-center justify-between">
984
+ <h1 class="text-2xl font-bold text-gray-900">{title}</h1>
985
+ <nav class="flex space-x-4">
986
+ <a href="/" class="text-gray-600 hover:text-gray-900">Home</a>
987
+ <a href="/about" class="text-gray-600 hover:text-gray-900">About</a>
988
+ </nav>
989
+ </div>
990
+ </div>
991
+ </header>
992
+ <main class="flex-1">
993
+ {children}
994
+ </main>
995
+ <footer class="bg-gray-100 mt-auto">
996
+ <div class="max-w-7xl mx-auto px-4 py-6 text-center text-gray-600">
997
+ <p>© 2025 NextPy Framework. All rights reserved.</p>
998
+ </div>
999
+ </footer>
1000
+ </div>
1001
+ )
1002
+
1003
+ default = Layout
1004
+ ''')
1005
+ click.echo(" Created: components/layout/Layout.py")
1006
+
1007
+ # Create VS Code configuration for JSX support
1008
+ (project_dir / ".vscode").mkdir(exist_ok=True)
1009
+ (project_dir / ".vscode" / "settings.json").write_text('''{
1010
+ "files.associations": {
1011
+ "*.py": "python",
1012
+ "*.py.jsx": "python",
1013
+ "*.jsx": "javascriptreact"
1014
+ },
1015
+ "emmet.includeLanguages": {
1016
+ "python": "html",
1017
+ "javascriptreact": "html",
1018
+ "typescriptreact": "html"
1019
+ },
1020
+ "emmet.triggerExpansionOnTab": true,
1021
+ "typescript.preferences.includePackageJsonAutoImports": "on",
1022
+ "editor.quickSuggestions": {
1023
+ "strings": true
1024
+ },
1025
+ "editor.suggestSelection": "first",
1026
+ "editor.wordBasedSuggestions": true,
1027
+ "editor.snippetSuggestions": "top",
1028
+ "editor.parameterHints": {
1029
+ "enabled": true
1030
+ },
1031
+ "editor.snippetSuggestions": "top",
1032
+ "html.autoClosingTags": true,
1033
+ "css.autoClosingTags": true,
1034
+ "javascript.autoClosingTags": true,
1035
+ "typescript.autoClosingTags": true,
1036
+ "editor.autoClosingBrackets": "always",
1037
+ "editor.autoClosingQuotes": "always",
1038
+ "editor.formatOnSave": true,
1039
+ "editor.codeActionsOnSave": {
1040
+ "source.fixAll.eslint": true,
1041
+ "source.organizeImports": true
1042
+ },
1043
+ "emmet.preferences": {
1044
+ "css.property.endWithSemicolon": true,
1045
+ "css.value.unit": "rem"
1046
+ },
1047
+ "files.exclude": {
1048
+ "**/__pycache__": true,
1049
+ "**/*.pyc": true,
1050
+ "**/node_modules": true,
1051
+ "**/out": true,
1052
+ "**/.next": true,
1053
+ "**/.pytest_cache": true,
1054
+ "**/.mypy_cache": true
1055
+ },
1056
+ "search.exclude": {
1057
+ "**/node_modules": true,
1058
+ "**/out": true,
1059
+ "**/.next": true,
1060
+ "**/__pycache__": true,
1061
+ "**/.pytest_cache": true,
1062
+ "**/.mypy_cache": true
1063
+ },
1064
+ "python.linting.enabled": true,
1065
+ "python.linting.pylintEnabled": false,
1066
+ "python.linting.flake8Enabled": false,
1067
+ "python.linting.pylintArgs": [
1068
+ "--disable=C0114,C0115,C0116,E1132,E1131,E1130"
1069
+ ],
1070
+ "python.formatting.provider": "black",
1071
+ "[python]": {
1072
+ "editor.defaultFormatter": "ms-python.black-formatter",
1073
+ "editor.formatOnSave": true,
1074
+ "editor.rulers": [88],
1075
+ "editor.tabSize": 4,
1076
+ "editor.insertSpaces": true
1077
+ }
1078
+ }''')
1079
+ click.echo(" Created: .vscode/settings.json")
1080
+
1081
+ (project_dir / ".vscode" / "extensions.json").write_text('''{
1082
+ "recommendations": [
1083
+ "ms-python.python",
1084
+ "ms-python.vscode-pylance",
1085
+ "bradlc.vscode-tailwindcss",
1086
+ "esbenp.prettier-vscode",
1087
+ "ms-vscode.vscode-json",
1088
+ "formulahendry.auto-rename-tag",
1089
+ "christian-kohler.path-intellisense",
1090
+ "ms-vscode.vscode-html-css-class-completion",
1091
+ "ms-vscode.vscode-emmet",
1092
+ "ms-vscode.vscode-eslint",
1093
+ "dbaeumer.vscode-eslint",
1094
+ "ms-vscode.vscode-typescript-next",
1095
+ "ritwickdey.liveserver",
1096
+ "ms-vscode.vscode-jest",
1097
+ "esbenp.prettier-vscode",
1098
+ "streetsidesoftware.code-spell-checker",
1099
+ "gruntfuggly.todo-tree",
1100
+ "ms-vscode.vscode-git-graph",
1101
+ "eamodio.gitlens",
1102
+ "ms-vscode.vscode-docker",
1103
+ "ms-vscode.remote-explorer",
1104
+ "ms-vscode-remote.remote-containers",
1105
+ "ms-vscode.vscode-remote-wsl",
1106
+ "redhat.vscode-yaml",
1107
+ "ms-vscode.vscode-markdown",
1108
+ "yzhang.markdown-all-in-one",
1109
+ "shd101wyy.markdown-preview-enhanced",
1110
+ "ms-vscode.vscode-python",
1111
+ "kevinrose.vsc-python-indent",
1112
+ "ms-python.black-formatter",
1113
+ "ms-python.isort",
1114
+ "ms-python.flake8",
1115
+ "ms-python.mypy-type-checker"
1116
+ ]
1117
+ }''')
1118
+ click.echo(" Created: .vscode/extensions.json")
1119
+
1120
+ # Create API example
1121
+ (project_dir / "pages" / "api" / "hello.py").write_text('''"""API example"""
1122
+
1123
+ def get(request):
1124
+ """GET /api/hello"""
1125
+ return {"message": "Hello from NextPy API!", "status": "success"}
1126
+
1127
+ def post(request):
1128
+ """POST /api/hello"""
1129
+ return {"message": "POST request received", "status": "success"}
1130
+ ''')
1131
+ click.echo(" Created: pages/api/hello.py")
1132
+
1133
+ (project_dir / "requirements.txt").write_text('''fastapi>=0.100.0
1134
+ uvicorn>=0.23.0
1135
+ jinja2>=3.1.0
1136
+ pydantic>=2.0.0
1137
+ pydantic-settings>=2.0.0
1138
+ click>=8.1.0
1139
+ watchdog>=3.0.0
1140
+ python-multipart>=0.0.6
1141
+ pillow>=10.0.0
1142
+ aiofiles>=23.0.0
1143
+ httpx>=0.24.0
1144
+ sqlalchemy>=2.0.0
1145
+ python-dotenv>=1.0.0
1146
+ pyjwt>=2.8.0
1147
+ markdown>=3.0.0 # Added markdown for documentation rendering
1148
+ ''')
1149
+ click.echo(" Created: requirements.txt")
1150
+
1151
+
1152
+
1153
+
1154
+ if __name__ == "__main__":
1155
+ cli()