@mandujs/cli 0.18.3 → 0.18.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/util/port.ts +29 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/cli",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.4",
|
|
4
4
|
"description": "Agent-Native Fullstack Framework - 에이전트가 코딩해도 아키텍처가 무너지지 않는 개발 OS",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/main.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@mandujs/core": "^0.18.
|
|
35
|
+
"@mandujs/core": "^0.18.6",
|
|
36
36
|
"@mandujs/ate": "^0.17.0",
|
|
37
37
|
"cfonts": "^3.3.0"
|
|
38
38
|
},
|
package/src/util/port.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createServer } from "net";
|
|
2
2
|
|
|
3
3
|
const DEFAULT_MAX_ATTEMPTS = 10;
|
|
4
|
+
// Windows TIME_WAIT 상태에서 같은 포트를 재시도하는 최대 횟수 (#125)
|
|
5
|
+
const TIME_WAIT_RETRY_ATTEMPTS = 3;
|
|
6
|
+
const TIME_WAIT_RETRY_DELAY_MS = 1000;
|
|
4
7
|
|
|
5
8
|
function isPortUsable(error: unknown): boolean {
|
|
6
9
|
if (!error || typeof error !== "object") return false;
|
|
@@ -33,6 +36,25 @@ async function isPortAvailable(port: number, hostname?: string): Promise<boolean
|
|
|
33
36
|
});
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Windows TIME_WAIT 상태를 고려한 포트 가용성 체크 (#125)
|
|
41
|
+
* EADDRINUSE이지만 TIME_WAIT 중인 포트는 잠시 후 사용 가능해질 수 있음
|
|
42
|
+
* → 같은 포트를 지정 횟수만큼 재시도 후 다음 포트로 이동
|
|
43
|
+
*/
|
|
44
|
+
async function isPortAvailableWithRetry(
|
|
45
|
+
port: number,
|
|
46
|
+
hostname?: string
|
|
47
|
+
): Promise<boolean> {
|
|
48
|
+
for (let i = 0; i < TIME_WAIT_RETRY_ATTEMPTS; i++) {
|
|
49
|
+
const available = await isPortAvailable(port, hostname);
|
|
50
|
+
if (available) return true;
|
|
51
|
+
if (i < TIME_WAIT_RETRY_ATTEMPTS - 1) {
|
|
52
|
+
await new Promise((r) => setTimeout(r, TIME_WAIT_RETRY_DELAY_MS));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
36
58
|
export async function resolveAvailablePort(
|
|
37
59
|
startPort: number,
|
|
38
60
|
options: {
|
|
@@ -43,6 +65,7 @@ export async function resolveAvailablePort(
|
|
|
43
65
|
): Promise<{ port: number; attempts: number }> {
|
|
44
66
|
const offsets = options.offsets && options.offsets.length > 0 ? options.offsets : [0];
|
|
45
67
|
const maxAttempts = options.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
68
|
+
const isWindows = process.platform === "win32";
|
|
46
69
|
|
|
47
70
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
48
71
|
const candidate = startPort + attempt;
|
|
@@ -58,9 +81,12 @@ export async function resolveAvailablePort(
|
|
|
58
81
|
continue;
|
|
59
82
|
}
|
|
60
83
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
84
|
+
// Windows에서는 최초 포트(attempt=0)에 한해 TIME_WAIT 재시도 적용
|
|
85
|
+
const checkFn = (isWindows && attempt === 0)
|
|
86
|
+
? (port: number) => isPortAvailableWithRetry(port, options.hostname)
|
|
87
|
+
: (port: number) => isPortAvailable(port, options.hostname);
|
|
88
|
+
|
|
89
|
+
const results = await Promise.all(targets.map(checkFn));
|
|
64
90
|
|
|
65
91
|
if (results.every(Boolean)) {
|
|
66
92
|
return { port: candidate, attempts: attempt };
|