Skip to content

Commit 2afc65f

Browse files
committed
refactor: move getPort to @workflow/utils/get-port
1 parent bb24361 commit 2afc65f

File tree

12 files changed

+161
-160
lines changed

12 files changed

+161
-160
lines changed

.changeset/smooth-rats-attack.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
"@workflow/core": patch
3+
"@workflow/utils": patch
34
"@workflow/world-local": patch
45
---
56

packages/core/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
"devalue": "^5.4.1",
6060
"ms": "2.1.3",
6161
"nanoid": "^5.1.6",
62-
"pid-port": "^2.0.0",
6362
"seedrandom": "^3.0.5",
6463
"ulid": "^3.0.1",
6564
"zod": "catalog:"

packages/core/src/runtime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
WorkflowRunNotCompletedError,
99
WorkflowRuntimeError,
1010
} from '@workflow/errors';
11+
import { getPort } from '@workflow/utils/get-port';
1112
import type {
1213
Event,
1314
WorkflowRun,
@@ -39,7 +40,6 @@ import { serializeTraceCarrier, trace, withTraceContext } from './telemetry.js';
3940
import { getErrorName, getErrorStack } from './types.js';
4041
import {
4142
buildWorkflowSuspensionMessage,
42-
getPort,
4343
getWorkflowRunStreamId,
4444
} from './util.js';
4545
import { runWorkflow } from './workflow.js';

packages/core/src/util.test.ts

Lines changed: 1 addition & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import http from 'node:http';
22
import { describe, expect, it } from 'vitest';
3-
import {
4-
buildWorkflowSuspensionMessage,
5-
getPort,
6-
getWorkflowRunStreamId,
7-
} from './util';
3+
import { buildWorkflowSuspensionMessage, getWorkflowRunStreamId } from './util';
84

95
describe('buildWorkflowSuspensionMessage', () => {
106
const runId = 'test-run-123';
@@ -170,93 +166,3 @@ describe('getWorkflowRunStreamId', () => {
170166
expect(result.includes('_user')).toBe(true);
171167
});
172168
});
173-
174-
describe('getPort', () => {
175-
it('should return undefined or a positive number', async () => {
176-
const port = await getPort();
177-
expect(port === undefined || typeof port === 'number').toBe(true);
178-
if (port !== undefined) {
179-
expect(port).toBeGreaterThan(0);
180-
}
181-
});
182-
183-
it('should return a port number when a server is listening', async () => {
184-
const server = http.createServer();
185-
186-
await new Promise<void>((resolve) => {
187-
server.listen(0, () => resolve());
188-
});
189-
190-
// Give system time to register the port
191-
await new Promise((resolve) => setTimeout(resolve, 100));
192-
193-
try {
194-
const port = await getPort();
195-
const address = server.address();
196-
197-
// Port detection may not work immediately in all environments (CI, Docker, etc.)
198-
// so we just verify the function returns a valid result
199-
if (port !== undefined) {
200-
expect(typeof port).toBe('number');
201-
expect(port).toBeGreaterThan(0);
202-
203-
// If we have the address, optionally verify it matches
204-
if (address && typeof address === 'object') {
205-
// In most cases it should match, but not required for test to pass
206-
expect([port, undefined]).toContain(port);
207-
}
208-
}
209-
} finally {
210-
await new Promise<void>((resolve, reject) => {
211-
server.close((err) => (err ? reject(err) : resolve()));
212-
});
213-
}
214-
});
215-
216-
it('should return the smallest port when multiple servers are listening', async () => {
217-
const server1 = http.createServer();
218-
const server2 = http.createServer();
219-
220-
await new Promise<void>((resolve) => {
221-
server1.listen(0, () => resolve());
222-
});
223-
224-
await new Promise<void>((resolve) => {
225-
server2.listen(0, () => resolve());
226-
});
227-
228-
// Give system time to register the ports
229-
await new Promise((resolve) => setTimeout(resolve, 100));
230-
231-
try {
232-
const port = await getPort();
233-
const addr1 = server1.address();
234-
const addr2 = server2.address();
235-
236-
// Port detection may not work in all environments
237-
if (
238-
port !== undefined &&
239-
addr1 &&
240-
typeof addr1 === 'object' &&
241-
addr2 &&
242-
typeof addr2 === 'object'
243-
) {
244-
// Should return the smallest port
245-
expect(port).toBeLessThanOrEqual(Math.max(addr1.port, addr2.port));
246-
expect(port).toBeGreaterThan(0);
247-
} else {
248-
// If port detection doesn't work in this environment, just pass
249-
expect(port === undefined || typeof port === 'number').toBe(true);
250-
}
251-
} finally {
252-
await Promise.all([
253-
new Promise<void>((resolve, reject) => {
254-
server1.close((err) => (err ? reject(err) : resolve()));
255-
}),
256-
new Promise<void>((resolve, reject) => {
257-
server2.close((err) => (err ? reject(err) : resolve()));
258-
}),
259-
]);
260-
}
261-
});
262-
});

packages/core/src/util.ts

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import { pidToPorts } from 'pid-port';
2-
31
/**
42
* Builds a workflow suspension log message based on the counts of steps, hooks, and waits.
53
* @param runId - The workflow run ID
@@ -64,25 +62,3 @@ export function getWorkflowRunStreamId(runId: string, namespace?: string) {
6462
);
6563
return `${streamId}_${encodedNamespace}`;
6664
}
67-
68-
/**
69-
* Gets the port number that the process is listening on.
70-
* @returns The port number that the process is listening on, or undefined if the process is not listening on any port.
71-
* NOTE: Can't move this to @workflow/utils because it's being imported into @workflow/errors for RetryableError (inside workflow runtime)
72-
*/
73-
export async function getPort(): Promise<number | undefined> {
74-
try {
75-
const pid = process.pid;
76-
const ports = await pidToPorts(pid);
77-
if (!ports || ports.size === 0) {
78-
return undefined;
79-
}
80-
81-
const smallest = Math.min(...ports);
82-
return smallest;
83-
} catch {
84-
// If port detection fails (e.g., `ss` command not available in production),
85-
// return undefined and fall back to default port
86-
return undefined;
87-
}
88-
}

packages/core/src/workflow.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { runInContext } from 'node:vm';
22
import { ERROR_SLUGS } from '@workflow/errors';
33
import { withResolvers } from '@workflow/utils';
4+
import { getPort } from '@workflow/utils/get-port';
45
import type { Event, WorkflowRun } from '@workflow/world';
56
import * as nanoid from 'nanoid';
67
import { monotonicFactory } from 'ulid';
@@ -21,7 +22,7 @@ import {
2122
} from './symbols.js';
2223
import * as Attribute from './telemetry/semantic-conventions.js';
2324
import { trace } from './telemetry.js';
24-
import { getPort, getWorkflowRunStreamId } from './util.js';
25+
import { getWorkflowRunStreamId } from './util.js';
2526
import { createContext } from './vm/index.js';
2627
import type { WorkflowMetadata } from './workflow/get-workflow-metadata.js';
2728
import { WORKFLOW_CONTEXT_SYMBOL } from './workflow/get-workflow-metadata.js';

packages/utils/package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@
2020
".": {
2121
"types": "./dist/index.d.ts",
2222
"default": "./dist/index.js"
23+
},
24+
"./get-port": {
25+
"types": "./dist/get-port.d.ts",
26+
"default": "./dist/get-port.js"
2327
}
2428
},
2529
"scripts": {
@@ -36,6 +40,7 @@
3640
"vitest": "catalog:"
3741
},
3842
"dependencies": {
39-
"ms": "2.1.3"
43+
"ms": "2.1.3",
44+
"pid-port": "^2.0.0"
4045
}
4146
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import http from 'node:http';
2+
import { describe, expect, it } from 'vitest';
3+
import { getPort } from './get-port';
4+
5+
describe('getPort', () => {
6+
it('should return undefined or a positive number', async () => {
7+
const port = await getPort();
8+
expect(port === undefined || typeof port === 'number').toBe(true);
9+
if (port !== undefined) {
10+
expect(port).toBeGreaterThan(0);
11+
}
12+
});
13+
14+
it('should return a port number when a server is listening', async () => {
15+
const server = http.createServer();
16+
17+
await new Promise<void>((resolve) => {
18+
server.listen(0, () => resolve());
19+
});
20+
21+
// Give system time to register the port
22+
await new Promise((resolve) => setTimeout(resolve, 100));
23+
24+
try {
25+
const port = await getPort();
26+
const address = server.address();
27+
28+
// Port detection may not work immediately in all environments (CI, Docker, etc.)
29+
// so we just verify the function returns a valid result
30+
if (port !== undefined) {
31+
expect(typeof port).toBe('number');
32+
expect(port).toBeGreaterThan(0);
33+
34+
// If we have the address, optionally verify it matches
35+
if (address && typeof address === 'object') {
36+
// In most cases it should match, but not required for test to pass
37+
expect([port, undefined]).toContain(port);
38+
}
39+
}
40+
} finally {
41+
await new Promise<void>((resolve, reject) => {
42+
server.close((err) => (err ? reject(err) : resolve()));
43+
});
44+
}
45+
});
46+
47+
it('should return the smallest port when multiple servers are listening', async () => {
48+
const server1 = http.createServer();
49+
const server2 = http.createServer();
50+
51+
await new Promise<void>((resolve) => {
52+
server1.listen(0, () => resolve());
53+
});
54+
55+
await new Promise<void>((resolve) => {
56+
server2.listen(0, () => resolve());
57+
});
58+
59+
// Give system time to register the ports
60+
await new Promise((resolve) => setTimeout(resolve, 100));
61+
62+
try {
63+
const port = await getPort();
64+
const addr1 = server1.address();
65+
const addr2 = server2.address();
66+
67+
// Port detection may not work in all environments
68+
if (
69+
port !== undefined &&
70+
addr1 &&
71+
typeof addr1 === 'object' &&
72+
addr2 &&
73+
typeof addr2 === 'object'
74+
) {
75+
// Should return the smallest port
76+
expect(port).toBeLessThanOrEqual(Math.max(addr1.port, addr2.port));
77+
expect(port).toBeGreaterThan(0);
78+
} else {
79+
// If port detection doesn't work in this environment, just pass
80+
expect(port === undefined || typeof port === 'number').toBe(true);
81+
}
82+
} finally {
83+
await Promise.all([
84+
new Promise<void>((resolve, reject) => {
85+
server1.close((err) => (err ? reject(err) : resolve()));
86+
}),
87+
new Promise<void>((resolve, reject) => {
88+
server2.close((err) => (err ? reject(err) : resolve()));
89+
}),
90+
]);
91+
}
92+
});
93+
});

packages/utils/src/get-port.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { pidToPorts } from 'pid-port';
2+
3+
/**
4+
* Gets the port number that the process is listening on.
5+
* @returns The port number that the process is listening on, or undefined if the process is not listening on any port.
6+
* NOTE: Can't move this to @workflow/utils because it's being imported into @workflow/errors for RetryableError (inside workflow runtime)
7+
*/
8+
export async function getPort(): Promise<number | undefined> {
9+
try {
10+
const pid = process.pid;
11+
const ports = await pidToPorts(pid);
12+
if (!ports || ports.size === 0) {
13+
return undefined;
14+
}
15+
16+
const smallest = Math.min(...ports);
17+
return smallest;
18+
} catch {
19+
// If port detection fails (e.g., `ss` command not available in production),
20+
// return undefined and fall back to default port
21+
return undefined;
22+
}
23+
}

packages/world-local/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
"@vercel/queue": "0.0.0-alpha.23",
3434
"@workflow/utils": "workspace:*",
3535
"@workflow/world": "workspace:*",
36-
"pid-port": "^2.0.0",
3736
"ulid": "^3.0.1",
3837
"undici": "^6.19.0",
3938
"zod": "catalog:"

0 commit comments

Comments
 (0)