Skip to content

refactor: Replace portscanner in server with a native port scan - #1630

Merged
d3xter666 merged 3 commits into
mainfrom
refactor-replace-port-scanner-native
Oct 2, 2026
Merged

d3xter666 merged 3 commits into
mainfrom
refactor-replace-port-scanner-native

Conversation

@d3xter666

Copy link
Copy Markdown
Member

JIRA: CPOUI5FOUNDATION-1359

Replace the unmaintained portscanner dependency with a small connect-probe helper built on node:net, removing the dependency entirely.

isPortInUse() mirrors portscanner's semantics exactly: a successful TCP connection means the port is in use, a refused connection or timeout means it is free, and any other socket error rejects as a scan failure. findAPortNotInUse() scans the inclusive range for the first free port.

Because the probe semantics are unchanged, the existing behavior is fully preserved (including detection of occupiers bound to the dualstack :: address) and no test occupiers needed adapting. Only the former portscanner-stub test was rewritten to inject a generic socket error via esmock.

Replace the unmaintained portscanner dependency (last published 2018,
pulls in async + is-number-like) with a small connect-probe helper built
on node:net, removing the dependency entirely.

isPortInUse() mirrors portscanner's semantics exactly: a successful TCP
connection means the port is in use, a refused connection or timeout
means it is free, and any other socket error rejects as a scan failure.
findAPortNotInUse() scans the inclusive range for the first free port.

Because the probe semantics are unchanged, the existing behavior is
fully preserved (including detection of occupiers bound to the dualstack
:: address) and no test occupiers needed adapting. Only the former
portscanner-stub test was rewritten to inject a generic socket error via
esmock.
@d3xter666 d3xter666 changed the title refactor(server): Replace portscanner with a native port scan refactor: Replace portscanner in server with a native port scan Oct 1, 2026
@d3xter666

Copy link
Copy Markdown
Member Author

Options considered

get-port — third-party library (sindresorhus, zero deps, ESM). Uses a bind-probe: tries to bind a throwaway server on each candidate port.

direct-listen — no library. Binds the real server directly, retries on EADDRINUSE. No separate probe, no TOCTOU race.

native — no library. Uses a connect-probe: opens a TCP socket to each candidate port. Mirrors portscanner's exact behavior.

Why native

Both get-port and direct-listen use bind semantics. On macOS/BSD, :::8080 and 127.0.0.1:8080 are independent socket addresses that can coexist — so if ui5 serve --port 8080 is already running and you start ui5 serve --port 8080 --accept-remote-connections, the bind-probe reports 8080 as free and a second server silently starts on the same port.

A connect-probe has no such blind spot: socket.connect(8080, "127.0.0.1") succeeds against any occupier regardless of how it is bound. Port is correctly detected as in use.

Native is a drop-in replacement for portscanner with identical semantics — no behavior change, no new dependency, CVE-flagged async removed.

@d3xter666
d3xter666 requested a review from a team October 1, 2026 14:48
matz3
matz3 previously approved these changes Oct 2, 2026

@matz3 matz3 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, tested locally on macOS.

RandomByte
RandomByte previously approved these changes Oct 2, 2026

@RandomByte RandomByte left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Replace the outer new Promise wrapper in listen() with linear async/await.
The only remaining explicit Promise is isolated in a listenOnce() helper that
bridges server.listen's separate 'listening' and 'error' event channels,
detaching the losing listener once one fires (the old code leaked the error
listener on every successful bind).
@matz3
matz3 dismissed stale reviews from RandomByte and themself via a79a27d October 2, 2026 09:00
Release event all handlers once an event has been executed.
@d3xter666
d3xter666 requested review from RandomByte and matz3 October 2, 2026 11:13
@d3xter666
d3xter666 force-pushed the refactor-replace-port-scanner-native branch from 040fd7f to 6c604b1 Compare October 2, 2026 12:00
@d3xter666
d3xter666 merged commit e409afe into main Oct 2, 2026
161 of 164 checks passed
@d3xter666
d3xter666 deleted the refactor-replace-port-scanner-native branch October 2, 2026 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants