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]} .")