Portman Extension
VSCode extension to manage ports on any operating system from an integrated graphical interface.
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:
- Probe: Execute network analysis utilities (e.g.,
lsof,netstat,ss). - Filter: Process standard output (stdout) to isolate the process bound to the conflicting port.
- Identify: Extract the Process Identifier (PID).
- 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:
- 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.
- Transformation and Mapping: The raw information extracted from the OS is normalized into structured domain models.
- Real-Time Presentation: The normalized models are rendered in an interactive tree structure within the VS Code graphical interface.
- 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 ofAddressHost(local/remote IP or hostname) andAddressPort(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 ofProcessobjects.KillProcess: Emits a Command to the repository to kill a specificProcessobject.
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 vianetstat. It features a graceful fallback mechanism to thessbinary if the former is missing. Furthermore, it evaluates theportman.linux.asRootUsersetting to elevate privileges usingpkexecorsudoif required.MacProcessRepository: Natively interacts withlsof -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 / Tool | Architectural Role | Functional Purpose |
|---|---|---|
| TypeScript (v5.x) | Primary Language | Provides static analysis and strong typing, crucial for modeling Domain layer entities and ensuring cross-layer contracts. |
| VS Code Extension API | Execution Environment | Provides the programmatic interface (vscode.*) for native UI rendering, command registration, and sidebar panel access. |
| Webpack (v5.x) | Bundler | Combines, 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.