diff --git a/sdk/python/packages/flet-cli/src/flet_cli/cli.py b/sdk/python/packages/flet-cli/src/flet_cli/cli.py index 540fec75b..ba5c4fb27 100644 --- a/sdk/python/packages/flet-cli/src/flet_cli/cli.py +++ b/sdk/python/packages/flet-cli/src/flet_cli/cli.py @@ -8,6 +8,7 @@ import flet_cli.commands.pack import flet_cli.commands.publish import flet_cli.commands.run +import flet_cli.commands.serve from flet.version import update_version @@ -76,6 +77,7 @@ def main(): flet_cli.commands.build.Command.register_to(sp, "build") flet_cli.commands.pack.Command.register_to(sp, "pack") flet_cli.commands.publish.Command.register_to(sp, "publish") + flet_cli.commands.serve.Command.register_to(sp, "serve") flet_cli.commands.doctor.Command.register_to( sp, "doctor" ) # Register the doctor command diff --git a/sdk/python/packages/flet-cli/src/flet_cli/commands/serve.py b/sdk/python/packages/flet-cli/src/flet_cli/commands/serve.py new file mode 100644 index 000000000..5fdc0839d --- /dev/null +++ b/sdk/python/packages/flet-cli/src/flet_cli/commands/serve.py @@ -0,0 +1,70 @@ +import argparse +import http.server +import socketserver +from pathlib import Path + +from flet_cli.commands.base import BaseCommand +from rich.console import Console +from rich.style import Style + +error_style = Style(color="red1", bold=True) +console = Console(log_path=False) + + +class CustomHandler(http.server.SimpleHTTPRequestHandler): + def __init__(self, *args, directory=None, **kwargs): + super().__init__(*args, directory=directory, **kwargs) + + def end_headers(self): + self.send_header("Cross-Origin-Opener-Policy", "same-origin") + self.send_header("Cross-Origin-Embedder-Policy", "require-corp") + self.send_header("Access-Control-Allow-Origin", "*") + super().end_headers() + + +class Command(BaseCommand): + """ + Serve static files from a directory with optional WASM headers. + """ + + def add_arguments(self, parser: argparse.ArgumentParser) -> None: + parser.add_argument( + "--port", + type=int, + default=8000, + help="Port to serve on (default: 8000)", + ) + parser.add_argument( + "--web-root", + type=str, + default="build/web", + help="Directory to serve (default: ./build/web)", + ) + + def handle(self, options: argparse.Namespace) -> None: + directory = Path(options.web_root).resolve() + if not directory.is_dir(): + console.print( + f"Error: Directory '{directory}' does not exist or is not a folder.", + style=error_style, + ) + exit(1) + + def handler(*args, **kwargs): + return CustomHandler( + *args, + directory=str(directory), + **kwargs, + ) + + try: + with socketserver.TCPServer(("", options.port), handler) as httpd: + console.print( + f"Serving [green]{directory}[/green] at [cyan]http://localhost:{options.port}[/cyan] (Press Ctrl+C to stop)\n" + ) + httpd.serve_forever() + except KeyboardInterrupt: + console.print("\n[bold yellow]Server stopped by user.[/bold yellow]") + except OSError as e: + console.print(f"Error: {e}", style=error_style) + exit(1)