Skip to content

Commit 0b1decc

Browse files
author
Your Name
committed
fix: replace detect-port with native net-based port detection for Alpine compatibility
The `detect-port` package shells out to `ps -p` to check process ownership of ports, which fails on Alpine Linux / BusyBox where `ps` does not support the `-p` flag. Replace it with a pure Node.js implementation using `net.createServer()` that works on all platforms without spawning subprocesses. Fixes #33847
1 parent 6b05884 commit 0b1decc

File tree

3 files changed

+46
-8
lines changed

3 files changed

+46
-8
lines changed

code/core/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,6 @@
276276
"@testing-library/dom": "^10.4.1",
277277
"@testing-library/react": "^14.0.0",
278278
"@types/cross-spawn": "^6.0.6",
279-
"@types/detect-port": "^1.3.0",
280279
"@types/diff": "^5.0.9",
281280
"@types/ejs": "^3.1.1",
282281
"@types/js-yaml": "^4.0.5",
@@ -305,7 +304,6 @@
305304
"deep-object-diff": "^1.1.0",
306305
"dequal": "^2.0.2",
307306
"detect-indent": "^7.0.1",
308-
"detect-port": "^1.6.1",
309307
"diff": "^8.0.2",
310308
"downshift": "^9.0.4",
311309
"ejs": "^3.1.10",

code/core/src/core-server/utils/server-address.test.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { describe, expect, it, vi } from 'vitest';
1+
import net from 'node:net';
22

3-
import detectPort from 'detect-port';
3+
import { describe, expect, it, vi } from 'vitest';
44

55
import { getServerAddresses, getServerChannelUrl, getServerPort } from './server-address';
66

@@ -11,7 +11,7 @@ vi.mock('node:os', () => ({
1111
signals: {},
1212
},
1313
}));
14-
vi.mock('detect-port');
14+
vi.mock('node:net');
1515
vi.mock('storybook/internal/node-logger');
1616

1717
describe('getServerAddresses', () => {
@@ -60,7 +60,17 @@ describe('getServerPort', () => {
6060
it('should resolve with a free port', async () => {
6161
const expectedFreePort = 4000;
6262

63-
vi.mocked(detectPort).mockResolvedValue(expectedFreePort);
63+
const mockServer = {
64+
unref: vi.fn(),
65+
on: vi.fn(),
66+
listen: vi.fn((_port: number, cb: () => void) => {
67+
cb();
68+
return mockServer;
69+
}),
70+
address: vi.fn(() => ({ port: expectedFreePort })),
71+
close: vi.fn((cb: () => void) => cb()),
72+
};
73+
vi.mocked(net.createServer).mockReturnValue(mockServer as unknown as net.Server);
6474

6575
const result = await getServerPort(port);
6676

code/core/src/core-server/utils/server-address.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1+
import net from 'node:net';
12
import os from 'node:os';
23

34
import { logger } from 'storybook/internal/node-logger';
45

5-
import detectFreePort from 'detect-port';
6-
76
export function getServerAddresses(
87
port: number,
98
host: string | undefined,
@@ -31,6 +30,37 @@ interface PortOptions {
3130
exactPort?: boolean;
3231
}
3332

33+
/**
34+
* Checks if a given port is available by attempting to bind a TCP server to it.
35+
* Returns the port if available, or rejects with an error if it is in use.
36+
* This avoids shelling out to `ps` (which fails on Alpine/BusyBox environments).
37+
*/
38+
function checkPort(port: number): Promise<number> {
39+
return new Promise((resolve, reject) => {
40+
const server = net.createServer();
41+
server.unref();
42+
server.on('error', reject);
43+
server.listen(port, () => {
44+
const { port: assignedPort } = server.address() as net.AddressInfo;
45+
server.close(() => resolve(assignedPort));
46+
});
47+
});
48+
}
49+
50+
/**
51+
* Finds a free port starting from the given port number.
52+
* Falls back to an OS-assigned port (port 0) when the requested port is unavailable.
53+
*/
54+
export function detectFreePort(port?: number): Promise<number> {
55+
return checkPort(port || 0).catch((err) => {
56+
if (err.code === 'EADDRINUSE') {
57+
// Let the OS assign a free port
58+
return checkPort(0);
59+
}
60+
return Promise.reject(err);
61+
});
62+
}
63+
3464
export const getServerPort = (port?: number, { exactPort }: PortOptions = {}) =>
3565
detectFreePort(port)
3666
.then((freePort) => {

0 commit comments

Comments
 (0)