Back to projects
Portman Extension logo

Portman Extension

VSCode extension to manage ports on any operating system from an integrated graphical interface.

+2k downloads VSCode Marketplace

During software development, a recurring operational hurdle is network port allocation collision (known in Node.js environments as the EADDRINUSE error). This occurs when a previous process, whether intentionally running or orphaned (zombie), holds an open listening socket on a specific network interface and port, preventing a new instance from binding to that resource.

Manual resolution of this state requires the developer to directly interact with the operating system via a command line interface (CLI) to:

  1. Probe: Execute network analysis utilities (e.g., lsof, netstat, ss).
  2. Filter: Process standard output (stdout) to isolate the process bound to the conflicting port.
  3. Identify: Extract the Process Identifier (PID).
  4. Terminate: Send a system signal (such as SIGKILL) to the identified PID.

The Operational Solution

Portman automates and abstracts this stream of diagnosis and intervention, integrating it directly into the Integrated Development Environment (IDE).

The functional workflow is summarized as:

  1. System State Extraction: The extension transparently interacts with the host operating system’s native APIs and binaries to retrieve the current state of network connections.
  2. Transformation and Mapping: The raw information extracted from the OS is normalized into structured domain models.
  3. Real-Time Presentation: The normalized models are rendered in an interactive tree structure within the VS Code graphical interface.
  4. Action Execution: Through built-in commands, the tool allows the user to select a tree node and dispatch the termination signal to the underlying process in a unified manner, regardless of the operating system being used.

Technical Analysis: Architecture and Models

To ensure that the software lifecycle supports sustainable growth, isolated testability, and transversal adaptability across multiple operating systems (Linux, macOS, Windows), I have designed the project structure under the principles of Clean Architecture and Domain-Driven Design (DDD).

The code is conceptually divided into four concentric layers, where dependency strictly flows towards the center (the Domain).

graph TD
    subgraph ext["Outer Layer (Infrastructure and Presentation)"]
        VS[VS Code Extension API]
        CLI[OS Binaries: netstat, lsof, ss]
    end

    subgraph pres["Presentation Layer"]
        PTD[ProcessTreeDataProvider]
        Cmds[KillProcessCommand]
    end

    subgraph app["Application Layer"]
        SP[UseCase: SearchProcesses]
        KP[UseCase: KillProcess]
    end

    subgraph dom["Domain Layer"]
        Model[Aggregate Root: Process]
        RepoInt[Interface: ProcessRepository]
    end

    VS --> PTD
    VS --> Cmds
    PTD --> SP
    Cmds --> KP
    SP --> RepoInt
    KP --> RepoInt
    CLI -.->|Implementation| RepoInt

    classDef domain fill:#d4edda,stroke:#28a745,stroke-width:2px,color:#155724;
    classDef app fill:#cce5ff,stroke:#007bff,stroke-width:2px,color:#004085;
    classDef pres fill:#fff3cd,stroke:#ffc107,stroke-width:2px,color:#856404;
    classDef external fill:#f8f9fa,stroke:#6c757d,stroke-width:1px,color:#495057;

    class Model,RepoInt domain;
    class SP,KP app;
    class PTD,Cmds pres;
    class VS,CLI external;

1. The Domain Core

The domain model is isolated from any external dependency, framework, or operating system detail. Its main goal is to model the abstraction of a network process.

The Process Aggregate

The central entity is Process, which acts as the Aggregate Root. It is composed of multiple Value Objects that guarantee data integrity at the time of its instantiation:

  • ProcessId: Unique identifier (PID).
  • ProcessProgram: Name of the executable or daemon.
  • Protocol: Transport protocol (TCP, UDP).
  • Address: Composition of AddressHost (local/remote IP or hostname) and AddressPort (numeric port).
  • ProcessStatus: Current status of the connection (e.g., LISTEN, ESTABLISHED).
classDiagram
    class Process {
        +ProcessId id
        +ProcessProgram program
        +Protocol protocol
        +Address local
        +Address remote
        +ProcessStatus status
        +String icon
        +String label
        +String description
        +String tooltip
    }

    class Address {
        +AddressHost host
        +AddressPort port
    }

    class ProcessRepository {
        <<Interface>>
        +search() Promise~Process[]~
        +kill(process: Process) Promise~void~
    }

    Process "1" *-- "2" Address : local/remote
    ProcessRepository --> Process : manages

Additionally, the domain defines the ProcessRepository contract, which establishes the fundamental search and termination operations that the system requires, without dictating how they are implemented.

2. Application Layer

The application layer orchestrates business rules using domain contracts. It is structured around specific Use Cases:

  • SearchProcesses: Emits a Query to the repository to retrieve the array of Process objects.
  • KillProcess: Emits a Command to the repository to kill a specific Process object.

These use cases are stateless and rely on the injection of the concrete repository at runtime.

3. Infrastructure Layer

This is where the technical implementations of the ProcessRepository interface reside. Dependency injection is managed at the application’s entry point (extension.ts), evaluating the process.platform environment variable to instantiate the correct adapter.

  • LinuxProcessRepository: Prioritizes extraction via netstat. It features a graceful fallback mechanism to the ss binary if the former is missing. Furthermore, it evaluates the portman.linux.asRootUser setting to elevate privileges using pkexec or sudo if required.
  • MacProcessRepository: Natively interacts with lsof -i -P -n.
  • WindowsProcessRepository: Invokes implementations based on PowerShell (Get-NetTCPConnection) or legacy MS-DOS commands (netstat -a -b -n -o).

Each repository delegates the parsing of the standard output to specific transformer classes (e.g., MacNetstatProcessTransformer), which parse raw text strings, validate the structure, and map the data to the Process domain object.

4. Presentation Layer

This layer isolates business logic from the extensive Visual Studio Code API. The main component is the ProcessTreeDataProvider, which implements VS Code’s TreeDataProvider interface. Its responsibility is to subscribe to IDE events, consume the SearchProcesses use case, and transform the array of Process entities into ProcessTreeItem (or ProcessQuickPickItem) instances for the final rendering of the visual tree in the User Interface. It also manages the capture and presentation of operational errors via built-in editor dialog boxes.


Technology Stack

The tool stack chosen for this project reflects the need to build an optimized, strongly typed packaged artifact tightly coupled to a Node.js (Electron) runtime environment.

Technology / ToolArchitectural RoleFunctional Purpose
TypeScript (v5.x)Primary LanguageProvides static analysis and strong typing, crucial for modeling Domain layer entities and ensuring cross-layer contracts.
VS Code Extension APIExecution EnvironmentProvides the programmatic interface (vscode.*) for native UI rendering, command registration, and sidebar panel access.
Webpack (v5.x)BundlerCombines, transpiles, and minifies the source code into an optimized extension.js file, reducing plugin activation latency.

Conclusion

The strict adoption of Clean Architecture in an IDE extension project—an environment fundamentally coupled to the view layer—has resulted in an absolute separation of concerns. The logical core for resolving network dependencies is completely agnostic to both the underlying operating system and the VS Code presentation framework. This abstraction guarantees that native integrations can be modified, optimized, or replaced (e.g., transitioning from CLI commands to C++ kernel invocations) without producing side effects on the business rules or the presentation logic of the port termination workflow.