diff --git a/docs/framework/chat-assistant.mdx b/docs/framework/chat-assistant.mdx index d63a29f40..14159b269 100644 --- a/docs/framework/chat-assistant.mdx +++ b/docs/framework/chat-assistant.mdx @@ -49,7 +49,7 @@ Next, open your terminal and navigate to the directory where you want to create This command sets up a new project called `chat-assistant` in the specified directory. - To edit your project, run the below commands. This will bring up the console, where Framework-wide messages and errors will appear, including logs from the API. By default, the Writer Framework Builder is accessible at `localhost:3006`. If that port is in use, you can specify a different port. Open this address in your browser to view your default application setup. + To edit your project, run the below commands. This will bring up the console, where Framework-wide messages and errors will appear, including logs from the API. By default, the Writer Framework Builder is accessible at `localhost:4005`. If that port is in use, you can specify a different port. Open this address in your browser to view your default application setup. ```bash Standard port @@ -151,4 +151,4 @@ Once the application is deployed, the CLI will return with the URL of your live ## Conclusion -That's all it takes to set up a basic application with the Writer Framework. This setup not only demonstrates the platform's capabilities, but also provides a foundation on which you can build more complex applications. To learn more, explore the rest of the Writer Framework documentation and the API documentation. \ No newline at end of file +That's all it takes to set up a basic application with the Writer Framework. This setup not only demonstrates the platform's capabilities, but also provides a foundation on which you can build more complex applications. To learn more, explore the rest of the Writer Framework documentation and the API documentation. diff --git a/docs/framework/product-description-generator.mdx b/docs/framework/product-description-generator.mdx index 6025a25b4..ca444e57a 100644 --- a/docs/framework/product-description-generator.mdx +++ b/docs/framework/product-description-generator.mdx @@ -51,7 +51,7 @@ Next, open your terminal and navigate to the directory where you want to create This command sets up a new project called `product-description-app` in the specified directory using a template designed for this tutorial. - To edit your project, run the below commands. This will bring up the console, where Framework-wide messages and errors will appear, including logs from the API. By default, the Writer Framework Builder is accessible at `localhost:3006`. If that port is in use, you can specify a different port. Open this address in your browser to view your default application setup. + To edit your project, run the below commands. This will bring up the console, where Framework-wide messages and errors will appear, including logs from the API. By default, the Writer Framework Builder is accessible at `localhost:4005`. If that port is in use, you can specify a different port. Open this address in your browser to view your default application setup. ```bash Standard port @@ -59,7 +59,7 @@ Next, open your terminal and navigate to the directory where you want to create ``` ```bash Custom port - writer edit product-description-app –port=3007 + writer edit product-description-app --port=3007 ``` @@ -377,4 +377,4 @@ Once the application is deployed, the CLI will return with the URL of your live ## Conclusion -You’ve now built a full application with the Writer Framework and the Writer AI module. Congratulations! This application not only demonstrates the platform's capabilities but also provides a foundation on which you can build more complex applications. To learn more, explore the rest of the Writer Framework documentation and the API documentation. \ No newline at end of file +You’ve now built a full application with the Writer Framework and the Writer AI module. Congratulations! This application not only demonstrates the platform's capabilities but also provides a foundation on which you can build more complex applications. To learn more, explore the rest of the Writer Framework documentation and the API documentation. diff --git a/docs/framework/quickstart-tutorial.mdx b/docs/framework/quickstart-tutorial.mdx index fff61f028..877fbfd04 100644 --- a/docs/framework/quickstart-tutorial.mdx +++ b/docs/framework/quickstart-tutorial.mdx @@ -47,7 +47,7 @@ Once this is done, we can finally run the Framework editor using the command: writer edit . ``` -This will run our Framework instance. Runtime logs can be observed in the terminal, and the app is available at http://localhost:3006. +This will run our Framework instance. Runtime logs can be observed in the terminal, and the app is available at http://localhost:4005. ![newly created application](./images/quickstart/new_app.png) diff --git a/docs/framework/quickstart.mdx b/docs/framework/quickstart.mdx index d85a08709..c6008cb12 100644 --- a/docs/framework/quickstart.mdx +++ b/docs/framework/quickstart.mdx @@ -61,6 +61,10 @@ It's not recommended to expose Framework to the Internet. If you need to access If you need to disable this protection, use the flag `--enable-remote-edit`. + +The editor starts by default on port 4005. If you launch multiple editors in parallel and do not specify a port, Writer Framework will automatically assign the next port until reaching the limit of 4099. + + ## Run an app When your app is ready, execute the `run` command, which will allow others to run, but not edit, your Framework app. @@ -69,6 +73,10 @@ When your app is ready, execute the `run` command, which will allow others to ru writer run my_app ``` + +Your app starts by default on port 3005. If you launch multiple apps in parallel and do not specify a port, Writer Framework will automatically assign the next port until reaching the limit of 3099. + + You can specify a port and host. Specifying `--host 0.0.0.0` enables you to share your application in your local network. ```sh diff --git a/docs/framework/release-notes-generator.mdx b/docs/framework/release-notes-generator.mdx index 923c80a16..4e8c82cc3 100644 --- a/docs/framework/release-notes-generator.mdx +++ b/docs/framework/release-notes-generator.mdx @@ -48,7 +48,7 @@ Next, open your terminal and navigate to the directory where you want to create ``` - To edit your project, run the following commands. This will bring up the console, which displays Framework-wide messages and errors, including logs from the API. By default, the Writer Framework Builder is accessible at `localhost:3006`. If this port is in use, you can specify a different port. Open this address in your browser to view your default application setup. + To edit your project, run the following commands. This will bring up the console, which displays Framework-wide messages and errors, including logs from the API. By default, the Writer Framework Builder is accessible at `localhost:4005`. If this port is in use, you can specify a different port. Open this address in your browser to view your default application setup. ```bash Standard port @@ -422,4 +422,4 @@ You'll be prompted for your API key. Once the application is deployed, the CLI will return with the URL of your live application. ## Conclusion -By following these steps, you've created a complete Release notes generator application using Writer Framework. To learn more, explore the rest of the Writer Framework documentation and the API documentation. \ No newline at end of file +By following these steps, you've created a complete Release notes generator application using Writer Framework. To learn more, explore the rest of the Writer Framework documentation and the API documentation. diff --git a/docs/framework/social-post-generator.mdx b/docs/framework/social-post-generator.mdx index f3fbab824..7babc8516 100644 --- a/docs/framework/social-post-generator.mdx +++ b/docs/framework/social-post-generator.mdx @@ -51,7 +51,7 @@ Next, open your terminal and navigate to the directory where you want to create This command will set up a new project called `social-generator` in the specified directory using an `ai-starter` template. - To edit your project, run the below commands. This will bring up the console, where Framework-wide messages and errors will appear, including logs from the API. By default, the Writer Framework Builder is accessible at `localhost:3006`. If that port is in use, you can specify a different port. Open this address in your browser to view your default application setup. + To edit your project, run the below commands. This will bring up the console, where Framework-wide messages and errors will appear, including logs from the API. By default, the Writer Framework Builder is accessible at `localhost:4005`. If that port is in use, you can specify a different port. Open this address in your browser to view your default application setup. ```bash Standard port @@ -59,7 +59,7 @@ Next, open your terminal and navigate to the directory where you want to create ``` ```bash Custom port - writer edit social-generator –port=3007 + writer edit social-generator --port=3007 ``` @@ -216,4 +216,4 @@ Once the application is deployed, the CLI will return with the URL of your live ## Conclusion -That's all it takes to set up a basic application with the Writer Framework. This setup not only demonstrates the platform's capabilities but also provides a foundation on which you can build more complex applications. To learn more, explore the rest of the Writer Framework documentation and the API documentation. \ No newline at end of file +That's all it takes to set up a basic application with the Writer Framework. This setup not only demonstrates the platform's capabilities but also provides a foundation on which you can build more complex applications. To learn more, explore the rest of the Writer Framework documentation and the API documentation. diff --git a/src/writer/command_line.py b/src/writer/command_line.py index 95d5da7cf..6bf8962f6 100644 --- a/src/writer/command_line.py +++ b/src/writer/command_line.py @@ -20,9 +20,9 @@ def main(): @main.command() @click.option('--host', default="127.0.0.1", help="Host to run the app on") -@click.option('--port', default=3005, help="Port to run the app on") +@click.option('--port', default=None, help="Port to run the app on") @click.argument('path') -def run(path, host, port): +def run(path: str, host: str, port: Optional[int]): """Run the app from PATH folder in run mode.""" abs_path = os.path.abspath(path) @@ -34,11 +34,11 @@ def run(path, host, port): @main.command() @click.option('--host', default="127.0.0.1", help="Host to run the app on") -@click.option('--port', default=3006, help="Port to run the app on") +@click.option('--port', default=None, help="Port to run the app on") @click.option('--enable-remote-edit', help="Set this flag to allow non-local requests in edit mode.", is_flag=True) @click.option('--enable-server-setup', help="Set this flag to enable server setup hook in edit mode.", is_flag=True) @click.argument('path') -def edit(path, port, host, enable_remote_edit, enable_server_setup): +def edit(path: str, port: Optional[int], host: str, enable_remote_edit: bool, enable_server_setup: bool): """Run the app from PATH folder in edit mode.""" abs_path = os.path.abspath(path) @@ -63,9 +63,9 @@ def create(path, template): @main.command() @click.option('--host', default="127.0.0.1", help="Host to run the app on") -@click.option('--port', default=3006, help="Port to run the app on") +@click.option('--port', default=None, help="Port to run the app on") @click.option('--enable-remote-edit', help="Set this flag to allow non-local requests in edit mode.", is_flag=True) -def hello(port, host, enable_remote_edit): +def hello(port: Optional[int], host: str, enable_remote_edit): """Create and run an onboarding 'Hello' app.""" create_app("hello", template_name="hello", overwrite=True) writer.serve.serve("hello", mode="edit", diff --git a/src/writer/serve.py b/src/writer/serve.py index 3e466f39a..c2b23e62d 100644 --- a/src/writer/serve.py +++ b/src/writer/serve.py @@ -5,6 +5,7 @@ import os import os.path import pathlib +import socket import textwrap import typing from contextlib import asynccontextmanager @@ -500,7 +501,7 @@ def register_auth( ): auth.register(app, callback=callback, unauthorized_action=unauthorized_action) -def serve(app_path: str, mode: ServeMode, port, host, enable_remote_edit=False, enable_server_setup=False): +def serve(app_path: str, mode: ServeMode, port: Optional[int], host, enable_remote_edit=False, enable_server_setup=False): """ Initialises the web server. """ print_init_message() @@ -513,6 +514,14 @@ def on_load(): Loading of the server_setup.py is active by default when Writer Framework is launched with the run command. """ + if port is None: + mode_allowed_ports = { + 'run': (3005, 3099), + 'edit': (4005, 4099) + } + + port = _next_localhost_available_port(mode_allowed_ports[mode]) + enable_server_setup = mode == "run" or enable_server_setup app = get_asgi_app(app_path, mode, enable_remote_edit, on_load=on_load, enable_server_setup=enable_server_setup) log_level = "warning" @@ -599,17 +608,6 @@ def _mount_server_static_path(app: FastAPI, server_static_path: pathlib.Path) -> if f.is_dir(): app.mount(f"/{f.name}", StaticFiles(directory=f), name=f"server_static_{f}") -def _execute_server_setup_hook(user_app_path: str) -> None: - """ - Runs the server_setup.py module if present in the application directory. - - """ - server_setup_path = os.path.join(user_app_path, "server_setup.py") - if os.path.isfile(server_setup_path): - spec = cast(ModuleSpec, importlib.util.spec_from_file_location("server_setup", server_setup_path)) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) # type: ignore - def app_runner(asgi_app: WriterFastAPI) -> AppRunner: return asgi_app.state.app_runner @@ -632,3 +630,34 @@ def wf_root_static_assets() -> List[pathlib.Path]: all_static_assets.append(f) return all_static_assets + + +def _execute_server_setup_hook(user_app_path: str) -> None: + """ + Runs the server_setup.py module if present in the application directory. + + """ + server_setup_path = os.path.join(user_app_path, "server_setup.py") + if os.path.isfile(server_setup_path): + spec = cast(ModuleSpec, importlib.util.spec_from_file_location("server_setup", server_setup_path)) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) # type: ignore + + +def _next_localhost_available_port(port_range: Tuple[int, int]) -> int: + """ + Searches for a free port in a given range on localhost to start the server + + >>> port = _next_localhost_available_port((3005, 3099)) + + 3005 is the first port to be tested. If it is not available, the port 3006 is tested, and so on. + """ + for port in range(port_range[0], port_range[1]): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(1) + result = sock.connect_ex(('127.0.0.1', port)) + sock.close() + if result != 0: + return port + + raise OSError(f"No free port found to start the server between {port_range[0]} and {port_range[1]} .")