Repository: aiidalab/aiidalab-launch (language: python)

Issue:
Automatically increment port number and/or change home directory name for new profiles
## Proposal

Automatically adjust the port number and potentially change the default home directory for new profiles. For example, assuming that there is already one "default" profile with port "8888", when creating a new "foo" profile, the default config would be:
```ini
port = 8889
default_apps = [ "aiidalab-widgets-base",]
system_user = "aiida"
image = "aiidalab/aiidalab-docker-stack:latest"
home_mount = "/home/sadorf/aiidalab-foo"
```

Pros:
- Makes it easier to create a new non-conflicting profile.

Cons:
- Increases complexity and may lead to unexpected behavior when users create a new profile expecting it to use the default values.


Discussion / hints:

Fix patch:
diff --git a/aiidalab_launch/__main__.py b/aiidalab_launch/__main__.py
index 22ba43d..0e4f960 100644
--- a/aiidalab_launch/__main__.py
+++ b/aiidalab_launch/__main__.py
@@ -17,7 +17,14 @@
 from packaging.version import parse
 from tabulate import tabulate
 
-from .core import APPLICATION_ID, LOGGER, AiidaLabInstance, Config, Profile
+from .core import (
+    APPLICATION_ID,
+    DEFAULT_PORT,
+    LOGGER,
+    AiidaLabInstance,
+    Config,
+    Profile,
+)
 from .util import get_docker_client, get_latest_version, spinner, webbrowser_available
 from .version import __version__
 
@@ -168,9 +175,23 @@ def show_profile(app_state, profile):
 
 @profiles.command("add")
 @click.argument("profile")
+@click.option(
+    "--port",
+    type=click.IntRange(min=1, max=65535),
+    help=(
+        "Specify port on which this instance will be exposed. The default port "
+        "is chosen such that it does not conflict with any currently configured "
+        "profiles."
+    ),
+)
+@click.option(
+    "--home-mount",
+    type=click.Path(file_okay=False),
+    help="Specify the path to be mounted as home directory.",
+)
 @pass_app_state
 @click.pass_context
-def add_profile(ctx, app_state, profile):
+def add_profile(ctx, app_state, port, home_mount, profile):
     """Add a new AiiDAlab profile to the configuration."""
     try:
         app_state.config.get_profile(profile)
@@ -179,8 +200,18 @@ def add_profile(ctx, app_state, profile):
     else:
         raise click.ClickException(f"Profile with name '{profile}' already exists.")
 
+    # Determine next available port or use the one provided by the user.
+    configured_ports = [prof.port for prof in app_state.config.profiles if prof.port]
+    port = port or (max(configured_ports, default=-1) + 1) or DEFAULT_PORT
+    # Determine home mount path unless provided.
+    home_mount = home_mount or str(Path.home() / f"aiidalab-{profile}")
+
     try:
-        new_profile = Profile(name=profile)
+        new_profile = Profile(
+            name=profile,
+            port=port,
+            home_mount=home_mount,
+        )
     except ValueError as error:  # invalid profile name
         raise click.ClickException(error)
 


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 1bf8dcf..03bb2ef 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -54,7 +54,10 @@ def test_show_profile():
 
 def test_add_remove_profile():
     runner: CliRunner = CliRunner()
-    result: Result = runner.invoke(cli.cli, ["profiles", "add", "new-profile"])
+    result: Result = runner.invoke(
+        cli.cli, ["profiles", "add", "new-profile"], input="n\n"
+    )
+    assert result.exit_code == 0
     assert "Added profile 'new-profile'." in result.output
     result: Result = runner.invoke(cli.cli, ["profiles", "list"])
     assert "new-profile" in result.output