@elisoncampos/local-router 0.2.0 → 0.2.1
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/bin/.installed-node-path +1 -0
- package/bin/local-router +104 -0
- package/dist/cli.js +111 -28
- package/package.json +6 -3
- package/scripts/write-node-path.js +10 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/opt/hostedtoolcache/node/24.14.0/x64/bin/node
|
package/bin/local-router
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
set -eu
|
|
4
|
+
|
|
5
|
+
SOURCE="$0"
|
|
6
|
+
|
|
7
|
+
while [ -h "$SOURCE" ]; do
|
|
8
|
+
DIR=$(CDPATH= cd -- "$(dirname -- "$SOURCE")" && pwd)
|
|
9
|
+
TARGET=$(readlink "$SOURCE")
|
|
10
|
+
case "$TARGET" in
|
|
11
|
+
/*) SOURCE="$TARGET" ;;
|
|
12
|
+
*) SOURCE="$DIR/$TARGET" ;;
|
|
13
|
+
esac
|
|
14
|
+
done
|
|
15
|
+
|
|
16
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$SOURCE")" && pwd)
|
|
17
|
+
PACKAGE_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
|
18
|
+
INSTALLED_NODE_PATH_FILE="$PACKAGE_ROOT/bin/.installed-node-path"
|
|
19
|
+
|
|
20
|
+
PRIMARY_NODE="$(command -v node 2>/dev/null || true)"
|
|
21
|
+
|
|
22
|
+
can_run_node() {
|
|
23
|
+
CANDIDATE="$1"
|
|
24
|
+
[ -n "$CANDIDATE" ] || return 1
|
|
25
|
+
[ -x "$CANDIDATE" ] || return 1
|
|
26
|
+
"$CANDIDATE" -e "process.exit(0)" >/dev/null 2>&1
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
find_fallback_node() {
|
|
30
|
+
OLD_IFS=$IFS
|
|
31
|
+
IFS=:
|
|
32
|
+
for DIR in $PATH; do
|
|
33
|
+
[ -n "$DIR" ] || continue
|
|
34
|
+
CANDIDATE="$DIR/node"
|
|
35
|
+
[ -x "$CANDIDATE" ] || continue
|
|
36
|
+
|
|
37
|
+
if [ -n "$PRIMARY_NODE" ] && [ "$CANDIDATE" = "$PRIMARY_NODE" ]; then
|
|
38
|
+
continue
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
case "$CANDIDATE" in
|
|
42
|
+
*/.asdf/shims/node|*/asdf/shims/node|*/.mise/shims/node|*/mise/shims/node)
|
|
43
|
+
continue
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
if can_run_node "$CANDIDATE"; then
|
|
48
|
+
printf '%s\n' "$CANDIDATE"
|
|
49
|
+
IFS=$OLD_IFS
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
52
|
+
done
|
|
53
|
+
IFS=$OLD_IFS
|
|
54
|
+
return 1
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
SELECTED_NODE=""
|
|
58
|
+
USED_FALLBACK="0"
|
|
59
|
+
|
|
60
|
+
if [ -n "$PRIMARY_NODE" ] && can_run_node "$PRIMARY_NODE"; then
|
|
61
|
+
SELECTED_NODE="$PRIMARY_NODE"
|
|
62
|
+
else
|
|
63
|
+
if [ -f "$INSTALLED_NODE_PATH_FILE" ]; then
|
|
64
|
+
INSTALLED_NODE="$(tr -d '\r\n' < "$INSTALLED_NODE_PATH_FILE")"
|
|
65
|
+
if can_run_node "$INSTALLED_NODE"; then
|
|
66
|
+
SELECTED_NODE="$INSTALLED_NODE"
|
|
67
|
+
fi
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if [ -z "$SELECTED_NODE" ]; then
|
|
71
|
+
SELECTED_NODE="$(find_fallback_node || true)"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
if [ -n "$SELECTED_NODE" ]; then
|
|
75
|
+
USED_FALLBACK="1"
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if [ -z "$SELECTED_NODE" ]; then
|
|
80
|
+
echo "local-router: could not find a working Node.js runtime." >&2
|
|
81
|
+
echo "local-router: install the project's Node version or make a global node available in PATH." >&2
|
|
82
|
+
exit 1
|
|
83
|
+
fi
|
|
84
|
+
|
|
85
|
+
CLI_JS="$PACKAGE_ROOT/dist/cli.js"
|
|
86
|
+
TSX_CLI="$PACKAGE_ROOT/node_modules/tsx/dist/cli.mjs"
|
|
87
|
+
CLI_TS="$PACKAGE_ROOT/src/cli.ts"
|
|
88
|
+
|
|
89
|
+
if [ -f "$CLI_JS" ]; then
|
|
90
|
+
exec env \
|
|
91
|
+
LOCAL_ROUTER_NODE_BIN="$SELECTED_NODE" \
|
|
92
|
+
LOCAL_ROUTER_NODE_FALLBACK="$USED_FALLBACK" \
|
|
93
|
+
"$SELECTED_NODE" "$CLI_JS" "$@"
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
if [ -f "$TSX_CLI" ] && [ -f "$CLI_TS" ]; then
|
|
97
|
+
exec env \
|
|
98
|
+
LOCAL_ROUTER_NODE_BIN="$SELECTED_NODE" \
|
|
99
|
+
LOCAL_ROUTER_NODE_FALLBACK="$USED_FALLBACK" \
|
|
100
|
+
"$SELECTED_NODE" "$TSX_CLI" "$CLI_TS" "$@"
|
|
101
|
+
fi
|
|
102
|
+
|
|
103
|
+
echo "local-router: CLI entrypoint not found. Build the project first." >&2
|
|
104
|
+
exit 1
|
package/dist/cli.js
CHANGED
|
@@ -45,8 +45,11 @@ function formatUrl(hostname, https2, port) {
|
|
|
45
45
|
function removeProtocol(hostname) {
|
|
46
46
|
return hostname.replace(/^https?:\/\//i, "").split("/")[0].trim();
|
|
47
47
|
}
|
|
48
|
+
function trimTrailingDots(hostname) {
|
|
49
|
+
return hostname.replace(/\.+$/, "");
|
|
50
|
+
}
|
|
48
51
|
function normalizeExplicitHostname(hostname) {
|
|
49
|
-
const normalized = removeProtocol(hostname).toLowerCase();
|
|
52
|
+
const normalized = trimTrailingDots(removeProtocol(hostname).toLowerCase());
|
|
50
53
|
if (!normalized) {
|
|
51
54
|
throw new Error("Hostname cannot be empty.");
|
|
52
55
|
}
|
|
@@ -68,6 +71,18 @@ function normalizeExplicitHostname(hostname) {
|
|
|
68
71
|
}
|
|
69
72
|
return normalized;
|
|
70
73
|
}
|
|
74
|
+
function normalizeRequestHostname(hostname) {
|
|
75
|
+
const authority = removeProtocol(hostname);
|
|
76
|
+
if (!authority) return "";
|
|
77
|
+
if (authority.startsWith("[")) {
|
|
78
|
+
const closingBracketIndex = authority.indexOf("]");
|
|
79
|
+
const bracketedHost = closingBracketIndex === -1 ? authority.slice(1) : authority.slice(1, closingBracketIndex);
|
|
80
|
+
return trimTrailingDots(bracketedHost.toLowerCase());
|
|
81
|
+
}
|
|
82
|
+
const lastColonIndex = authority.lastIndexOf(":");
|
|
83
|
+
const withoutPort = lastColonIndex === -1 || authority.includes(":", lastColonIndex + 1) ? authority : authority.slice(0, lastColonIndex);
|
|
84
|
+
return trimTrailingDots(withoutPort.toLowerCase());
|
|
85
|
+
}
|
|
71
86
|
|
|
72
87
|
// src/certs.ts
|
|
73
88
|
var CA_VALIDITY_DAYS = 3650;
|
|
@@ -299,22 +314,23 @@ function createSNICallback(stateDir, defaultCert, defaultKey) {
|
|
|
299
314
|
const defaultContext = tls.createSecureContext({ cert: defaultCert, key: defaultKey });
|
|
300
315
|
return async (servername, callback) => {
|
|
301
316
|
try {
|
|
302
|
-
|
|
317
|
+
const normalizedServername = normalizeRequestHostname(servername);
|
|
318
|
+
if (!normalizedServername || normalizedServername === "localhost") {
|
|
303
319
|
callback(null, defaultContext);
|
|
304
320
|
return;
|
|
305
321
|
}
|
|
306
|
-
const cached = cache.get(
|
|
322
|
+
const cached = cache.get(normalizedServername);
|
|
307
323
|
if (cached) {
|
|
308
324
|
callback(null, cached);
|
|
309
325
|
return;
|
|
310
326
|
}
|
|
311
|
-
const safeName = sanitizeHostForFilename(
|
|
327
|
+
const safeName = sanitizeHostForFilename(normalizedServername);
|
|
312
328
|
const certPath = path.join(stateDir, HOST_CERTS_DIR, `${safeName}.pem`);
|
|
313
329
|
const keyPath = path.join(stateDir, HOST_CERTS_DIR, `${safeName}-key.pem`);
|
|
314
330
|
let resolvedCertPath = certPath;
|
|
315
331
|
let resolvedKeyPath = keyPath;
|
|
316
332
|
if (!fileExists(certPath) || !fileExists(keyPath) || !isCertValid(certPath) || !isCertSignatureStrong(certPath)) {
|
|
317
|
-
const generated = await generateHostCertAsync(stateDir,
|
|
333
|
+
const generated = await generateHostCertAsync(stateDir, normalizedServername);
|
|
318
334
|
resolvedCertPath = generated.certPath;
|
|
319
335
|
resolvedKeyPath = generated.keyPath;
|
|
320
336
|
}
|
|
@@ -322,7 +338,7 @@ function createSNICallback(stateDir, defaultCert, defaultKey) {
|
|
|
322
338
|
cert: fs2.readFileSync(resolvedCertPath),
|
|
323
339
|
key: fs2.readFileSync(resolvedKeyPath)
|
|
324
340
|
});
|
|
325
|
-
cache.set(
|
|
341
|
+
cache.set(normalizedServername, context);
|
|
326
342
|
callback(null, context);
|
|
327
343
|
} catch (error) {
|
|
328
344
|
callback(error instanceof Error ? error : new Error(String(error)), defaultContext);
|
|
@@ -505,12 +521,19 @@ function inferProjectName(cwd = process.cwd()) {
|
|
|
505
521
|
|
|
506
522
|
// src/config.ts
|
|
507
523
|
var CONFIG_FILENAMES = [".local-router", ".local-router.json", "local-router.config.json"];
|
|
524
|
+
function isReadableFile(candidate) {
|
|
525
|
+
try {
|
|
526
|
+
return fs4.statSync(candidate).isFile();
|
|
527
|
+
} catch {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
508
531
|
function findLocalRouterConfig(cwd = process.cwd()) {
|
|
509
532
|
let dir = cwd;
|
|
510
533
|
for (; ; ) {
|
|
511
534
|
for (const filename of CONFIG_FILENAMES) {
|
|
512
535
|
const candidate = path3.join(dir, filename);
|
|
513
|
-
if (
|
|
536
|
+
if (isReadableFile(candidate)) {
|
|
514
537
|
return candidate;
|
|
515
538
|
}
|
|
516
539
|
}
|
|
@@ -667,7 +690,7 @@ var MIN_APP_PORT = 4e3;
|
|
|
667
690
|
var MAX_APP_PORT = 4999;
|
|
668
691
|
var RANDOM_PORT_ATTEMPTS = 50;
|
|
669
692
|
var SOCKET_TIMEOUT_MS = 750;
|
|
670
|
-
var WAIT_FOR_PROXY_MAX_ATTEMPTS =
|
|
693
|
+
var WAIT_FOR_PROXY_MAX_ATTEMPTS = 60;
|
|
671
694
|
var WAIT_FOR_PROXY_INTERVAL_MS = 250;
|
|
672
695
|
var HEALTH_HEADER = "x-local-router";
|
|
673
696
|
var SIGNAL_CODES = {
|
|
@@ -972,19 +995,34 @@ function getHealthHeader() {
|
|
|
972
995
|
}
|
|
973
996
|
|
|
974
997
|
// src/proxy.ts
|
|
975
|
-
function
|
|
998
|
+
function getRequestAuthority(req) {
|
|
976
999
|
const authority = req.headers[":authority"];
|
|
977
1000
|
if (typeof authority === "string" && authority) return authority;
|
|
1001
|
+
if (Array.isArray(req.headers.host)) {
|
|
1002
|
+
return req.headers.host[0] ?? "";
|
|
1003
|
+
}
|
|
978
1004
|
return req.headers.host ?? "";
|
|
979
1005
|
}
|
|
980
|
-
function
|
|
1006
|
+
function getAuthorityPort(authority) {
|
|
1007
|
+
if (!authority) return void 0;
|
|
1008
|
+
if (authority.startsWith("[")) {
|
|
1009
|
+
const closingBracketIndex = authority.indexOf("]");
|
|
1010
|
+
if (closingBracketIndex !== -1 && authority[closingBracketIndex + 1] === ":") {
|
|
1011
|
+
return authority.slice(closingBracketIndex + 2);
|
|
1012
|
+
}
|
|
1013
|
+
return void 0;
|
|
1014
|
+
}
|
|
1015
|
+
const lastColonIndex = authority.lastIndexOf(":");
|
|
1016
|
+
if (lastColonIndex === -1) return void 0;
|
|
1017
|
+
return authority.slice(lastColonIndex + 1);
|
|
1018
|
+
}
|
|
1019
|
+
function buildForwardedHeaders(req, authority, tls2) {
|
|
981
1020
|
const remoteAddress = req.socket.remoteAddress || "127.0.0.1";
|
|
982
|
-
const hostHeader = getRequestHost(req);
|
|
983
1021
|
return {
|
|
984
1022
|
"x-forwarded-for": req.headers["x-forwarded-for"] ? `${req.headers["x-forwarded-for"]}, ${remoteAddress}` : remoteAddress,
|
|
985
1023
|
"x-forwarded-proto": req.headers["x-forwarded-proto"] || (tls2 ? "https" : "http"),
|
|
986
|
-
"x-forwarded-host": req.headers["x-forwarded-host"] ||
|
|
987
|
-
"x-forwarded-port": req.headers["x-forwarded-port"] ||
|
|
1024
|
+
"x-forwarded-host": req.headers["x-forwarded-host"] || authority,
|
|
1025
|
+
"x-forwarded-port": req.headers["x-forwarded-port"] || getAuthorityPort(authority) || (tls2 ? "443" : "80")
|
|
988
1026
|
};
|
|
989
1027
|
}
|
|
990
1028
|
function findRoute(routes, host) {
|
|
@@ -1021,7 +1059,8 @@ function createProxyServers(options) {
|
|
|
1021
1059
|
res.end();
|
|
1022
1060
|
return;
|
|
1023
1061
|
}
|
|
1024
|
-
const
|
|
1062
|
+
const authority = getRequestAuthority(req);
|
|
1063
|
+
const host = normalizeRequestHostname(authority);
|
|
1025
1064
|
if (!host) {
|
|
1026
1065
|
res.writeHead(400, { "content-type": "text/plain" });
|
|
1027
1066
|
res.end("Missing Host header.");
|
|
@@ -1040,8 +1079,8 @@ function createProxyServers(options) {
|
|
|
1040
1079
|
);
|
|
1041
1080
|
return;
|
|
1042
1081
|
}
|
|
1043
|
-
const headers = { ...req.headers, ...buildForwardedHeaders(req, httpsRequest) };
|
|
1044
|
-
headers.host =
|
|
1082
|
+
const headers = { ...req.headers, ...buildForwardedHeaders(req, authority, httpsRequest) };
|
|
1083
|
+
headers.host = authority;
|
|
1045
1084
|
for (const key of Object.keys(headers)) {
|
|
1046
1085
|
if (key.startsWith(":")) {
|
|
1047
1086
|
delete headers[key];
|
|
@@ -1090,14 +1129,15 @@ function createProxyServers(options) {
|
|
|
1090
1129
|
};
|
|
1091
1130
|
const handleUpgrade = (httpsRequest) => (req, socket, head) => {
|
|
1092
1131
|
socket.on("error", () => socket.destroy());
|
|
1093
|
-
const
|
|
1132
|
+
const authority = getRequestAuthority(req);
|
|
1133
|
+
const host = normalizeRequestHostname(authority);
|
|
1094
1134
|
const route = findRoute(options.getRoutes(), host);
|
|
1095
1135
|
if (!route) {
|
|
1096
1136
|
socket.destroy();
|
|
1097
1137
|
return;
|
|
1098
1138
|
}
|
|
1099
|
-
const headers = { ...req.headers, ...buildForwardedHeaders(req, httpsRequest) };
|
|
1100
|
-
headers.host =
|
|
1139
|
+
const headers = { ...req.headers, ...buildForwardedHeaders(req, authority, httpsRequest) };
|
|
1140
|
+
headers.host = authority;
|
|
1101
1141
|
for (const key of Object.keys(headers)) {
|
|
1102
1142
|
if (key.startsWith(":")) {
|
|
1103
1143
|
delete headers[key];
|
|
@@ -1195,6 +1235,16 @@ var RouteConflictError = class extends Error {
|
|
|
1195
1235
|
function isValidRoute(value) {
|
|
1196
1236
|
return typeof value === "object" && value !== null && typeof value.hostname === "string" && typeof value.port === "number" && typeof value.pid === "number";
|
|
1197
1237
|
}
|
|
1238
|
+
function normalizeRoute(route) {
|
|
1239
|
+
try {
|
|
1240
|
+
return {
|
|
1241
|
+
...route,
|
|
1242
|
+
hostname: normalizeExplicitHostname(route.hostname)
|
|
1243
|
+
};
|
|
1244
|
+
} catch {
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1198
1248
|
var RouteStore = class _RouteStore {
|
|
1199
1249
|
dir;
|
|
1200
1250
|
routesPath;
|
|
@@ -1279,7 +1329,10 @@ var RouteStore = class _RouteStore {
|
|
|
1279
1329
|
this.onWarning?.(`Corrupted routes file: expected an array at ${this.routesPath}.`);
|
|
1280
1330
|
return [];
|
|
1281
1331
|
}
|
|
1282
|
-
const routes = parsed.filter(isValidRoute)
|
|
1332
|
+
const routes = parsed.filter(isValidRoute).flatMap((route) => {
|
|
1333
|
+
const normalized = normalizeRoute(route);
|
|
1334
|
+
return normalized ? [normalized] : [];
|
|
1335
|
+
});
|
|
1283
1336
|
const aliveRoutes = routes.filter((route) => this.isProcessAlive(route.pid));
|
|
1284
1337
|
if (persistCleanup && aliveRoutes.length !== routes.length) {
|
|
1285
1338
|
this.saveRoutes(aliveRoutes);
|
|
@@ -1295,30 +1348,32 @@ var RouteStore = class _RouteStore {
|
|
|
1295
1348
|
fixOwnership(this.routesPath);
|
|
1296
1349
|
}
|
|
1297
1350
|
addRoute(hostname, port, pid, force = false) {
|
|
1351
|
+
const normalizedHostname = normalizeExplicitHostname(hostname);
|
|
1298
1352
|
this.ensureDir();
|
|
1299
1353
|
if (!this.acquireLock()) {
|
|
1300
1354
|
throw new Error("Failed to acquire route lock.");
|
|
1301
1355
|
}
|
|
1302
1356
|
try {
|
|
1303
1357
|
const routes = this.loadRoutes(true);
|
|
1304
|
-
const existing = routes.find((route) => route.hostname ===
|
|
1358
|
+
const existing = routes.find((route) => route.hostname === normalizedHostname);
|
|
1305
1359
|
if (existing && existing.pid !== pid && this.isProcessAlive(existing.pid) && !force) {
|
|
1306
|
-
throw new RouteConflictError(
|
|
1360
|
+
throw new RouteConflictError(normalizedHostname, existing.pid);
|
|
1307
1361
|
}
|
|
1308
|
-
const nextRoutes = routes.filter((route) => route.hostname !==
|
|
1309
|
-
nextRoutes.push({ hostname, port, pid });
|
|
1362
|
+
const nextRoutes = routes.filter((route) => route.hostname !== normalizedHostname);
|
|
1363
|
+
nextRoutes.push({ hostname: normalizedHostname, port, pid });
|
|
1310
1364
|
this.saveRoutes(nextRoutes);
|
|
1311
1365
|
} finally {
|
|
1312
1366
|
this.releaseLock();
|
|
1313
1367
|
}
|
|
1314
1368
|
}
|
|
1315
1369
|
removeRoute(hostname) {
|
|
1370
|
+
const normalizedHostname = normalizeExplicitHostname(hostname);
|
|
1316
1371
|
this.ensureDir();
|
|
1317
1372
|
if (!this.acquireLock()) {
|
|
1318
1373
|
throw new Error("Failed to acquire route lock.");
|
|
1319
1374
|
}
|
|
1320
1375
|
try {
|
|
1321
|
-
const nextRoutes = this.loadRoutes(true).filter((route) => route.hostname !==
|
|
1376
|
+
const nextRoutes = this.loadRoutes(true).filter((route) => route.hostname !== normalizedHostname);
|
|
1322
1377
|
this.saveRoutes(nextRoutes);
|
|
1323
1378
|
} finally {
|
|
1324
1379
|
this.releaseLock();
|
|
@@ -1334,7 +1389,31 @@ var POLL_INTERVAL_MS = 3e3;
|
|
|
1334
1389
|
var EXIT_TIMEOUT_MS = 2e3;
|
|
1335
1390
|
var START_TIMEOUT_MS = 3e4;
|
|
1336
1391
|
var AUTO_STOP_IDLE_MS = 3e3;
|
|
1392
|
+
var AUTO_STOP_STARTUP_GRACE_MS = 15e3;
|
|
1337
1393
|
var CLI_ENTRY_PATH = fileURLToPath(import.meta.url);
|
|
1394
|
+
var PACKAGE_VERSION = getPackageVersion();
|
|
1395
|
+
function getPackageVersion() {
|
|
1396
|
+
try {
|
|
1397
|
+
const packageJsonPath = path7.join(path7.dirname(path7.dirname(CLI_ENTRY_PATH)), "package.json");
|
|
1398
|
+
const packageJson = JSON.parse(fs8.readFileSync(packageJsonPath, "utf-8"));
|
|
1399
|
+
return packageJson.version ?? "0.0.0";
|
|
1400
|
+
} catch {
|
|
1401
|
+
return "0.0.0";
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
function maybeWarnAboutNodeFallback() {
|
|
1405
|
+
if (process.env.LOCAL_ROUTER_NODE_FALLBACK !== "1") return;
|
|
1406
|
+
const fallbackNode = process.env.LOCAL_ROUTER_NODE_BIN;
|
|
1407
|
+
if (!fallbackNode) return;
|
|
1408
|
+
console.warn(
|
|
1409
|
+
chalk.yellow(
|
|
1410
|
+
`Warning: the project-local Node.js runtime was not available. Falling back to ${fallbackNode}.`
|
|
1411
|
+
)
|
|
1412
|
+
);
|
|
1413
|
+
console.warn(
|
|
1414
|
+
chalk.gray("Install the project's Node version in asdf, mise, nvm, or your preferred manager.\n")
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1338
1417
|
function getSelfInvocation(args) {
|
|
1339
1418
|
if (CLI_ENTRY_PATH.endsWith(".ts")) {
|
|
1340
1419
|
const projectRoot = path7.dirname(path7.dirname(CLI_ENTRY_PATH));
|
|
@@ -1639,6 +1718,7 @@ function startProxyServer(store, runtime, stateDir) {
|
|
|
1639
1718
|
let poller = null;
|
|
1640
1719
|
let idleTimer = null;
|
|
1641
1720
|
let cleaningUp = false;
|
|
1721
|
+
let hasSeenRoutes = cachedRoutes.length > 0;
|
|
1642
1722
|
const clearIdleTimer = () => {
|
|
1643
1723
|
if (!idleTimer) return;
|
|
1644
1724
|
clearTimeout(idleTimer);
|
|
@@ -1646,13 +1726,14 @@ function startProxyServer(store, runtime, stateDir) {
|
|
|
1646
1726
|
};
|
|
1647
1727
|
const scheduleIdleShutdown = () => {
|
|
1648
1728
|
if (!runtime.autoStopWhenIdle || idleTimer || cachedRoutes.length > 0) return;
|
|
1729
|
+
const delay = hasSeenRoutes ? AUTO_STOP_IDLE_MS : AUTO_STOP_STARTUP_GRACE_MS;
|
|
1649
1730
|
idleTimer = setTimeout(() => {
|
|
1650
1731
|
idleTimer = null;
|
|
1651
1732
|
cachedRoutes = store.loadRoutes(true);
|
|
1652
1733
|
if (cachedRoutes.length === 0) {
|
|
1653
1734
|
cleanup();
|
|
1654
1735
|
}
|
|
1655
|
-
},
|
|
1736
|
+
}, delay);
|
|
1656
1737
|
idleTimer.unref();
|
|
1657
1738
|
};
|
|
1658
1739
|
const reloadRoutes = () => {
|
|
@@ -1660,6 +1741,7 @@ function startProxyServer(store, runtime, stateDir) {
|
|
|
1660
1741
|
cachedRoutes = store.loadRoutes(true);
|
|
1661
1742
|
syncHostsFromStore(store);
|
|
1662
1743
|
if (cachedRoutes.length > 0) {
|
|
1744
|
+
hasSeenRoutes = true;
|
|
1663
1745
|
clearIdleTimer();
|
|
1664
1746
|
} else {
|
|
1665
1747
|
scheduleIdleShutdown();
|
|
@@ -1677,7 +1759,6 @@ function startProxyServer(store, runtime, stateDir) {
|
|
|
1677
1759
|
poller = setInterval(reloadRoutes, POLL_INTERVAL_MS);
|
|
1678
1760
|
poller.unref();
|
|
1679
1761
|
syncHostsFromStore(store);
|
|
1680
|
-
scheduleIdleShutdown();
|
|
1681
1762
|
let tlsOptions;
|
|
1682
1763
|
if (runtime.httpsEnabled) {
|
|
1683
1764
|
const certs = ensureCerts(stateDir);
|
|
@@ -1726,6 +1807,7 @@ function startProxyServer(store, runtime, stateDir) {
|
|
|
1726
1807
|
if (runtime.httpsEnabled) {
|
|
1727
1808
|
console.log(chalk.gray(`HTTPS -> ${runtime.httpsPort}`));
|
|
1728
1809
|
}
|
|
1810
|
+
scheduleIdleShutdown();
|
|
1729
1811
|
};
|
|
1730
1812
|
const registerServerError = (label, port) => (error) => {
|
|
1731
1813
|
if (error.code === "EADDRINUSE") {
|
|
@@ -2023,6 +2105,7 @@ async function handleTrust() {
|
|
|
2023
2105
|
console.log(chalk.green("Local CA added to the trust store."));
|
|
2024
2106
|
}
|
|
2025
2107
|
async function main() {
|
|
2108
|
+
maybeWarnAboutNodeFallback();
|
|
2026
2109
|
const args = process.argv.slice(2);
|
|
2027
2110
|
const command = args[0];
|
|
2028
2111
|
if (!command || command === "--help" || command === "-h") {
|
|
@@ -2030,7 +2113,7 @@ async function main() {
|
|
|
2030
2113
|
return;
|
|
2031
2114
|
}
|
|
2032
2115
|
if (command === "--version" || command === "-v") {
|
|
2033
|
-
console.log(
|
|
2116
|
+
console.log(PACKAGE_VERSION);
|
|
2034
2117
|
return;
|
|
2035
2118
|
}
|
|
2036
2119
|
if (command === "run") {
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elisoncampos/local-router",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Named local domains with automatic HTTP/HTTPS routing for development.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"local-router": "
|
|
8
|
+
"local-router": "bin/local-router"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
|
-
"
|
|
22
|
+
"bin",
|
|
23
|
+
"dist",
|
|
24
|
+
"scripts"
|
|
23
25
|
],
|
|
24
26
|
"engines": {
|
|
25
27
|
"node": ">=20.0.0"
|
|
@@ -28,6 +30,7 @@
|
|
|
28
30
|
"build": "tsup src/cli.ts --format esm --target node20 --out-dir dist --clean",
|
|
29
31
|
"changeset": "changeset",
|
|
30
32
|
"dev": "tsx src/cli.ts",
|
|
33
|
+
"postinstall": "node scripts/write-node-path.js",
|
|
31
34
|
"prepack": "npm run build",
|
|
32
35
|
"prepublishOnly": "npm run typecheck && npm test",
|
|
33
36
|
"release": "npm run typecheck && npm test && npm run build && changeset publish",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const projectRoot = path.dirname(scriptDir);
|
|
7
|
+
const outputPath = path.join(projectRoot, "bin", ".installed-node-path");
|
|
8
|
+
|
|
9
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
10
|
+
fs.writeFileSync(outputPath, `${process.execPath}\n`, "utf8");
|