portless 0.14.0 → 0.15.0
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/README.md +6 -1
- package/dist/{chunk-OZM4AEYL.js → chunk-PCBKLZK2.js} +31 -3
- package/dist/cli.js +406 -13
- package/dist/index.d.ts +10 -2
- package/dist/index.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -348,6 +348,7 @@ portless alias <name> <port> # Register a static route (e.g. for Docker)
|
|
|
348
348
|
portless alias <name> <port> --force # Overwrite an existing route
|
|
349
349
|
portless alias --remove <name> # Remove a static route
|
|
350
350
|
portless list # Show active routes
|
|
351
|
+
portless doctor # Check proxy, routes, DNS, and CA trust
|
|
351
352
|
portless trust # Add local CA to system trust store
|
|
352
353
|
portless clean # Remove state, CA trust entry, and hosts block
|
|
353
354
|
portless prune # Kill orphaned dev servers from crashed sessions
|
|
@@ -423,7 +424,7 @@ PORTLESS_NGROK_URL ngrok URL of the app (when --ngrok is active)
|
|
|
423
424
|
NODE_EXTRA_CA_CERTS Path to the portless CA (when HTTPS is active)
|
|
424
425
|
```
|
|
425
426
|
|
|
426
|
-
> **Reserved names:** `run`, `get`, `alias`, `hosts`, `list`, `trust`, `clean`, `prune`, `proxy`, and `service` are subcommands and cannot be used as app names directly. Use `portless run <cmd>` to infer the name from your project, or `portless --name <name> <cmd>` to force any name including reserved ones.
|
|
427
|
+
> **Reserved names:** `run`, `get`, `alias`, `hosts`, `list`, `doctor`, `trust`, `clean`, `prune`, `proxy`, and `service` are subcommands and cannot be used as app names directly. Use `portless run <cmd>` to infer the name from your project, or `portless --name <name> <cmd>` to force any name including reserved ones.
|
|
427
428
|
|
|
428
429
|
## Uninstall / reset
|
|
429
430
|
|
|
@@ -448,6 +449,10 @@ portless hosts clean # Clean up later
|
|
|
448
449
|
|
|
449
450
|
Auto-syncs `/etc/hosts` for route hostnames by default (`.localhost`, custom TLDs, LAN `.local`). Set `PORTLESS_SYNC_HOSTS=0` to disable.
|
|
450
451
|
|
|
452
|
+
## Troubleshooting
|
|
453
|
+
|
|
454
|
+
Run `portless doctor` to inspect local health without changing state. It checks Node.js, the state directory, proxy liveness, route entries, HTTPS CA trust, hostname resolution, and LAN mode prerequisites, then prints suggested fixes.
|
|
455
|
+
|
|
451
456
|
## Proxying Between Portless Apps
|
|
452
457
|
|
|
453
458
|
If your frontend dev server (e.g. Vite, webpack) proxies API requests to another portless app, make sure the proxy rewrites the `Host` header. Without this, portless routes the request back to the frontend in an infinite loop.
|
|
@@ -17,6 +17,15 @@ function fixOwnership(...paths) {
|
|
|
17
17
|
function isErrnoException(err) {
|
|
18
18
|
return err instanceof Error && "code" in err && typeof err.code === "string";
|
|
19
19
|
}
|
|
20
|
+
function isProcessAlive(pid) {
|
|
21
|
+
if (pid <= 0) return false;
|
|
22
|
+
try {
|
|
23
|
+
process.kill(pid, 0);
|
|
24
|
+
return true;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
return isErrnoException(err) && err.code === "EPERM";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
20
29
|
function escapeHtml(str) {
|
|
21
30
|
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
22
31
|
}
|
|
@@ -375,6 +384,9 @@ function createProxyServer(options) {
|
|
|
375
384
|
delete proxyReqHeaders[key];
|
|
376
385
|
}
|
|
377
386
|
}
|
|
387
|
+
if (!proxyReqHeaders.host) {
|
|
388
|
+
proxyReqHeaders.host = getRequestHost(req);
|
|
389
|
+
}
|
|
378
390
|
const proxyReq = http.request(
|
|
379
391
|
{
|
|
380
392
|
hostname: "127.0.0.1",
|
|
@@ -460,6 +472,9 @@ function createProxyServer(options) {
|
|
|
460
472
|
delete proxyReqHeaders[key];
|
|
461
473
|
}
|
|
462
474
|
}
|
|
475
|
+
if (!proxyReqHeaders.host) {
|
|
476
|
+
proxyReqHeaders.host = getRequestHost(req);
|
|
477
|
+
}
|
|
463
478
|
const proxyReq = http.request({
|
|
464
479
|
hostname: "127.0.0.1",
|
|
465
480
|
port: route.port,
|
|
@@ -869,13 +884,17 @@ var RouteStore = class _RouteStore {
|
|
|
869
884
|
try {
|
|
870
885
|
parsed = JSON.parse(raw);
|
|
871
886
|
} catch {
|
|
887
|
+
this.onWarning?.(`Corrupted routes file (invalid JSON): ${this.routesPath}`);
|
|
872
888
|
return [];
|
|
873
889
|
}
|
|
874
890
|
if (!Array.isArray(parsed)) {
|
|
891
|
+
this.onWarning?.(`Corrupted routes file (expected array): ${this.routesPath}`);
|
|
875
892
|
return [];
|
|
876
893
|
}
|
|
877
894
|
return parsed.filter(isValidRoute);
|
|
878
|
-
} catch {
|
|
895
|
+
} catch (err) {
|
|
896
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
897
|
+
this.onWarning?.(`Could not read routes file: ${message}`);
|
|
879
898
|
return [];
|
|
880
899
|
}
|
|
881
900
|
}
|
|
@@ -947,13 +966,21 @@ var RouteStore = class _RouteStore {
|
|
|
947
966
|
this.releaseLock();
|
|
948
967
|
}
|
|
949
968
|
}
|
|
950
|
-
|
|
969
|
+
/**
|
|
970
|
+
* Remove a route by hostname. When `ownerPid` is provided, the entry is
|
|
971
|
+
* only removed while it is still owned by that pid. Exit cleanups must
|
|
972
|
+
* pass their own pid: after a `--force` takeover the killed process would
|
|
973
|
+
* otherwise deregister the route the new owner just registered.
|
|
974
|
+
*/
|
|
975
|
+
removeRoute(hostname, ownerPid) {
|
|
951
976
|
this.ensureDir();
|
|
952
977
|
if (!this.acquireLock()) {
|
|
953
978
|
throw new Error("Failed to acquire route lock");
|
|
954
979
|
}
|
|
955
980
|
try {
|
|
956
|
-
const routes = this.loadRoutes(true).filter(
|
|
981
|
+
const routes = this.loadRoutes(true).filter(
|
|
982
|
+
(r) => r.hostname !== hostname || ownerPid !== void 0 && r.pid !== ownerPid
|
|
983
|
+
);
|
|
957
984
|
this.saveRoutes(routes);
|
|
958
985
|
} finally {
|
|
959
986
|
this.releaseLock();
|
|
@@ -964,6 +991,7 @@ var RouteStore = class _RouteStore {
|
|
|
964
991
|
export {
|
|
965
992
|
fixOwnership,
|
|
966
993
|
isErrnoException,
|
|
994
|
+
isProcessAlive,
|
|
967
995
|
escapeHtml,
|
|
968
996
|
formatUrl,
|
|
969
997
|
parseHostname,
|
package/dist/cli.js
CHANGED
|
@@ -4,16 +4,19 @@ import {
|
|
|
4
4
|
PORTLESS_HEADER,
|
|
5
5
|
RouteConflictError,
|
|
6
6
|
RouteStore,
|
|
7
|
+
checkHostResolution,
|
|
7
8
|
cleanHostsFile,
|
|
8
9
|
createHttpRedirectServer,
|
|
9
10
|
createProxyServer,
|
|
10
11
|
fixOwnership,
|
|
11
12
|
formatUrl,
|
|
13
|
+
getManagedHostnames,
|
|
12
14
|
isErrnoException,
|
|
15
|
+
isProcessAlive,
|
|
13
16
|
parseHostname,
|
|
14
17
|
shouldAutoSyncHosts,
|
|
15
18
|
syncHostsFile
|
|
16
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-PCBKLZK2.js";
|
|
17
20
|
|
|
18
21
|
// src/colors.ts
|
|
19
22
|
function supportsColor() {
|
|
@@ -1503,6 +1506,7 @@ function readPortFromDir(dir) {
|
|
|
1503
1506
|
}
|
|
1504
1507
|
}
|
|
1505
1508
|
var TLS_MARKER_FILE = "proxy.tls";
|
|
1509
|
+
var CUSTOM_CERT_MARKER_FILE = "proxy.custom-cert";
|
|
1506
1510
|
function readTlsMarker(dir) {
|
|
1507
1511
|
try {
|
|
1508
1512
|
return fs3.existsSync(path3.join(dir, TLS_MARKER_FILE));
|
|
@@ -1521,6 +1525,24 @@ function writeTlsMarker(dir, enabled2) {
|
|
|
1521
1525
|
}
|
|
1522
1526
|
}
|
|
1523
1527
|
}
|
|
1528
|
+
function readCustomCertMarker(dir) {
|
|
1529
|
+
try {
|
|
1530
|
+
return fs3.existsSync(path3.join(dir, CUSTOM_CERT_MARKER_FILE));
|
|
1531
|
+
} catch {
|
|
1532
|
+
return false;
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
function writeCustomCertMarker(dir, enabled2) {
|
|
1536
|
+
const markerPath = path3.join(dir, CUSTOM_CERT_MARKER_FILE);
|
|
1537
|
+
if (enabled2) {
|
|
1538
|
+
fs3.writeFileSync(markerPath, "1", { mode: 420 });
|
|
1539
|
+
} else {
|
|
1540
|
+
try {
|
|
1541
|
+
fs3.unlinkSync(markerPath);
|
|
1542
|
+
} catch {
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
}
|
|
1524
1546
|
var LAN_MARKER_FILE = "proxy.lan";
|
|
1525
1547
|
function readLanMarker(dir) {
|
|
1526
1548
|
try {
|
|
@@ -2001,6 +2023,7 @@ var PORTLESS_STATE_FILES = [
|
|
|
2001
2023
|
"proxy.port",
|
|
2002
2024
|
"proxy.log",
|
|
2003
2025
|
"proxy.tls",
|
|
2026
|
+
"proxy.custom-cert",
|
|
2004
2027
|
"proxy.tld",
|
|
2005
2028
|
"proxy.lan",
|
|
2006
2029
|
"ca-key.pem",
|
|
@@ -3880,7 +3903,7 @@ function formatProcessExitSuffix(code, signal) {
|
|
|
3880
3903
|
if (code !== null) return ` (exit ${code})`;
|
|
3881
3904
|
return "";
|
|
3882
3905
|
}
|
|
3883
|
-
function startProxyServer(store, proxyPort, tld, tlsOptions, lanIp, strict) {
|
|
3906
|
+
function startProxyServer(store, proxyPort, tld, tlsOptions, lanIp, strict, customCert = false) {
|
|
3884
3907
|
store.ensureDir();
|
|
3885
3908
|
const isTls = !!tlsOptions;
|
|
3886
3909
|
const mdnsSupport = isMdnsSupported();
|
|
@@ -4009,6 +4032,7 @@ function startProxyServer(store, proxyPort, tld, tlsOptions, lanIp, strict) {
|
|
|
4009
4032
|
fs9.writeFileSync(store.pidPath, process.pid.toString(), { mode: FILE_MODE });
|
|
4010
4033
|
fs9.writeFileSync(store.portFilePath, proxyPort.toString(), { mode: FILE_MODE });
|
|
4011
4034
|
writeTlsMarker(store.dir, isTls);
|
|
4035
|
+
writeCustomCertMarker(store.dir, isTls && customCert);
|
|
4012
4036
|
writeTldFile(store.dir, tld);
|
|
4013
4037
|
writeLanMarker(store.dir, activeLanIp);
|
|
4014
4038
|
fixOwnership(store.dir, store.pidPath, store.portFilePath);
|
|
@@ -4021,7 +4045,7 @@ function startProxyServer(store, proxyPort, tld, tlsOptions, lanIp, strict) {
|
|
|
4021
4045
|
if (activeLanIp) {
|
|
4022
4046
|
console.log(chalk.green(`LAN mode: ${activeLanIp}`));
|
|
4023
4047
|
console.log(chalk.gray("Services are discoverable as <name>.local on your network"));
|
|
4024
|
-
if (isTls) {
|
|
4048
|
+
if (isTls && !customCert) {
|
|
4025
4049
|
console.log(chalk.yellow("For HTTPS on devices, install the CA certificate:"));
|
|
4026
4050
|
console.log(chalk.gray(` ${path9.join(store.dir, "ca.pem")}`));
|
|
4027
4051
|
}
|
|
@@ -4063,6 +4087,7 @@ function startProxyServer(store, proxyPort, tld, tlsOptions, lanIp, strict) {
|
|
|
4063
4087
|
} catch {
|
|
4064
4088
|
}
|
|
4065
4089
|
writeTlsMarker(store.dir, false);
|
|
4090
|
+
writeCustomCertMarker(store.dir, false);
|
|
4066
4091
|
writeTldFile(store.dir, DEFAULT_TLD);
|
|
4067
4092
|
writeLanMarker(store.dir, null);
|
|
4068
4093
|
if (autoSyncHosts) cleanHostsFile();
|
|
@@ -4101,6 +4126,7 @@ async function stopProxy(store, proxyPort, _tls) {
|
|
|
4101
4126
|
} catch {
|
|
4102
4127
|
}
|
|
4103
4128
|
writeTlsMarker(store.dir, false);
|
|
4129
|
+
writeCustomCertMarker(store.dir, false);
|
|
4104
4130
|
writeTldFile(store.dir, DEFAULT_TLD);
|
|
4105
4131
|
writeLanMarker(store.dir, null);
|
|
4106
4132
|
console.log(colors_default.green(`Killed process ${pid}. Proxy stopped.`));
|
|
@@ -4140,6 +4166,7 @@ async function stopProxy(store, proxyPort, _tls) {
|
|
|
4140
4166
|
console.error(colors_default.red("Corrupted PID file. Removing it."));
|
|
4141
4167
|
fs9.unlinkSync(pidPath);
|
|
4142
4168
|
writeTlsMarker(store.dir, false);
|
|
4169
|
+
writeCustomCertMarker(store.dir, false);
|
|
4143
4170
|
writeTldFile(store.dir, DEFAULT_TLD);
|
|
4144
4171
|
writeLanMarker(store.dir, null);
|
|
4145
4172
|
return;
|
|
@@ -4158,6 +4185,7 @@ async function stopProxy(store, proxyPort, _tls) {
|
|
|
4158
4185
|
} catch {
|
|
4159
4186
|
}
|
|
4160
4187
|
writeTlsMarker(store.dir, false);
|
|
4188
|
+
writeCustomCertMarker(store.dir, false);
|
|
4161
4189
|
writeTldFile(store.dir, DEFAULT_TLD);
|
|
4162
4190
|
writeLanMarker(store.dir, null);
|
|
4163
4191
|
return;
|
|
@@ -4171,6 +4199,7 @@ async function stopProxy(store, proxyPort, _tls) {
|
|
|
4171
4199
|
console.log(colors_default.yellow("Removing stale PID file."));
|
|
4172
4200
|
fs9.unlinkSync(pidPath);
|
|
4173
4201
|
writeTlsMarker(store.dir, false);
|
|
4202
|
+
writeCustomCertMarker(store.dir, false);
|
|
4174
4203
|
writeTldFile(store.dir, DEFAULT_TLD);
|
|
4175
4204
|
writeLanMarker(store.dir, null);
|
|
4176
4205
|
return;
|
|
@@ -4182,6 +4211,7 @@ async function stopProxy(store, proxyPort, _tls) {
|
|
|
4182
4211
|
} catch {
|
|
4183
4212
|
}
|
|
4184
4213
|
writeTlsMarker(store.dir, false);
|
|
4214
|
+
writeCustomCertMarker(store.dir, false);
|
|
4185
4215
|
writeTldFile(store.dir, DEFAULT_TLD);
|
|
4186
4216
|
writeLanMarker(store.dir, null);
|
|
4187
4217
|
console.log(colors_default.green("Proxy stopped."));
|
|
@@ -4564,7 +4594,7 @@ portless
|
|
|
4564
4594
|
} catch {
|
|
4565
4595
|
}
|
|
4566
4596
|
try {
|
|
4567
|
-
store.removeRoute(hostname);
|
|
4597
|
+
store.removeRoute(hostname, process.pid);
|
|
4568
4598
|
} catch {
|
|
4569
4599
|
}
|
|
4570
4600
|
process.exit(1);
|
|
@@ -4618,7 +4648,7 @@ portless
|
|
|
4618
4648
|
} catch {
|
|
4619
4649
|
}
|
|
4620
4650
|
try {
|
|
4621
|
-
store.removeRoute(hostname);
|
|
4651
|
+
store.removeRoute(hostname, process.pid);
|
|
4622
4652
|
} catch {
|
|
4623
4653
|
}
|
|
4624
4654
|
}
|
|
@@ -4803,13 +4833,14 @@ ${colors_default.bold("Usage:")}
|
|
|
4803
4833
|
${colors_default.cyan("portless run")} Same as above
|
|
4804
4834
|
${colors_default.cyan("portless run <cmd>")} Run a command through the proxy
|
|
4805
4835
|
${colors_default.cyan("portless <name> <cmd>")} Run with an explicit app name
|
|
4806
|
-
${colors_default.cyan("portless proxy start")} Start the proxy (HTTPS on port 443, daemon)
|
|
4836
|
+
${colors_default.cyan("portless proxy start")} Start the proxy (HTTPS on port 443, daemon); rarely needed since it auto-starts on first run
|
|
4807
4837
|
${colors_default.cyan("portless proxy stop")} Stop the proxy
|
|
4808
4838
|
${colors_default.cyan("portless service install")} Start proxy automatically when the OS starts
|
|
4809
4839
|
${colors_default.cyan("portless get <name>")} Print URL for a service (for cross-service refs)
|
|
4810
4840
|
${colors_default.cyan("portless alias <name> <port>")} Register a static route (e.g. for Docker)
|
|
4811
4841
|
${colors_default.cyan("portless alias --remove <name>")} Remove a static route
|
|
4812
4842
|
${colors_default.cyan("portless list")} Show active routes
|
|
4843
|
+
${colors_default.cyan("portless doctor")} Check local portless health
|
|
4813
4844
|
${colors_default.cyan("portless trust")} Add local CA to system trust store
|
|
4814
4845
|
${colors_default.cyan("portless clean")} Remove portless state, trust entry, and hosts block
|
|
4815
4846
|
${colors_default.cyan("portless prune")} Kill orphaned dev servers from crashed sessions
|
|
@@ -4827,6 +4858,7 @@ ${colors_default.bold("Examples:")}
|
|
|
4827
4858
|
portless service install --lan # Persist LAN mode in the startup service
|
|
4828
4859
|
portless service install --wildcard # Persist wildcard routing in the startup service
|
|
4829
4860
|
portless get backend # -> https://backend.localhost
|
|
4861
|
+
portless doctor # Check proxy, routes, DNS, and CA trust
|
|
4830
4862
|
portless myapp --tailscale next dev # -> also https://<node>.ts.net (tailnet)
|
|
4831
4863
|
portless myapp --funnel next dev # -> also https://<node>.ts.net (public)
|
|
4832
4864
|
portless myapp --ngrok next dev # -> also https://<random>.ngrok.app (public)
|
|
@@ -4957,14 +4989,14 @@ ${colors_default.bold("Skip portless:")}
|
|
|
4957
4989
|
PORTLESS=0 pnpm dev # Runs command directly without proxy
|
|
4958
4990
|
|
|
4959
4991
|
${colors_default.bold("Reserved names:")}
|
|
4960
|
-
run, get, alias, hosts, list, trust, clean, prune, proxy, service are subcommands and
|
|
4992
|
+
run, get, alias, hosts, list, doctor, trust, clean, prune, proxy, service are subcommands and
|
|
4961
4993
|
cannot be used as app names directly. Use "portless run" to infer the name,
|
|
4962
4994
|
or "portless --name <name>" to force any name including reserved ones.
|
|
4963
4995
|
`);
|
|
4964
4996
|
process.exit(0);
|
|
4965
4997
|
}
|
|
4966
4998
|
function printVersion() {
|
|
4967
|
-
console.log("0.
|
|
4999
|
+
console.log("0.15.0");
|
|
4968
5000
|
process.exit(0);
|
|
4969
5001
|
}
|
|
4970
5002
|
async function handleTrust() {
|
|
@@ -5401,6 +5433,355 @@ ${colors_default.bold("Usage: portless hosts <command>")}
|
|
|
5401
5433
|
);
|
|
5402
5434
|
process.exit(1);
|
|
5403
5435
|
}
|
|
5436
|
+
function colorDoctorStatus(status) {
|
|
5437
|
+
if (status === "fail") return colors_default.red;
|
|
5438
|
+
if (status === "warn") return colors_default.yellow;
|
|
5439
|
+
if (status === "ok") return colors_default.green;
|
|
5440
|
+
return colors_default.gray;
|
|
5441
|
+
}
|
|
5442
|
+
function printDoctorFinding(finding) {
|
|
5443
|
+
const status = finding.status.padEnd(5);
|
|
5444
|
+
console.log(`${colorDoctorStatus(finding.status)(status)} ${finding.message}`);
|
|
5445
|
+
if (finding.hint) {
|
|
5446
|
+
console.log(colors_default.gray(` ${finding.hint}`));
|
|
5447
|
+
}
|
|
5448
|
+
}
|
|
5449
|
+
function isProcessAliveForDoctor(pid) {
|
|
5450
|
+
if (pid <= 0) return true;
|
|
5451
|
+
return isProcessAlive(pid);
|
|
5452
|
+
}
|
|
5453
|
+
function checkPathWritable(targetPath) {
|
|
5454
|
+
try {
|
|
5455
|
+
fs9.accessSync(targetPath, fs9.constants.W_OK);
|
|
5456
|
+
return true;
|
|
5457
|
+
} catch {
|
|
5458
|
+
return false;
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
5461
|
+
function findExistingAncestor(targetPath) {
|
|
5462
|
+
let current = targetPath;
|
|
5463
|
+
for (; ; ) {
|
|
5464
|
+
if (fs9.existsSync(current)) return current;
|
|
5465
|
+
const parent = path9.dirname(current);
|
|
5466
|
+
if (parent === current) return null;
|
|
5467
|
+
current = parent;
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
function checkCommandAvailable(command, args) {
|
|
5471
|
+
const result = spawnSync5(command, args, {
|
|
5472
|
+
stdio: "ignore",
|
|
5473
|
+
timeout: 3e3,
|
|
5474
|
+
windowsHide: true
|
|
5475
|
+
});
|
|
5476
|
+
return !result.error && (result.status === 0 || result.status === null);
|
|
5477
|
+
}
|
|
5478
|
+
function pluralize(count, singular, plural = `${singular}s`) {
|
|
5479
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
5480
|
+
}
|
|
5481
|
+
function isValidTcpPort(port) {
|
|
5482
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535;
|
|
5483
|
+
}
|
|
5484
|
+
function doctorProxyStartHint(proxyPort, tls2) {
|
|
5485
|
+
const defaultPort = getDefaultPort(tls2);
|
|
5486
|
+
const portArgs = proxyPort === defaultPort ? "" : ` -p ${proxyPort}`;
|
|
5487
|
+
const tlsArgs = tls2 ? "" : " --no-tls";
|
|
5488
|
+
return `Run: portless proxy start${portArgs}${tlsArgs}`;
|
|
5489
|
+
}
|
|
5490
|
+
async function handleDoctor(args) {
|
|
5491
|
+
if (args[1] === "--help" || args[1] === "-h") {
|
|
5492
|
+
console.log(`
|
|
5493
|
+
${colors_default.bold("portless doctor")} - Check local portless health and print suggested fixes.
|
|
5494
|
+
|
|
5495
|
+
${colors_default.bold("Usage:")}
|
|
5496
|
+
${colors_default.cyan("portless doctor")}
|
|
5497
|
+
|
|
5498
|
+
Checks Node.js, the state directory, proxy liveness, route entries, HTTPS CA
|
|
5499
|
+
trust, hostname resolution, and LAN mode prerequisites. It does not start,
|
|
5500
|
+
stop, clean, prune, trust, or modify portless state.
|
|
5501
|
+
|
|
5502
|
+
${colors_default.bold("Options:")}
|
|
5503
|
+
--help, -h Show this help
|
|
5504
|
+
`);
|
|
5505
|
+
process.exit(0);
|
|
5506
|
+
}
|
|
5507
|
+
if (args.length > 1) {
|
|
5508
|
+
console.error(colors_default.red(`Error: Unknown argument "${args[1]}".`));
|
|
5509
|
+
console.error(colors_default.cyan(" portless doctor --help"));
|
|
5510
|
+
process.exit(1);
|
|
5511
|
+
}
|
|
5512
|
+
const findings = [];
|
|
5513
|
+
const add = (status, message, hint) => {
|
|
5514
|
+
findings.push({ status, message, hint });
|
|
5515
|
+
};
|
|
5516
|
+
let state;
|
|
5517
|
+
try {
|
|
5518
|
+
state = await discoverState();
|
|
5519
|
+
} catch (err) {
|
|
5520
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5521
|
+
add("fail", `Could not discover portless state: ${message}`);
|
|
5522
|
+
state = {
|
|
5523
|
+
dir: resolveStateDir(),
|
|
5524
|
+
port: getDefaultPort(!isHttpsEnvDisabled()),
|
|
5525
|
+
tls: !isHttpsEnvDisabled(),
|
|
5526
|
+
tld: DEFAULT_TLD,
|
|
5527
|
+
lanMode: isLanEnvEnabled(),
|
|
5528
|
+
lanIp: null
|
|
5529
|
+
};
|
|
5530
|
+
}
|
|
5531
|
+
const store = new RouteStore(state.dir, {
|
|
5532
|
+
onWarning: (msg) => add("warn", msg)
|
|
5533
|
+
});
|
|
5534
|
+
const hasPortFile = fs9.existsSync(store.portFilePath);
|
|
5535
|
+
const configuredTls = hasPortFile ? state.tls : !isHttpsEnvDisabled();
|
|
5536
|
+
const configuredPort = hasPortFile ? state.port : getDefaultPort(configuredTls);
|
|
5537
|
+
const initialProxyRunning = await isProxyRunning(state.port, state.tls);
|
|
5538
|
+
const probePort = initialProxyRunning || hasPortFile ? state.port : configuredPort;
|
|
5539
|
+
const probeTls = initialProxyRunning || hasPortFile ? state.tls : configuredTls;
|
|
5540
|
+
const proxyRunning = initialProxyRunning && probePort === state.port ? true : await isProxyRunning(probePort, probeTls);
|
|
5541
|
+
const portListening = proxyRunning ? true : await isPortListening(probePort);
|
|
5542
|
+
const proxyPort = proxyRunning || portListening || hasPortFile ? probePort : configuredPort;
|
|
5543
|
+
const proxyTls = proxyRunning || portListening || hasPortFile ? probeTls : configuredTls;
|
|
5544
|
+
const currentProxyStateIsHttp = (proxyRunning || portListening || hasPortFile) && !proxyTls;
|
|
5545
|
+
const proxyUsesCustomCert = proxyTls && readCustomCertMarker(state.dir);
|
|
5546
|
+
const stateExists = fs9.existsSync(state.dir);
|
|
5547
|
+
console.log(colors_default.blue.bold("\nportless doctor\n"));
|
|
5548
|
+
console.log(`Version: ${"0.15.0"}`);
|
|
5549
|
+
console.log(`Node.js: ${process.versions.node}`);
|
|
5550
|
+
console.log(`Platform: ${process.platform} ${process.arch}`);
|
|
5551
|
+
console.log(`State dir: ${state.dir}`);
|
|
5552
|
+
console.log(`Proxy target: ${formatUrl("127.0.0.1", proxyPort, proxyTls)}`);
|
|
5553
|
+
console.log(`Mode: ${proxyTls ? "HTTPS" : "HTTP"}, .${state.tld}${state.lanMode ? ", LAN" : ""}`);
|
|
5554
|
+
console.log("");
|
|
5555
|
+
const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
|
|
5556
|
+
if (nodeMajor >= 24) {
|
|
5557
|
+
add("ok", `Node.js ${process.versions.node} satisfies portless requirements.`);
|
|
5558
|
+
} else {
|
|
5559
|
+
add("fail", `Node.js ${process.versions.node} is unsupported.`, "Install Node.js 24 or newer.");
|
|
5560
|
+
}
|
|
5561
|
+
if (stateExists) {
|
|
5562
|
+
try {
|
|
5563
|
+
const stat = fs9.statSync(state.dir);
|
|
5564
|
+
if (!stat.isDirectory()) {
|
|
5565
|
+
add("fail", `State path exists but is not a directory: ${state.dir}`);
|
|
5566
|
+
} else if (checkPathWritable(state.dir)) {
|
|
5567
|
+
add("ok", `State directory is writable: ${state.dir}`);
|
|
5568
|
+
} else {
|
|
5569
|
+
add("fail", `State directory is not writable: ${state.dir}`);
|
|
5570
|
+
}
|
|
5571
|
+
} catch (err) {
|
|
5572
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5573
|
+
add("fail", `Could not inspect state directory: ${message}`);
|
|
5574
|
+
}
|
|
5575
|
+
} else {
|
|
5576
|
+
const ancestor = findExistingAncestor(path9.dirname(state.dir));
|
|
5577
|
+
if (!ancestor) {
|
|
5578
|
+
add(
|
|
5579
|
+
"fail",
|
|
5580
|
+
`State directory does not exist and no writable ancestor was found: ${state.dir}`
|
|
5581
|
+
);
|
|
5582
|
+
} else {
|
|
5583
|
+
const ancestorStat = fs9.statSync(ancestor);
|
|
5584
|
+
if (!ancestorStat.isDirectory()) {
|
|
5585
|
+
add("fail", `State directory does not exist and ancestor is not a directory: ${ancestor}`);
|
|
5586
|
+
} else if (checkPathWritable(ancestor)) {
|
|
5587
|
+
add("info", `State directory has not been created yet: ${state.dir}`);
|
|
5588
|
+
} else {
|
|
5589
|
+
add("fail", `State directory does not exist and ancestor is not writable: ${ancestor}`);
|
|
5590
|
+
}
|
|
5591
|
+
}
|
|
5592
|
+
}
|
|
5593
|
+
if (proxyRunning) {
|
|
5594
|
+
add("ok", `Proxy is responding on port ${proxyPort}.`);
|
|
5595
|
+
} else if (portListening) {
|
|
5596
|
+
const pid = findPidOnPort(proxyPort);
|
|
5597
|
+
add(
|
|
5598
|
+
"fail",
|
|
5599
|
+
`Port ${proxyPort} is in use, but it is not a portless proxy.`,
|
|
5600
|
+
pid ? `Process on port: PID ${pid}` : "Could not identify the process holding the port."
|
|
5601
|
+
);
|
|
5602
|
+
} else {
|
|
5603
|
+
add(
|
|
5604
|
+
"warn",
|
|
5605
|
+
`Proxy is not running on port ${proxyPort}.`,
|
|
5606
|
+
doctorProxyStartHint(proxyPort, proxyTls)
|
|
5607
|
+
);
|
|
5608
|
+
}
|
|
5609
|
+
if (fs9.existsSync(store.pidPath)) {
|
|
5610
|
+
try {
|
|
5611
|
+
const rawPid = fs9.readFileSync(store.pidPath, "utf-8").trim();
|
|
5612
|
+
const pid = parseInt(rawPid, 10);
|
|
5613
|
+
if (isNaN(pid) || pid <= 0) {
|
|
5614
|
+
add("fail", `Proxy PID file is invalid: ${store.pidPath}`);
|
|
5615
|
+
} else if (!isProcessAliveForDoctor(pid)) {
|
|
5616
|
+
add("warn", `Proxy PID file is stale: ${pid}`, "Run: portless proxy stop");
|
|
5617
|
+
} else if (!proxyRunning) {
|
|
5618
|
+
add(
|
|
5619
|
+
"warn",
|
|
5620
|
+
`Proxy PID file points to PID ${pid}, but no portless proxy is responding on port ${proxyPort}.`,
|
|
5621
|
+
"Run: portless proxy stop"
|
|
5622
|
+
);
|
|
5623
|
+
} else {
|
|
5624
|
+
const portPid = findPidOnPort(proxyPort);
|
|
5625
|
+
if (portPid !== null && portPid !== pid) {
|
|
5626
|
+
add(
|
|
5627
|
+
"warn",
|
|
5628
|
+
`Proxy PID file points to PID ${pid}, but port ${proxyPort} is owned by PID ${portPid}.`,
|
|
5629
|
+
"Run: portless proxy stop"
|
|
5630
|
+
);
|
|
5631
|
+
} else {
|
|
5632
|
+
add("ok", `Proxy PID file points to the responding proxy process: ${pid}`);
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
} catch (err) {
|
|
5636
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5637
|
+
add("fail", `Could not read proxy PID file: ${message}`);
|
|
5638
|
+
}
|
|
5639
|
+
} else if (proxyRunning) {
|
|
5640
|
+
add("warn", `Proxy is running but the PID file is missing: ${store.pidPath}`);
|
|
5641
|
+
}
|
|
5642
|
+
if (proxyUsesCustomCert) {
|
|
5643
|
+
add("ok", "Proxy is configured with a custom TLS certificate.");
|
|
5644
|
+
} else if (proxyTls || !currentProxyStateIsHttp && !isHttpsEnvDisabled()) {
|
|
5645
|
+
if (checkCommandAvailable("openssl", ["version"])) {
|
|
5646
|
+
add("ok", "OpenSSL is available for certificate generation.");
|
|
5647
|
+
} else {
|
|
5648
|
+
add(
|
|
5649
|
+
"fail",
|
|
5650
|
+
"OpenSSL is not available on PATH.",
|
|
5651
|
+
isWindows ? "Install OpenSSL or add Git for Windows OpenSSL to PATH." : "Install OpenSSL with your system package manager."
|
|
5652
|
+
);
|
|
5653
|
+
}
|
|
5654
|
+
} else {
|
|
5655
|
+
add("info", "HTTPS is disabled, so OpenSSL is not required for this run.");
|
|
5656
|
+
}
|
|
5657
|
+
if (proxyTls && proxyUsesCustomCert) {
|
|
5658
|
+
add("info", "Generated local CA is not required for custom TLS certificates.");
|
|
5659
|
+
} else if (proxyTls) {
|
|
5660
|
+
const caPath = path9.join(state.dir, "ca.pem");
|
|
5661
|
+
if (fs9.existsSync(caPath)) {
|
|
5662
|
+
if (isCATrusted(state.dir)) {
|
|
5663
|
+
add("ok", "Local CA is trusted by the OS trust store.");
|
|
5664
|
+
} else {
|
|
5665
|
+
add(
|
|
5666
|
+
"warn",
|
|
5667
|
+
"Local CA exists but is not trusted by the OS trust store.",
|
|
5668
|
+
"Run: portless trust"
|
|
5669
|
+
);
|
|
5670
|
+
}
|
|
5671
|
+
} else if (proxyRunning) {
|
|
5672
|
+
add("warn", `Generated CA file is missing: ${caPath}`);
|
|
5673
|
+
} else {
|
|
5674
|
+
add("info", "Local CA has not been generated yet.");
|
|
5675
|
+
}
|
|
5676
|
+
} else {
|
|
5677
|
+
add("info", "HTTPS is disabled for the current proxy state.");
|
|
5678
|
+
}
|
|
5679
|
+
let rawRoutes = [];
|
|
5680
|
+
try {
|
|
5681
|
+
rawRoutes = store.loadRoutesRaw();
|
|
5682
|
+
} catch (err) {
|
|
5683
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
5684
|
+
add("fail", `Could not read routes: ${message}`);
|
|
5685
|
+
}
|
|
5686
|
+
const liveRoutes = rawRoutes.filter(
|
|
5687
|
+
(route) => route.pid === 0 || isProcessAliveForDoctor(route.pid)
|
|
5688
|
+
);
|
|
5689
|
+
const staleRoutes = rawRoutes.filter(
|
|
5690
|
+
(route) => route.pid !== 0 && !isProcessAliveForDoctor(route.pid)
|
|
5691
|
+
);
|
|
5692
|
+
if (rawRoutes.length === 0) {
|
|
5693
|
+
add("info", "No routes are registered.");
|
|
5694
|
+
} else if (staleRoutes.length === 0) {
|
|
5695
|
+
add("ok", `Routes: ${pluralize(liveRoutes.length, "active route")}.`);
|
|
5696
|
+
} else {
|
|
5697
|
+
add(
|
|
5698
|
+
"warn",
|
|
5699
|
+
`Routes: ${pluralize(liveRoutes.length, "active route")}, ${pluralize(staleRoutes.length, "stale route")}.`,
|
|
5700
|
+
"Run: portless prune"
|
|
5701
|
+
);
|
|
5702
|
+
}
|
|
5703
|
+
for (const route of staleRoutes.slice(0, 5)) {
|
|
5704
|
+
add("warn", `Stale route ${route.hostname} is owned by exited PID ${route.pid}.`);
|
|
5705
|
+
}
|
|
5706
|
+
if (staleRoutes.length > 5) {
|
|
5707
|
+
add("warn", `${staleRoutes.length - 5} additional stale routes hidden.`);
|
|
5708
|
+
}
|
|
5709
|
+
const routePortChecks = await Promise.all(
|
|
5710
|
+
liveRoutes.map(async (route) => {
|
|
5711
|
+
const validPort = isValidTcpPort(route.port);
|
|
5712
|
+
return {
|
|
5713
|
+
route,
|
|
5714
|
+
invalidPort: !validPort,
|
|
5715
|
+
listening: validPort ? await isPortListening(route.port) : false
|
|
5716
|
+
};
|
|
5717
|
+
})
|
|
5718
|
+
);
|
|
5719
|
+
for (const { route, invalidPort, listening } of routePortChecks) {
|
|
5720
|
+
if (invalidPort) {
|
|
5721
|
+
add(
|
|
5722
|
+
"warn",
|
|
5723
|
+
`Route ${route.hostname} has invalid port ${route.port}.`,
|
|
5724
|
+
route.pid === 0 ? "Remove or recreate the alias." : "Run: portless prune"
|
|
5725
|
+
);
|
|
5726
|
+
continue;
|
|
5727
|
+
}
|
|
5728
|
+
if (listening) continue;
|
|
5729
|
+
add(
|
|
5730
|
+
"warn",
|
|
5731
|
+
`Route ${route.hostname} points to port ${route.port}, but nothing is listening there.`,
|
|
5732
|
+
route.pid === 0 ? "Remove the alias or start that service." : "The app may still be starting."
|
|
5733
|
+
);
|
|
5734
|
+
}
|
|
5735
|
+
if (state.lanMode || isLanEnvEnabled()) {
|
|
5736
|
+
const mdns = isMdnsSupported();
|
|
5737
|
+
if (mdns.supported) {
|
|
5738
|
+
add("ok", "mDNS publishing support is available for LAN mode.");
|
|
5739
|
+
} else {
|
|
5740
|
+
add("fail", `LAN mode is enabled but mDNS publishing is unavailable: ${mdns.reason}`);
|
|
5741
|
+
}
|
|
5742
|
+
if (state.lanIp) {
|
|
5743
|
+
add("ok", `LAN IP is recorded: ${state.lanIp}`);
|
|
5744
|
+
} else {
|
|
5745
|
+
add("warn", "LAN mode is enabled but no LAN IP is recorded.");
|
|
5746
|
+
}
|
|
5747
|
+
} else if (liveRoutes.length > 0) {
|
|
5748
|
+
const managedHosts = new Set(getManagedHostnames());
|
|
5749
|
+
const resolutionChecks = await Promise.all(
|
|
5750
|
+
liveRoutes.map(async (route) => ({
|
|
5751
|
+
hostname: route.hostname,
|
|
5752
|
+
resolves: await checkHostResolution(route.hostname),
|
|
5753
|
+
managed: managedHosts.has(route.hostname)
|
|
5754
|
+
}))
|
|
5755
|
+
);
|
|
5756
|
+
const unresolved = resolutionChecks.filter((result) => !result.resolves);
|
|
5757
|
+
if (unresolved.length === 0) {
|
|
5758
|
+
add("ok", "Registered hostnames resolve through the system resolver.");
|
|
5759
|
+
} else {
|
|
5760
|
+
add(
|
|
5761
|
+
"warn",
|
|
5762
|
+
`${pluralize(unresolved.length, "hostname")} did not resolve through the system resolver.`,
|
|
5763
|
+
"Run: portless hosts sync"
|
|
5764
|
+
);
|
|
5765
|
+
for (const result of unresolved.slice(0, 5)) {
|
|
5766
|
+
const hostState = result.managed ? "present in hosts block" : "missing from hosts block";
|
|
5767
|
+
add("warn", `${result.hostname} is ${hostState}.`);
|
|
5768
|
+
}
|
|
5769
|
+
}
|
|
5770
|
+
}
|
|
5771
|
+
for (const finding of findings) {
|
|
5772
|
+
printDoctorFinding(finding);
|
|
5773
|
+
}
|
|
5774
|
+
const failures = findings.filter((finding) => finding.status === "fail").length;
|
|
5775
|
+
const warnings = findings.filter((finding) => finding.status === "warn").length;
|
|
5776
|
+
console.log("");
|
|
5777
|
+
if (failures > 0) {
|
|
5778
|
+
console.log(
|
|
5779
|
+
colors_default.red(`Summary: ${pluralize(failures, "failure")}, ${pluralize(warnings, "warning")}.`)
|
|
5780
|
+
);
|
|
5781
|
+
process.exit(1);
|
|
5782
|
+
}
|
|
5783
|
+
console.log(colors_default.green(`Summary: 0 failures, ${pluralize(warnings, "warning")}.`));
|
|
5784
|
+
}
|
|
5404
5785
|
async function handleProxy(args) {
|
|
5405
5786
|
if (args[1] === "stop") {
|
|
5406
5787
|
let explicitPort;
|
|
@@ -5786,7 +6167,15 @@ ${colors_default.bold("LAN mode (--lan):")}
|
|
|
5786
6167
|
}
|
|
5787
6168
|
if (isForeground) {
|
|
5788
6169
|
console.log(chalk.blue.bold("\nportless proxy\n"));
|
|
5789
|
-
startProxyServer(
|
|
6170
|
+
startProxyServer(
|
|
6171
|
+
store,
|
|
6172
|
+
proxyPort,
|
|
6173
|
+
tld,
|
|
6174
|
+
tlsOptions,
|
|
6175
|
+
lanIp,
|
|
6176
|
+
desiredWildcard ? false : void 0,
|
|
6177
|
+
!!(customCertPath && customKeyPath)
|
|
6178
|
+
);
|
|
5790
6179
|
return;
|
|
5791
6180
|
}
|
|
5792
6181
|
store.ensureDir();
|
|
@@ -6002,7 +6391,7 @@ async function spawnProxiedApp(app, stateDir, proxyPort, tls2, tld, exitCodes) {
|
|
|
6002
6391
|
}
|
|
6003
6392
|
if (capturedStore && capturedHostname) {
|
|
6004
6393
|
try {
|
|
6005
|
-
capturedStore.removeRoute(capturedHostname);
|
|
6394
|
+
capturedStore.removeRoute(capturedHostname, process.pid);
|
|
6006
6395
|
} catch {
|
|
6007
6396
|
}
|
|
6008
6397
|
}
|
|
@@ -6216,7 +6605,7 @@ async function runWithTurbo(wsRoot, stateDir, proxyPort, tls2, tld, scriptName,
|
|
|
6216
6605
|
}, SIGKILL_TIMEOUT_MS).unref();
|
|
6217
6606
|
for (const { hostname } of routes) {
|
|
6218
6607
|
try {
|
|
6219
|
-
store.removeRoute(hostname);
|
|
6608
|
+
store.removeRoute(hostname, process.pid);
|
|
6220
6609
|
} catch {
|
|
6221
6610
|
}
|
|
6222
6611
|
}
|
|
@@ -6280,7 +6669,7 @@ async function runWithDirectSpawn(stateDir, proxyPort, tls2, tld, proxiedApps, t
|
|
|
6280
6669
|
}, SIGKILL_TIMEOUT_MS).unref();
|
|
6281
6670
|
for (const { store, hostname } of routeEntries) {
|
|
6282
6671
|
try {
|
|
6283
|
-
store.removeRoute(hostname);
|
|
6672
|
+
store.removeRoute(hostname, process.pid);
|
|
6284
6673
|
} catch {
|
|
6285
6674
|
}
|
|
6286
6675
|
}
|
|
@@ -6492,7 +6881,7 @@ async function main() {
|
|
|
6492
6881
|
args.shift();
|
|
6493
6882
|
}
|
|
6494
6883
|
const skipPortless = process.env.PORTLESS === "0" || process.env.PORTLESS === "false" || process.env.PORTLESS === "skip";
|
|
6495
|
-
if (skipPortless && (isRunCommand || args.length === 0 || args.length >= 2 && args[0] !== "proxy" && args[0] !== "clean" && args[0] !== "service")) {
|
|
6884
|
+
if (skipPortless && (isRunCommand || args.length === 0 || args.length >= 2 && args[0] !== "proxy" && args[0] !== "clean" && args[0] !== "doctor" && args[0] !== "service")) {
|
|
6496
6885
|
const parsed = isRunCommand ? parseRunArgs(args) : parseAppArgs(args);
|
|
6497
6886
|
let commandArgs = parsed.commandArgs;
|
|
6498
6887
|
if (commandArgs.length === 0 && (isRunCommand || args.length === 0)) {
|
|
@@ -6540,6 +6929,10 @@ async function main() {
|
|
|
6540
6929
|
await handleList();
|
|
6541
6930
|
return;
|
|
6542
6931
|
}
|
|
6932
|
+
if (args[0] === "doctor") {
|
|
6933
|
+
await handleDoctor(args);
|
|
6934
|
+
return;
|
|
6935
|
+
}
|
|
6543
6936
|
if (args[0] === "get") {
|
|
6544
6937
|
await handleGet(args);
|
|
6545
6938
|
return;
|
package/dist/index.d.ts
CHANGED
|
@@ -136,7 +136,13 @@ declare class RouteStore {
|
|
|
136
136
|
* merged; the route must already exist (matched by hostname).
|
|
137
137
|
*/
|
|
138
138
|
updateRoute(hostname: string, fields: RouteMetadataPatch): void;
|
|
139
|
-
|
|
139
|
+
/**
|
|
140
|
+
* Remove a route by hostname. When `ownerPid` is provided, the entry is
|
|
141
|
+
* only removed while it is still owned by that pid. Exit cleanups must
|
|
142
|
+
* pass their own pid: after a `--force` takeover the killed process would
|
|
143
|
+
* otherwise deregister the route the new owner just registered.
|
|
144
|
+
*/
|
|
145
|
+
removeRoute(hostname: string, ownerPid?: number): void;
|
|
140
146
|
}
|
|
141
147
|
|
|
142
148
|
/**
|
|
@@ -147,6 +153,8 @@ declare class RouteStore {
|
|
|
147
153
|
declare function fixOwnership(...paths: string[]): void;
|
|
148
154
|
/** Type guard for Node.js system errors with an error code. */
|
|
149
155
|
declare function isErrnoException(err: unknown): err is NodeJS.ErrnoException;
|
|
156
|
+
/** Return whether a process exists, treating permission denial as alive. */
|
|
157
|
+
declare function isProcessAlive(pid: number): boolean;
|
|
150
158
|
/**
|
|
151
159
|
* Escape HTML special characters to prevent XSS.
|
|
152
160
|
*/
|
|
@@ -204,4 +212,4 @@ declare function getManagedHostnames(): string[];
|
|
|
204
212
|
*/
|
|
205
213
|
declare function checkHostResolution(hostname: string): Promise<boolean>;
|
|
206
214
|
|
|
207
|
-
export { DIR_MODE, FILE_MODE, PORTLESS_HEADER, type ProxyServer, type ProxyServerOptions, RouteConflictError, type RouteInfo, type RouteMapping, RouteStore, buildBlock, checkHostResolution, cleanHostsFile, createHttpRedirectServer, createProxyServer, escapeHtml, extractManagedBlock, fixOwnership, formatUrl, getManagedHostnames, isErrnoException, parseHostname, removeBlock, shouldAutoSyncHosts, syncHostsFile };
|
|
215
|
+
export { DIR_MODE, FILE_MODE, PORTLESS_HEADER, type ProxyServer, type ProxyServerOptions, RouteConflictError, type RouteInfo, type RouteMapping, RouteStore, buildBlock, checkHostResolution, cleanHostsFile, createHttpRedirectServer, createProxyServer, escapeHtml, extractManagedBlock, fixOwnership, formatUrl, getManagedHostnames, isErrnoException, isProcessAlive, parseHostname, removeBlock, shouldAutoSyncHosts, syncHostsFile };
|
package/dist/index.js
CHANGED
|
@@ -15,11 +15,12 @@ import {
|
|
|
15
15
|
formatUrl,
|
|
16
16
|
getManagedHostnames,
|
|
17
17
|
isErrnoException,
|
|
18
|
+
isProcessAlive,
|
|
18
19
|
parseHostname,
|
|
19
20
|
removeBlock,
|
|
20
21
|
shouldAutoSyncHosts,
|
|
21
22
|
syncHostsFile
|
|
22
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-PCBKLZK2.js";
|
|
23
24
|
export {
|
|
24
25
|
DIR_MODE,
|
|
25
26
|
FILE_MODE,
|
|
@@ -37,6 +38,7 @@ export {
|
|
|
37
38
|
formatUrl,
|
|
38
39
|
getManagedHostnames,
|
|
39
40
|
isErrnoException,
|
|
41
|
+
isProcessAlive,
|
|
40
42
|
parseHostname,
|
|
41
43
|
removeBlock,
|
|
42
44
|
shouldAutoSyncHosts,
|