Architecture - ahmadzein/portkeeper GitHub Wiki

Architecture

Technical architecture and design of Port Keeper.

๐Ÿ—๏ธ System Overview

Port Keeper follows a modular architecture with clear separation between CLI, GUI, and core functionality.

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                   User Interface                     โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚      CLI (Node.js)  โ”‚      GUI (Electron)           โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                  Core Library                        โ”‚
โ”‚        (Port Service, Database, Models)              โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                Database (SQLite)                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“ Project Structure

portkeeper/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ cli/                    # CLI implementation
โ”‚   โ”‚   โ”œโ”€โ”€ index.ts           # CLI entry point
โ”‚   โ”‚   โ”œโ”€โ”€ commands/          # Command implementations
โ”‚   โ”‚   โ””โ”€โ”€ utils/             # CLI utilities
โ”‚   โ”‚
โ”‚   โ”œโ”€โ”€ gui/                    # GUI implementation
โ”‚   โ”‚   โ”œโ”€โ”€ main/              # Electron main process
โ”‚   โ”‚   โ”œโ”€โ”€ preload/           # Preload scripts
โ”‚   โ”‚   โ””โ”€โ”€ renderer/          # React application
โ”‚   โ”‚       โ”œโ”€โ”€ components/    # React components
โ”‚   โ”‚       โ”œโ”€โ”€ pages/         # Page components
โ”‚   โ”‚       โ”œโ”€โ”€ hooks/         # Custom React hooks
โ”‚   โ”‚       โ””โ”€โ”€ store/         # State management
โ”‚   โ”‚
โ”‚   โ””โ”€โ”€ core/                   # Shared core logic
โ”‚       โ”œโ”€โ”€ models/            # Data models
โ”‚       โ”œโ”€โ”€ services/          # Business logic
โ”‚       โ””โ”€โ”€ database/          # Database layer
โ”‚
โ”œโ”€โ”€ dist/                       # Compiled output
โ”œโ”€โ”€ scripts/                    # Build/utility scripts
โ”œโ”€โ”€ tests/                      # Test files
โ””โ”€โ”€ docs/                       # Documentation

๐Ÿ”ง Core Components

1. Port Service

The central service managing all port operations.

// src/core/services/PortService.ts
export class PortService {
  constructor(private db: PortDatabase) {}
  
  async checkPort(port: number): Promise<PortStatus> {
    // Check database for reservation
    const dbPort = await this.db.getPort(port);
    if (dbPort?.status === 'reserved') {
      return this.checkReservedPort(dbPort);
    }
    
    // Check if port is actively in use
    const isActive = await this.isPortInUse(port);
    if (isActive) {
      return this.getActivePortInfo(port);
    }
    
    return { status: 'free', port };
  }
  
  async reservePort(
    port: number,
    projectName: string,
    description?: string,
    tags?: string[]
  ): Promise<Port> {
    // Validate port availability
    const status = await this.checkPort(port);
    if (status.status !== 'free') {
      throw new PortError('Port not available');
    }
    
    // Create reservation
    return this.db.createPort({
      number: port,
      projectName,
      description,
      status: 'reserved',
      tags
    });
  }
}

2. Database Layer

Abstraction over SQLite operations.

// src/core/database/Database.ts
export class PortDatabase {
  private db: Database;
  
  constructor(dbPath: string) {
    this.db = new Database(dbPath);
    this.initialize();
  }
  
  private initialize() {
    // Create tables if not exists
    this.db.exec(SCHEMA_SQL);
    
    // Run migrations
    this.runMigrations();
  }
  
  async getPort(number: number): Promise<Port | null> {
    return this.db.prepare(
      'SELECT * FROM ports WHERE number = ?'
    ).get(number);
  }
  
  async createPort(port: Partial<Port>): Promise<Port> {
    const stmt = this.db.prepare(`
      INSERT INTO ports (number, projectName, description, status, tags)
      VALUES (@number, @projectName, @description, @status, @tags)
    `);
    
    stmt.run(port);
    return this.getPort(port.number!);
  }
}

3. CLI Architecture

Command-based architecture using Commander.js.

// src/cli/index.ts
import { Command } from 'commander';

const program = new Command();

program
  .name('portman')
  .description('Port Keeper - Manage development ports')
  .version(VERSION);

// Register commands
registerCommands(program);

// Parse arguments
program.parse(process.argv);

Each command is a separate module:

// src/cli/commands/reserve.ts
export function registerReserveCommand(program: Command) {
  program
    .command('reserve <port>')
    .description('Reserve a port')
    .option('-n, --name <name>', 'Project name')
    .option('-d, --desc <description>', 'Description')
    .option('-t, --tags <tags...>', 'Tags')
    .action(async (port, options) => {
      const service = await getPortService();
      const result = await service.reservePort(
        parseInt(port),
        options.name,
        options.desc,
        options.tags
      );
      
      displayResult(result, options);
    });
}

4. GUI Architecture

Electron-based with React frontend.

Main Process

// src/gui/main/index.ts
import { app, BrowserWindow, ipcMain } from 'electron';

let mainWindow: BrowserWindow | null;

app.whenReady().then(() => {
  mainWindow = new BrowserWindow({
    width: 1200,
    height: 800,
    webPreferences: {
      preload: path.join(__dirname, '../preload/index.js'),
      contextIsolation: true
    }
  });
  
  // Register IPC handlers
  registerIPCHandlers();
  
  // Load the app
  mainWindow.loadFile('dist/renderer/index.html');
});

Preload Script

// src/gui/preload/index.ts
import { contextBridge, ipcRenderer } from 'electron';

contextBridge.exposeInMainWorld('portManager', {
  port: {
    check: (port: number) => ipcRenderer.invoke('port:check', port),
    reserve: (port: number, options: any) => 
      ipcRenderer.invoke('port:reserve', port, options),
    list: (options: any) => ipcRenderer.invoke('port:list', options),
    release: (ports: number[]) => 
      ipcRenderer.invoke('port:release', ports)
  }
});

React Application

// src/gui/renderer/App.tsx
import React from 'react';
import { Dashboard } from './pages/Dashboard';
import { ScanView } from './pages/ScanView';
import { usePortStore } from './store/portStore';

export function App() {
  const { ports, fetchPorts } = usePortStore();
  
  React.useEffect(() => {
    fetchPorts();
  }, []);
  
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Dashboard />} />
        <Route path="/scan" element={<ScanView />} />
      </Routes>
    </Router>
  );
}

๐Ÿ”„ Data Flow

CLI Data Flow

User Input โ†’ Commander.js โ†’ Command Handler โ†’ Port Service โ†’ Database โ†’ Response โ†’ Console Output

GUI Data Flow

User Action โ†’ React Component โ†’ IPC Renderer โ†’ IPC Main โ†’ Port Service โ†’ Database โ†’ IPC Response โ†’ State Update โ†’ UI Update

๐Ÿ’พ State Management

CLI State

  • Stateless between commands
  • Configuration loaded per command
  • Database handles persistence

GUI State (Zustand)

// src/gui/renderer/store/portStore.ts
interface PortStore {
  ports: Port[];
  loading: boolean;
  error: string | null;
  
  fetchPorts: () => Promise<void>;
  reservePort: (port: number, options: any) => Promise<void>;
  releasePort: (port: number) => Promise<void>;
}

export const usePortStore = create<PortStore>((set, get) => ({
  ports: [],
  loading: false,
  error: null,
  
  fetchPorts: async () => {
    set({ loading: true });
    try {
      const ports = await window.portManager.port.list();
      set({ ports, loading: false });
    } catch (error) {
      set({ error: error.message, loading: false });
    }
  }
}));

๐Ÿ”Œ Plugin Architecture

Future support for plugins:

interface PortKeeperPlugin {
  name: string;
  version: string;
  
  // Lifecycle hooks
  onInstall?: () => Promise<void>;
  onUninstall?: () => Promise<void>;
  
  // Event hooks
  onPortReserved?: (port: Port) => Promise<void>;
  onPortReleased?: (port: Port) => Promise<void>;
  
  // Command extensions
  commands?: CommandDefinition[];
  
  // GUI extensions
  components?: ComponentDefinition[];
}

๐Ÿ”’ Security Architecture

Process Isolation

  • GUI renderer process isolated from main
  • No direct file system access from renderer
  • All operations through IPC

Input Validation

// Port number validation
function validatePort(port: number): void {
  if (port < 1 || port > 65535) {
    throw new Error('Invalid port number');
  }
}

// Project name sanitization
function sanitizeProjectName(name: string): string {
  return name.replace(/[^a-zA-Z0-9-_]/g, '');
}

Permission Model

  • User-level permissions only
  • No elevated privileges required
  • Database file permissions: 600

๐Ÿš€ Performance Considerations

Database Performance

  • SQLite with WAL mode
  • Indexed columns for fast queries
  • Connection pooling in GUI

Port Scanning

  • Parallel scanning with worker threads
  • Configurable batch sizes
  • Caching of scan results

GUI Performance

  • Virtual scrolling for large lists
  • Debounced search inputs
  • React.memo for expensive components

๐Ÿงช Testing Architecture

Unit Tests

// tests/unit/services/PortService.test.ts
describe('PortService', () => {
  let service: PortService;
  let db: MockDatabase;
  
  beforeEach(() => {
    db = new MockDatabase();
    service = new PortService(db);
  });
  
  test('reserves available port', async () => {
    const port = await service.reservePort(3000, 'test');
    expect(port.status).toBe('reserved');
  });
});

Integration Tests

// tests/integration/cli.test.ts
describe('CLI Integration', () => {
  test('reserve and release flow', async () => {
    const { stdout: reserve } = await runCLI('reserve 3000 -n test');
    expect(reserve).toContain('reserved');
    
    const { stdout: release } = await runCLI('release 3000');
    expect(release).toContain('released');
  });
});

๐Ÿ“ฆ Build System

TypeScript Compilation

  • Separate configs for CLI and GUI
  • Source maps for debugging
  • Declaration files for types

Electron Build

// electron.vite.config.ts
export default defineConfig({
  main: {
    build: {
      rollupOptions: {
        external: ['electron', 'better-sqlite3']
      }
    }
  },
  preload: {
    build: {
      rollupOptions: {
        external: ['electron']
      }
    }
  },
  renderer: {
    build: {
      rollupOptions: {
        input: 'src/gui/renderer/index.html'
      }
    }
  }
});

๐Ÿ”ฎ Future Architecture

Planned Enhancements

  1. Microservices Support

    • Service discovery integration
    • Dynamic port allocation
    • Health checking
  2. Cloud Integration

    • Remote port management
    • Team synchronization
    • Cloud database support
  3. API Server Mode

    • REST API
    • WebSocket support
    • GraphQL endpoint
  4. Plugin System

    • Custom commands
    • UI extensions
    • Integration hooks

For implementation details, see the source code or Contributing Guide.

โš ๏ธ **GitHub.com Fallback** โš ๏ธ