Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <n>` 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:
Expand All @@ -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
Expand Down
17 changes: 14 additions & 3 deletions backend/src/github_pm/cli.py
Original file line number Diff line number Diff line change
@@ -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,
)

Expand Down
62 changes: 34 additions & 28 deletions backend/tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -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()

Expand All @@ -17,72 +20,75 @@ 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,
)

@patch("github_pm.cli.uvicorn.run")
@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
12 changes: 11 additions & 1 deletion frontend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
51 changes: 36 additions & 15 deletions frontend/vite.config.js
Original file line number Diff line number Diff line change
@@ -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',
},
};
});
34 changes: 30 additions & 4 deletions run-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
# development.
#
# Usage:
# ./run-local.sh
# ./run-local.sh [--port <n>]
#
# 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
Expand All @@ -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
Expand All @@ -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})"
Expand All @@ -56,7 +82,7 @@ fi
echo "Starting backend..."
(
cd ${BACKEND}
uv run github_pm
uv run github_pm "${GITHUB_PM_ARGS[@]}"
) &
backend_pid=$!

Expand Down Expand Up @@ -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
Expand All @@ -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:"
Expand Down
Loading