From 533bc53d2a66d92343d35c8c6ffa68ecc785428a Mon Sep 17 00:00:00 2001 From: David Butenhof Date: Thu, 4 Jun 2026 07:29:16 -0400 Subject: [PATCH] Make backend port configureable Avoid collision with local e2e product testing assuming a fixed port. Assisted-by: Cursor Signed-off-by: David Butenhof --- README.md | 7 ++-- backend/src/github_pm/cli.py | 17 ++++++++-- backend/tests/test_cli.py | 62 ++++++++++++++++++++---------------- frontend/README.md | 12 ++++++- frontend/vite.config.js | 51 ++++++++++++++++++++--------- run-local.sh | 34 +++++++++++++++++--- 6 files changed, 129 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 58be16c..94d5c98 100644 --- a/README.md +++ b/README.md @@ -56,12 +56,13 @@ npm run dev # Start the frontend development server ./run-local.sh ``` -This will start both the backend (on port 8000) and the frontend (on port 3000) +This will start both the backend (default port 8080) and the frontend (port 3000). +Pass `--port ` to `./run-local.sh` to use a different backend port (the frontend proxy follows automatically). in the background. The script will give you a sample alias command to stop both of them, like ```console -Backend is running in the background at http://localhost:8000 +Backend is running in the background at http://localhost:8080 Frontend is running in the background at http://localhost:3000 To terminate, run: @@ -78,7 +79,7 @@ ______________________________________________________________________ ## Usage - Navigate to the frontend web UI to manage your GitHub projects visually. -- Access the API docs at `http://localhost:8000/docs` when the backend is running. +- Access the API docs at `http://localhost:8080/docs` when the backend is running (adjust the port if you used `--port`). The UI shows all the milestones defined for the configured GitHub project, with the description and due date. You can show all issues and PRs associated with a diff --git a/backend/src/github_pm/cli.py b/backend/src/github_pm/cli.py index 6aaec33..8d70655 100644 --- a/backend/src/github_pm/cli.py +++ b/backend/src/github_pm/cli.py @@ -1,18 +1,29 @@ -"""CLI entry point for github_pm.""" +"""CLI entry point for github_pm. + +Assisted-by: Cursor +""" import os +import click import uvicorn -def main(): +@click.command() +@click.option( + "--port", + type=click.IntRange(1, 65535), + default=8080, + help="TCP port to bind (default: 8080).", +) +def main(port: int) -> None: """Launch the FastAPI application with uvicorn.""" pgid = os.getpgid(0) print(f"'kill -- -{pgid}' to stop the server") uvicorn.run( "github_pm.app:app", host="0.0.0.0", - port=8000, + port=port, reload=True, ) diff --git a/backend/tests/test_cli.py b/backend/tests/test_cli.py index 01243bf..e3abb94 100644 --- a/backend/tests/test_cli.py +++ b/backend/tests/test_cli.py @@ -1,11 +1,14 @@ """Tests for the cli module. Generated-by: Cursor +Assisted-by: Cursor """ import sys from unittest.mock import MagicMock, patch +from click.testing import CliRunner + # Mock uvicorn before importing cli module to avoid import errors sys.modules["uvicorn"] = MagicMock() @@ -17,38 +20,47 @@ class TestMain: @patch("github_pm.cli.uvicorn.run") @patch("github_pm.cli.os.getpgid") - @patch("builtins.print") def test_main_calls_getpgid_and_prints_message( - self, mock_print, mock_getpgid, mock_uvicorn_run + self, mock_getpgid, mock_uvicorn_run ): """Test that main gets the process group ID and prints the kill command.""" - # Arrange mock_getpgid.return_value = 12345 - # Act - main() + result = CliRunner().invoke(main, []) - # Assert + assert result.exit_code == 0 mock_getpgid.assert_called_once_with(0) - mock_print.assert_called_once_with("'kill -- -12345' to stop the server") + assert result.output.strip() == "'kill -- -12345' to stop the server" @patch("github_pm.cli.uvicorn.run") @patch("github_pm.cli.os.getpgid") - def test_main_calls_uvicorn_with_correct_parameters( - self, mock_getpgid, mock_uvicorn_run - ): - """Test that main calls uvicorn.run with the correct parameters.""" - # Arrange + def test_main_calls_uvicorn_with_default_port(self, mock_getpgid, mock_uvicorn_run): + """Test that main calls uvicorn.run with the default port.""" + mock_getpgid.return_value = 12345 + + result = CliRunner().invoke(main, []) + + assert result.exit_code == 0 + mock_uvicorn_run.assert_called_once_with( + "github_pm.app:app", + host="0.0.0.0", + port=8080, + reload=True, + ) + + @patch("github_pm.cli.uvicorn.run") + @patch("github_pm.cli.os.getpgid") + def test_main_calls_uvicorn_with_custom_port(self, mock_getpgid, mock_uvicorn_run): + """Test that --port is passed through to uvicorn.run.""" mock_getpgid.return_value = 12345 - # Act - main() + result = CliRunner().invoke(main, ["--port", "9000"]) - # Assert + assert result.exit_code == 0 mock_uvicorn_run.assert_called_once_with( "github_pm.app:app", host="0.0.0.0", - port=8000, + port=9000, reload=True, ) @@ -56,33 +68,27 @@ def test_main_calls_uvicorn_with_correct_parameters( @patch("github_pm.cli.os.getpgid") def test_main_handles_different_pgid_values(self, mock_getpgid, mock_uvicorn_run): """Test that main works with different process group ID values.""" - # Arrange mock_getpgid.return_value = 99999 - # Act - main() + result = CliRunner().invoke(main, []) - # Assert + assert result.exit_code == 0 mock_getpgid.assert_called_once_with(0) mock_uvicorn_run.assert_called_once() @patch("github_pm.cli.uvicorn.run") @patch("github_pm.cli.os.getpgid") - @patch("builtins.print") def test_main_prints_correct_kill_command_format( - self, mock_print, mock_getpgid, mock_uvicorn_run + self, mock_getpgid, mock_uvicorn_run ): """Test that the printed kill command has the correct format.""" - # Arrange test_pgids = [1, 100, 12345, 999999] for pgid in test_pgids: mock_getpgid.return_value = pgid - mock_print.reset_mock() - # Act - main() + result = CliRunner().invoke(main, []) - # Assert + assert result.exit_code == 0 expected_message = f"'kill -- -{pgid}' to stop the server" - mock_print.assert_called_once_with(expected_message) + assert result.output.strip() == expected_message diff --git a/frontend/README.md b/frontend/README.md index e906f4e..feb7d2f 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -72,7 +72,17 @@ npm run format:check ## API Integration -The frontend expects the backend API to be running on `http://localhost:8000`. The Vite dev server is configured to proxy `/api` requests to the backend. +The Vite dev server proxies `/api` requests to the backend. The backend port defaults to **8080** and is controlled by the `BACKEND_PORT` environment variable (read by `vite.config.js`). + +When using `./run-local.sh` from the repo root, `BACKEND_PORT` is set automatically from `--port` (e.g. `./run-local.sh --port 9000`). + +For `npm run dev` alone, set the port explicitly: + +```bash +BACKEND_PORT=9000 npm run dev +``` + +Or add `BACKEND_PORT=8080` to a `.env.development` file in this directory. ### API Endpoints diff --git a/frontend/vite.config.js b/frontend/vite.config.js index a4be00b..b384d54 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,21 +1,42 @@ // Generated-by: Cursor -import { defineConfig } from 'vite'; +// Assisted-by: Cursor +import { defineConfig, loadEnv } from 'vite'; import react from '@vitejs/plugin-react'; -export default defineConfig({ - plugins: [react()], - server: { - port: 3000, - proxy: { - '/api': { - target: 'http://localhost:8000', - changeOrigin: true, +const DEFAULT_BACKEND_PORT = 8080; + +function resolveBackendPort(env) { + const raw = env.BACKEND_PORT; + if (raw === undefined || raw === '') { + return DEFAULT_BACKEND_PORT; + } + const port = Number.parseInt(raw, 10); + if (!Number.isFinite(port) || port < 1 || port > 65535) { + return DEFAULT_BACKEND_PORT; + } + return port; +} + +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ''); + const backendPort = resolveBackendPort(env); + const backendTarget = `http://localhost:${backendPort}`; + + return { + plugins: [react()], + server: { + port: 3000, + proxy: { + '/api': { + target: backendTarget, + changeOrigin: true, + }, }, }, - }, - test: { - globals: true, - environment: 'jsdom', - setupFiles: './src/test/setup.js', - }, + test: { + globals: true, + environment: 'jsdom', + setupFiles: './src/test/setup.js', + }, + }; }); diff --git a/run-local.sh b/run-local.sh index 36f1054..7d9eec4 100755 --- a/run-local.sh +++ b/run-local.sh @@ -5,7 +5,9 @@ # development. # # Usage: -# ./run-local.sh +# ./run-local.sh [--port ] +# +# Extra arguments are passed through to the github_pm CLI (e.g. --port 9000). # # Dependencies: # - Users will need to update the backend/.env file to meet their needs or @@ -21,6 +23,8 @@ # - GITHUB_REPO: the GitHub repository to use for authentication # - This is used to identify the repository in the UI. # - This is optional and will default to "vllm-project/guidellm". +# - BACKEND_PORT: set automatically from --port (default 8080) for the Vite +# dev proxy when starting the frontend via this script. # # Assisted-by: Cursor AI if [ ! -z "${DEBUG}" ]; then @@ -35,6 +39,28 @@ if [ ! -f "${BACKEND}/.env" -a -z "${GITHUB_TOKEN}" ]; then exit 1 fi +# Forward script args to github_pm; derive port for readiness checks and messages. +GITHUB_PM_ARGS=("$@") +BACKEND_PORT=8080 +args=("${GITHUB_PM_ARGS[@]}") +idx=0 +while [ "${idx}" -lt "${#args[@]}" ]; do + arg="${args[idx]}" + case "${arg}" in + --port) + next=$((idx + 1)) + if [ "${next}" -lt "${#args[@]}" ]; then + BACKEND_PORT="${args[next]}" + fi + ;; + --port=*) + BACKEND_PORT="${arg#--port=}" + ;; + esac + idx=$((idx + 1)) +done +export BACKEND_PORT + # Make sure all dependencies are installed. temp_file=$(mktemp) echo "Installing dependencies... (${temp_file})" @@ -56,7 +82,7 @@ fi echo "Starting backend..." ( cd ${BACKEND} - uv run github_pm + uv run github_pm "${GITHUB_PM_ARGS[@]}" ) & backend_pid=$! @@ -86,7 +112,7 @@ fi # Let frontend and backend start up and write their output before we finish, # or our helpful note will be lost. waiting=0 -while ! curl -s http://localhost:8000/ > /dev/null 2>&1; do +while ! curl -s "http://localhost:${BACKEND_PORT}/" > /dev/null 2>&1; do if [ ${waiting} -eq 0 ]; then echo "Waiting for backend to start..." fi @@ -112,7 +138,7 @@ done echo "" echo "--------------------------------" -echo "Backend is running in the background at http://localhost:8000" +echo "Backend is running in the background at http://localhost:${BACKEND_PORT}" echo "Frontend is running in the background at http://localhost:3000" echo "" echo "To terminate, run:"