jskelet 0.6.3 → 0.6.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/AGENTS.md +136 -136
- package/CHANGELOG.md +628 -620
- package/LICENSE +21 -21
- package/README.md +2 -0
- package/bin/jskelet.mjs +130 -130
- package/docs/01-baslangic.md +291 -291
- package/docs/02-mimari.md +310 -310
- package/docs/03-routing.md +515 -515
- package/docs/04-render-ve-sablonlar.md +667 -661
- package/docs/05-islands.md +486 -486
- package/docs/06-cache.md +1467 -1443
- package/docs/07-yapilandirma.md +1208 -1197
- package/docs/08-build.md +429 -429
- package/docs/09-dev-araclari.md +364 -364
- package/docs/10-dagitim.md +348 -338
- package/docs/12-panel-ve-oturum.md +479 -478
- package/docs/README.md +83 -83
- package/docs/en/01-getting-started.md +298 -298
- package/docs/en/02-architecture.md +329 -329
- package/docs/en/03-routing.md +531 -531
- package/docs/en/04-rendering.md +675 -669
- package/docs/en/05-islands.md +497 -497
- package/docs/en/06-caching.md +1476 -1453
- package/docs/en/07-configuration.md +1229 -1219
- package/docs/en/08-build.md +447 -447
- package/docs/en/09-dev-tools.md +373 -373
- package/docs/en/10-deployment.md +351 -340
- package/docs/en/11-migration.md +398 -398
- package/docs/en/12-dashboards-and-sessions.md +489 -488
- package/docs/en/README.md +87 -87
- package/package.json +137 -137
- package/src/build/ensure-build.mjs +19 -19
- package/src/build/paths.mjs +153 -153
- package/src/build/resolve-peer.mjs +36 -36
- package/src/build/tasks/client.mjs +349 -349
- package/src/build/tasks/css.mjs +235 -235
- package/src/build/tasks/fonts.mjs +146 -146
- package/src/build/tasks/icons.mjs +357 -357
- package/src/build/tasks/images.mjs +244 -244
- package/src/build/tasks/precompress.mjs +78 -78
- package/src/build/tasks/templates.mjs +20 -20
- package/src/client/admin/i18n.js +764 -764
- package/src/client/admin/login.html +74 -74
- package/src/client/admin/panel.css +809 -809
- package/src/client/admin/panel.html +495 -495
- package/src/client/admin/panel.js +1251 -1251
- package/src/client/devtools/report.html +185 -185
- package/src/client/devtools/report.js +745 -745
- package/src/client/devtools/seo.js +628 -628
- package/src/client/dom.js +95 -95
- package/src/client/form.js +192 -192
- package/src/client/index.js +45 -45
- package/src/client/registry.js +305 -305
- package/src/client/safe-image.js +91 -91
- package/src/client/shared-cookie.js +225 -225
- package/src/client/store.js +36 -36
- package/src/client/swap.js +188 -188
- package/src/compile/codegen.js +336 -336
- package/src/compile/compile-all.js +149 -149
- package/src/compile/errors.js +66 -66
- package/src/compile/expr.js +409 -409
- package/src/compile/index.js +17 -17
- package/src/compile/parse.js +541 -541
- package/src/compile/resolve.js +211 -211
- package/src/compile/scan-exports.js +51 -51
- package/src/config/defaults.js +541 -534
- package/src/config/index.js +1500 -1469
- package/src/config/pattern.js +107 -107
- package/src/generate.mjs +163 -163
- package/src/http/control-flow.js +71 -71
- package/src/http/cookies-entry.js +21 -21
- package/src/http/cookies.js +277 -277
- package/src/http/request-cache.js +46 -46
- package/src/http/request-context.js +165 -165
- package/src/http/shared-cookie.js +178 -178
- package/src/index.js +101 -101
- package/src/init.mjs +232 -230
- package/src/migrate/apply.mjs +262 -262
- package/src/migrate/babel.mjs +79 -79
- package/src/migrate/classify.mjs +155 -155
- package/src/migrate/config.mjs +126 -126
- package/src/migrate/fs-walk.mjs +191 -191
- package/src/migrate/parse.mjs +26 -26
- package/src/migrate/scan.mjs +177 -177
- package/src/migrate/transform/expr-source.mjs +168 -168
- package/src/migrate/transform/island.mjs +67 -67
- package/src/migrate/transform/jsx-to-component.mjs +302 -302
- package/src/migrate/transform/jsx-to-jsk.mjs +330 -330
- package/src/migrate/transform/page-split.mjs +435 -435
- package/src/migrate/write.mjs +81 -81
- package/src/migrate.mjs +171 -171
- package/src/runtime/alias-hooks.mjs +119 -119
- package/src/runtime/register.mjs +4 -4
- package/src/server/admin/actions.js +229 -229
- package/src/server/admin/auth.js +125 -125
- package/src/server/admin/event-log.js +151 -151
- package/src/server/admin/gate.js +209 -209
- package/src/server/admin/inventory.js +188 -188
- package/src/server/admin/mount.js +56 -56
- package/src/server/admin/router.js +216 -216
- package/src/server/admin/snapshot.js +241 -241
- package/src/server/assets.js +147 -147
- package/src/server/auth/handoff.js +309 -309
- package/src/server/cache-blob.js +70 -70
- package/src/server/cache-control.js +45 -0
- package/src/server/cache-deps.js +42 -42
- package/src/server/cache-vary.js +113 -113
- package/src/server/cloudflare.js +607 -607
- package/src/server/create-app.js +366 -366
- package/src/server/data-cache.js +553 -553
- package/src/server/dev/report.js +485 -485
- package/src/server/dev/socket.js +170 -170
- package/src/server/dev/version-check.mjs +139 -139
- package/src/server/disk-cache.js +233 -233
- package/src/server/ejs-adapter.js +59 -59
- package/src/server/html-cache.js +1196 -1196
- package/src/server/image-optimizer.js +500 -500
- package/src/server/logs/access-middleware.js +66 -66
- package/src/server/logs/file-sink.js +193 -193
- package/src/server/logs/pipeline.js +165 -165
- package/src/server/logs/s3-put.js +214 -214
- package/src/server/logs/s3-sink.js +112 -112
- package/src/server/metadata.js +102 -102
- package/src/server/middleware/compression.js +205 -205
- package/src/server/middleware/csrf.js +134 -134
- package/src/server/middleware/dev-gate.js +75 -75
- package/src/server/middleware/headers.js +37 -37
- package/src/server/middleware/redirects.js +32 -32
- package/src/server/middleware/robots-txt.js +341 -341
- package/src/server/middleware/static-precompressed.js +121 -121
- package/src/server/middleware/trailing-slash.js +53 -53
- package/src/server/middleware/upstream-proxy.js +141 -141
- package/src/server/og-image.js +369 -356
- package/src/server/port-guard.js +255 -255
- package/src/server/prewarm.js +1082 -1082
- package/src/server/redis.js +588 -588
- package/src/server/render.js +910 -910
- package/src/server/router.js +157 -157
- package/src/server/status-page.js +265 -265
- package/src/server/upstream-limiter.js +376 -376
- package/src/server/upstream-tracking.js +166 -166
- package/src/shared/cookie-domain.js +66 -66
- package/src/start.mjs +22 -22
- package/src/templates/layout.ejs +30 -30
- package/src/templates/layout.jsk +30 -30
- package/src/version.mjs +31 -31
- package/src/views/components/loader.js +101 -101
- package/src/views/helpers/html.js +102 -102
- package/src/views/helpers/tags.js +375 -375
- package/types/config/defaults.d.ts +6 -0
- package/types/config/index.d.ts +6 -0
- package/types/server/cache-control.d.ts +28 -0
- package/types/server/og-image.d.ts +5 -0
package/src/server/port-guard.js
CHANGED
|
@@ -1,255 +1,255 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dinlenecek port doluysa başlamadan önce net bir hata verilir; `--murder`
|
|
3
|
-
* verilmişse o porttaki dinleyici süreçler öldürülüp bağlama denenir.
|
|
4
|
-
*
|
|
5
|
-
* Windows: `netstat` + `taskkill`. Unix: `lsof` (yoksa `ss`) + `process.kill`.
|
|
6
|
-
* Bu süreçlerin PID'lerini okumak için yerleşik bir Node API'si yok.
|
|
7
|
-
*/
|
|
8
|
-
import { execFile } from "node:child_process";
|
|
9
|
-
import { promisify } from "node:util";
|
|
10
|
-
import process from "node:process";
|
|
11
|
-
|
|
12
|
-
const execFileAsync = promisify(execFile);
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param {number} ms
|
|
16
|
-
* @returns {Promise<void>}
|
|
17
|
-
*/
|
|
18
|
-
function sleep(ms) {
|
|
19
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* `netstat -ano -p tcp` çıktısından LISTENING PID'lerini çıkarır.
|
|
24
|
-
* `3000` ile `30001` karışmasın diye porttan sonra rakam olmamalı.
|
|
25
|
-
*
|
|
26
|
-
* @param {string} stdout
|
|
27
|
-
* @param {number} port
|
|
28
|
-
* @returns {number[]}
|
|
29
|
-
*/
|
|
30
|
-
export function parseWindowsNetstat(stdout, port) {
|
|
31
|
-
const re = new RegExp(
|
|
32
|
-
String.raw`TCP\s+\S*:${port}(?!\d)\s+\S+\s+LISTENING\s+(\d+)`,
|
|
33
|
-
"gi",
|
|
34
|
-
);
|
|
35
|
-
/** @type {number[]} */
|
|
36
|
-
const pids = [];
|
|
37
|
-
for (const match of stdout.matchAll(re)) {
|
|
38
|
-
pids.push(Number(match[1]));
|
|
39
|
-
}
|
|
40
|
-
return pids;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* `lsof -t` çıktısı: satır başına bir PID.
|
|
45
|
-
*
|
|
46
|
-
* @param {string} stdout
|
|
47
|
-
* @returns {number[]}
|
|
48
|
-
*/
|
|
49
|
-
export function parseLsofPids(stdout) {
|
|
50
|
-
return stdout
|
|
51
|
-
.split(/\r?\n/)
|
|
52
|
-
.map((line) => Number(line.trim()))
|
|
53
|
-
.filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* `ss -H -lptn` satırlarından `pid=` değerlerini alır.
|
|
58
|
-
*
|
|
59
|
-
* @param {string} stdout
|
|
60
|
-
* @returns {number[]}
|
|
61
|
-
*/
|
|
62
|
-
export function parseSsPids(stdout) {
|
|
63
|
-
/** @type {number[]} */
|
|
64
|
-
const pids = [];
|
|
65
|
-
for (const match of stdout.matchAll(/pid=(\d+)/g)) {
|
|
66
|
-
pids.push(Number(match[1]));
|
|
67
|
-
}
|
|
68
|
-
return pids;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Bu süreç ve ebeveyni öldürülmesin: `--murder` yenilemede kendi CLI'sını
|
|
73
|
-
* ya da henüz dinlemeyen kendisini vurmasın.
|
|
74
|
-
*
|
|
75
|
-
* @param {number} pid
|
|
76
|
-
* @returns {boolean}
|
|
77
|
-
*/
|
|
78
|
-
function isSafeToKill(pid) {
|
|
79
|
-
return pid > 0 && pid !== process.pid && pid !== process.ppid;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* @param {number} port
|
|
84
|
-
* @returns {Promise<number[]>}
|
|
85
|
-
*/
|
|
86
|
-
async function listWindows(port) {
|
|
87
|
-
const { stdout } = await execFileAsync("netstat", ["-ano", "-p", "tcp"], {
|
|
88
|
-
windowsHide: true,
|
|
89
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
90
|
-
});
|
|
91
|
-
return parseWindowsNetstat(stdout, port);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* @param {number} port
|
|
96
|
-
* @returns {Promise<number[]>}
|
|
97
|
-
*/
|
|
98
|
-
async function listUnix(port) {
|
|
99
|
-
try {
|
|
100
|
-
const { stdout } = await execFileAsync(
|
|
101
|
-
"lsof",
|
|
102
|
-
["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"],
|
|
103
|
-
{ maxBuffer: 1024 * 1024 },
|
|
104
|
-
);
|
|
105
|
-
return parseLsofPids(stdout);
|
|
106
|
-
} catch (error) {
|
|
107
|
-
// lsof yoksa (exit 1 = dinleyen yok; ENOENT = komut yok) ss'e düş.
|
|
108
|
-
if (/** @type {NodeJS.ErrnoException} */ (error).code === "ENOENT") {
|
|
109
|
-
return listUnixSs(port);
|
|
110
|
-
}
|
|
111
|
-
const status = /** @type {{ status?: number | null }} */ (error).status;
|
|
112
|
-
if (status === 1) return [];
|
|
113
|
-
throw error;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* @param {number} port
|
|
119
|
-
* @returns {Promise<number[]>}
|
|
120
|
-
*/
|
|
121
|
-
async function listUnixSs(port) {
|
|
122
|
-
try {
|
|
123
|
-
const { stdout } = await execFileAsync(
|
|
124
|
-
"ss",
|
|
125
|
-
["-H", "-lptn", `sport = :${port}`],
|
|
126
|
-
{ maxBuffer: 1024 * 1024 },
|
|
127
|
-
);
|
|
128
|
-
return parseSsPids(stdout);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
if (/** @type {NodeJS.ErrnoException} */ (error).code === "ENOENT") {
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
const status = /** @type {{ status?: number | null }} */ (error).status;
|
|
134
|
-
if (status === 1) return [];
|
|
135
|
-
throw error;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Portu dinleyen yabancı süreçlerin PID listesi.
|
|
141
|
-
*
|
|
142
|
-
* @param {number} port
|
|
143
|
-
* @returns {Promise<number[]>}
|
|
144
|
-
*/
|
|
145
|
-
export async function listListeningPids(port) {
|
|
146
|
-
const raw =
|
|
147
|
-
process.platform === "win32"
|
|
148
|
-
? await listWindows(port)
|
|
149
|
-
: await listUnix(port);
|
|
150
|
-
return [...new Set(raw)].filter(isSafeToKill);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* @param {number} pid
|
|
155
|
-
* @returns {Promise<void>}
|
|
156
|
-
*/
|
|
157
|
-
async function killPid(pid) {
|
|
158
|
-
if (process.platform === "win32") {
|
|
159
|
-
try {
|
|
160
|
-
await execFileAsync("taskkill", ["/PID", String(pid), "/F"], {
|
|
161
|
-
windowsHide: true,
|
|
162
|
-
});
|
|
163
|
-
} catch (error) {
|
|
164
|
-
// Süreç zaten gitmiş olabilir.
|
|
165
|
-
const status = /** @type {{ status?: number | null }} */ (error).status;
|
|
166
|
-
if (status === 128 || status === 1) return;
|
|
167
|
-
throw error;
|
|
168
|
-
}
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
try {
|
|
173
|
-
process.kill(pid, "SIGTERM");
|
|
174
|
-
} catch (error) {
|
|
175
|
-
if (/** @type {NodeJS.ErrnoException} */ (error).code === "ESRCH") return;
|
|
176
|
-
throw error;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
await sleep(200);
|
|
180
|
-
try {
|
|
181
|
-
process.kill(pid, 0);
|
|
182
|
-
process.kill(pid, "SIGKILL");
|
|
183
|
-
} catch {
|
|
184
|
-
// ESRCH: SIGTERM yetmiş.
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Porttaki dinleyicileri öldürür ve soketin boşalmasını kısa süre bekler.
|
|
190
|
-
*
|
|
191
|
-
* @param {number} port
|
|
192
|
-
* @returns {Promise<number[]>} öldürülen PID'ler
|
|
193
|
-
*/
|
|
194
|
-
export async function murderPort(port) {
|
|
195
|
-
const pids = await listListeningPids(port);
|
|
196
|
-
for (const pid of pids) {
|
|
197
|
-
await killPid(pid);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const deadline = Date.now() + 3000;
|
|
201
|
-
while (Date.now() < deadline) {
|
|
202
|
-
const still = await listListeningPids(port);
|
|
203
|
-
if (still.length === 0) return pids;
|
|
204
|
-
await sleep(50);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return pids;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Port doluysa ya net hata fırlatır ya da `--murder` ile temizler.
|
|
212
|
-
*
|
|
213
|
-
* Dinleyici listesi alınamazsa (araç yok / yetki) sessizce geçer; asıl bağlama
|
|
214
|
-
* `EADDRINUSE` ile yine düşer.
|
|
215
|
-
*
|
|
216
|
-
* @param {number} port
|
|
217
|
-
* @param {{ murder?: boolean }} [options]
|
|
218
|
-
* @returns {Promise<void>}
|
|
219
|
-
*/
|
|
220
|
-
export async function ensurePortFree(port, options = {}) {
|
|
221
|
-
const murder = options.murder === true;
|
|
222
|
-
|
|
223
|
-
/** @type {number[]} */
|
|
224
|
-
let pids;
|
|
225
|
-
try {
|
|
226
|
-
pids = await listListeningPids(port);
|
|
227
|
-
} catch {
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (pids.length === 0) return;
|
|
232
|
-
|
|
233
|
-
if (!murder) {
|
|
234
|
-
const error = new Error(
|
|
235
|
-
`port ${port} is already in use (pid ${pids.join(", ")}). ` +
|
|
236
|
-
`Pass --murder to kill it and start, or set PORT to another value.`,
|
|
237
|
-
);
|
|
238
|
-
/** @type {NodeJS.ErrnoException} */ (error).code = "EADDRINUSE";
|
|
239
|
-
throw error;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
console.error(
|
|
243
|
-
`[jskelet] port ${port} in use by pid ${pids.join(", ")} — killing (--murder)`,
|
|
244
|
-
);
|
|
245
|
-
await murderPort(port);
|
|
246
|
-
|
|
247
|
-
const still = await listListeningPids(port);
|
|
248
|
-
if (still.length > 0) {
|
|
249
|
-
const error = new Error(
|
|
250
|
-
`port ${port} still in use after --murder (pid ${still.join(", ")})`,
|
|
251
|
-
);
|
|
252
|
-
/** @type {NodeJS.ErrnoException} */ (error).code = "EADDRINUSE";
|
|
253
|
-
throw error;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Dinlenecek port doluysa başlamadan önce net bir hata verilir; `--murder`
|
|
3
|
+
* verilmişse o porttaki dinleyici süreçler öldürülüp bağlama denenir.
|
|
4
|
+
*
|
|
5
|
+
* Windows: `netstat` + `taskkill`. Unix: `lsof` (yoksa `ss`) + `process.kill`.
|
|
6
|
+
* Bu süreçlerin PID'lerini okumak için yerleşik bir Node API'si yok.
|
|
7
|
+
*/
|
|
8
|
+
import { execFile } from "node:child_process";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
import process from "node:process";
|
|
11
|
+
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {number} ms
|
|
16
|
+
* @returns {Promise<void>}
|
|
17
|
+
*/
|
|
18
|
+
function sleep(ms) {
|
|
19
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* `netstat -ano -p tcp` çıktısından LISTENING PID'lerini çıkarır.
|
|
24
|
+
* `3000` ile `30001` karışmasın diye porttan sonra rakam olmamalı.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} stdout
|
|
27
|
+
* @param {number} port
|
|
28
|
+
* @returns {number[]}
|
|
29
|
+
*/
|
|
30
|
+
export function parseWindowsNetstat(stdout, port) {
|
|
31
|
+
const re = new RegExp(
|
|
32
|
+
String.raw`TCP\s+\S*:${port}(?!\d)\s+\S+\s+LISTENING\s+(\d+)`,
|
|
33
|
+
"gi",
|
|
34
|
+
);
|
|
35
|
+
/** @type {number[]} */
|
|
36
|
+
const pids = [];
|
|
37
|
+
for (const match of stdout.matchAll(re)) {
|
|
38
|
+
pids.push(Number(match[1]));
|
|
39
|
+
}
|
|
40
|
+
return pids;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* `lsof -t` çıktısı: satır başına bir PID.
|
|
45
|
+
*
|
|
46
|
+
* @param {string} stdout
|
|
47
|
+
* @returns {number[]}
|
|
48
|
+
*/
|
|
49
|
+
export function parseLsofPids(stdout) {
|
|
50
|
+
return stdout
|
|
51
|
+
.split(/\r?\n/)
|
|
52
|
+
.map((line) => Number(line.trim()))
|
|
53
|
+
.filter((pid) => Number.isInteger(pid) && pid > 0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* `ss -H -lptn` satırlarından `pid=` değerlerini alır.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} stdout
|
|
60
|
+
* @returns {number[]}
|
|
61
|
+
*/
|
|
62
|
+
export function parseSsPids(stdout) {
|
|
63
|
+
/** @type {number[]} */
|
|
64
|
+
const pids = [];
|
|
65
|
+
for (const match of stdout.matchAll(/pid=(\d+)/g)) {
|
|
66
|
+
pids.push(Number(match[1]));
|
|
67
|
+
}
|
|
68
|
+
return pids;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Bu süreç ve ebeveyni öldürülmesin: `--murder` yenilemede kendi CLI'sını
|
|
73
|
+
* ya da henüz dinlemeyen kendisini vurmasın.
|
|
74
|
+
*
|
|
75
|
+
* @param {number} pid
|
|
76
|
+
* @returns {boolean}
|
|
77
|
+
*/
|
|
78
|
+
function isSafeToKill(pid) {
|
|
79
|
+
return pid > 0 && pid !== process.pid && pid !== process.ppid;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @param {number} port
|
|
84
|
+
* @returns {Promise<number[]>}
|
|
85
|
+
*/
|
|
86
|
+
async function listWindows(port) {
|
|
87
|
+
const { stdout } = await execFileAsync("netstat", ["-ano", "-p", "tcp"], {
|
|
88
|
+
windowsHide: true,
|
|
89
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
90
|
+
});
|
|
91
|
+
return parseWindowsNetstat(stdout, port);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* @param {number} port
|
|
96
|
+
* @returns {Promise<number[]>}
|
|
97
|
+
*/
|
|
98
|
+
async function listUnix(port) {
|
|
99
|
+
try {
|
|
100
|
+
const { stdout } = await execFileAsync(
|
|
101
|
+
"lsof",
|
|
102
|
+
["-nP", `-iTCP:${port}`, "-sTCP:LISTEN", "-t"],
|
|
103
|
+
{ maxBuffer: 1024 * 1024 },
|
|
104
|
+
);
|
|
105
|
+
return parseLsofPids(stdout);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// lsof yoksa (exit 1 = dinleyen yok; ENOENT = komut yok) ss'e düş.
|
|
108
|
+
if (/** @type {NodeJS.ErrnoException} */ (error).code === "ENOENT") {
|
|
109
|
+
return listUnixSs(port);
|
|
110
|
+
}
|
|
111
|
+
const status = /** @type {{ status?: number | null }} */ (error).status;
|
|
112
|
+
if (status === 1) return [];
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @param {number} port
|
|
119
|
+
* @returns {Promise<number[]>}
|
|
120
|
+
*/
|
|
121
|
+
async function listUnixSs(port) {
|
|
122
|
+
try {
|
|
123
|
+
const { stdout } = await execFileAsync(
|
|
124
|
+
"ss",
|
|
125
|
+
["-H", "-lptn", `sport = :${port}`],
|
|
126
|
+
{ maxBuffer: 1024 * 1024 },
|
|
127
|
+
);
|
|
128
|
+
return parseSsPids(stdout);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
if (/** @type {NodeJS.ErrnoException} */ (error).code === "ENOENT") {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
const status = /** @type {{ status?: number | null }} */ (error).status;
|
|
134
|
+
if (status === 1) return [];
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Portu dinleyen yabancı süreçlerin PID listesi.
|
|
141
|
+
*
|
|
142
|
+
* @param {number} port
|
|
143
|
+
* @returns {Promise<number[]>}
|
|
144
|
+
*/
|
|
145
|
+
export async function listListeningPids(port) {
|
|
146
|
+
const raw =
|
|
147
|
+
process.platform === "win32"
|
|
148
|
+
? await listWindows(port)
|
|
149
|
+
: await listUnix(port);
|
|
150
|
+
return [...new Set(raw)].filter(isSafeToKill);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* @param {number} pid
|
|
155
|
+
* @returns {Promise<void>}
|
|
156
|
+
*/
|
|
157
|
+
async function killPid(pid) {
|
|
158
|
+
if (process.platform === "win32") {
|
|
159
|
+
try {
|
|
160
|
+
await execFileAsync("taskkill", ["/PID", String(pid), "/F"], {
|
|
161
|
+
windowsHide: true,
|
|
162
|
+
});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
// Süreç zaten gitmiş olabilir.
|
|
165
|
+
const status = /** @type {{ status?: number | null }} */ (error).status;
|
|
166
|
+
if (status === 128 || status === 1) return;
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
process.kill(pid, "SIGTERM");
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (/** @type {NodeJS.ErrnoException} */ (error).code === "ESRCH") return;
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await sleep(200);
|
|
180
|
+
try {
|
|
181
|
+
process.kill(pid, 0);
|
|
182
|
+
process.kill(pid, "SIGKILL");
|
|
183
|
+
} catch {
|
|
184
|
+
// ESRCH: SIGTERM yetmiş.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Porttaki dinleyicileri öldürür ve soketin boşalmasını kısa süre bekler.
|
|
190
|
+
*
|
|
191
|
+
* @param {number} port
|
|
192
|
+
* @returns {Promise<number[]>} öldürülen PID'ler
|
|
193
|
+
*/
|
|
194
|
+
export async function murderPort(port) {
|
|
195
|
+
const pids = await listListeningPids(port);
|
|
196
|
+
for (const pid of pids) {
|
|
197
|
+
await killPid(pid);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const deadline = Date.now() + 3000;
|
|
201
|
+
while (Date.now() < deadline) {
|
|
202
|
+
const still = await listListeningPids(port);
|
|
203
|
+
if (still.length === 0) return pids;
|
|
204
|
+
await sleep(50);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return pids;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Port doluysa ya net hata fırlatır ya da `--murder` ile temizler.
|
|
212
|
+
*
|
|
213
|
+
* Dinleyici listesi alınamazsa (araç yok / yetki) sessizce geçer; asıl bağlama
|
|
214
|
+
* `EADDRINUSE` ile yine düşer.
|
|
215
|
+
*
|
|
216
|
+
* @param {number} port
|
|
217
|
+
* @param {{ murder?: boolean }} [options]
|
|
218
|
+
* @returns {Promise<void>}
|
|
219
|
+
*/
|
|
220
|
+
export async function ensurePortFree(port, options = {}) {
|
|
221
|
+
const murder = options.murder === true;
|
|
222
|
+
|
|
223
|
+
/** @type {number[]} */
|
|
224
|
+
let pids;
|
|
225
|
+
try {
|
|
226
|
+
pids = await listListeningPids(port);
|
|
227
|
+
} catch {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (pids.length === 0) return;
|
|
232
|
+
|
|
233
|
+
if (!murder) {
|
|
234
|
+
const error = new Error(
|
|
235
|
+
`port ${port} is already in use (pid ${pids.join(", ")}). ` +
|
|
236
|
+
`Pass --murder to kill it and start, or set PORT to another value.`,
|
|
237
|
+
);
|
|
238
|
+
/** @type {NodeJS.ErrnoException} */ (error).code = "EADDRINUSE";
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
console.error(
|
|
243
|
+
`[jskelet] port ${port} in use by pid ${pids.join(", ")} — killing (--murder)`,
|
|
244
|
+
);
|
|
245
|
+
await murderPort(port);
|
|
246
|
+
|
|
247
|
+
const still = await listListeningPids(port);
|
|
248
|
+
if (still.length > 0) {
|
|
249
|
+
const error = new Error(
|
|
250
|
+
`port ${port} still in use after --murder (pid ${still.join(", ")})`,
|
|
251
|
+
);
|
|
252
|
+
/** @type {NodeJS.ErrnoException} */ (error).code = "EADDRINUSE";
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|