portless 0.10.1 → 0.10.2
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 +8 -3
- package/dist/{chunk-FM7IA4AF.js → chunk-EZJWUTUA.js} +47 -8
- package/dist/cli.js +81 -31
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,11 @@ portless myapp next dev
|
|
|
32
32
|
|
|
33
33
|
HTTPS with HTTP/2 is enabled by default. On first run, portless generates a local CA, trusts it, and binds port 443 (auto-elevates with sudo on macOS/Linux). Use `--no-tls` for plain HTTP.
|
|
34
34
|
|
|
35
|
-
The proxy auto-starts when you run an app. A random port (4000
|
|
35
|
+
The proxy auto-starts when you run an app. A random port (4000-4999) is assigned via the `PORT` environment variable. Most frameworks (Next.js, Express, Nuxt, etc.) respect this automatically. For frameworks that ignore `PORT` (Vite, VitePlus, Astro, React Router, Angular, Expo, React Native), portless auto-injects the right `--port` flag and, when needed, a matching `--host` flag.
|
|
36
|
+
|
|
37
|
+
When auto-starting, portless reuses the configuration (port, TLS, TLD) from the most recent proxy run, so a restart or reboot does not silently revert to defaults. Explicit env vars (`PORTLESS_PORT`, `PORTLESS_HTTPS`, etc.) always take priority.
|
|
38
|
+
|
|
39
|
+
In non-interactive environments (no TTY, or `CI=1`), portless exits with a descriptive error instead of prompting, so task runners like turborepo and CI scripts fail early with a clear message.
|
|
36
40
|
|
|
37
41
|
## Use in package.json
|
|
38
42
|
|
|
@@ -139,7 +143,7 @@ portless proxy start --lan --ip 192.168.1.42
|
|
|
139
143
|
|
|
140
144
|
`--lan` switches the proxy to mDNS discovery: services are advertised as `<name>.local` and reachable from any device on the same network. Portless auto-detects your LAN IP and follows Wi-Fi/IP changes automatically, but you can pin another address with `--ip <address>` or by exporting `PORTLESS_LAN_IP`. Set `PORTLESS_LAN=1` in your shell (0/1 boolean) to make LAN mode the default whenever the proxy starts.
|
|
141
145
|
|
|
142
|
-
Portless remembers LAN mode via `proxy.lan`, so if you stop a LAN proxy and start it again, it stays in LAN mode.
|
|
146
|
+
Portless remembers LAN mode via `proxy.lan`, so if you stop a LAN proxy and start it again, it stays in LAN mode. All proxy settings (port, TLS, TLD, LAN) are persisted and reused on auto-start unless overridden by explicit flags or env vars. Use `PORTLESS_LAN=0` for one start to switch back to `.localhost` mode. If a proxy is already running with different explicit LAN/TLS/TLD settings, portless warns and asks you to stop it first.
|
|
143
147
|
|
|
144
148
|
LAN mode depends on the system mDNS tools that portless already spawns: macOS ships with `dns-sd`, while Linux uses `avahi-publish-address` from `avahi-utils` (install via `sudo apt install avahi-utils` or your distro’s equivalent). If the command is missing or your network isn’t reachable, `portless proxy start --lan` prints the relevant error and exits.
|
|
145
149
|
|
|
@@ -218,6 +222,7 @@ PORTLESS_STATE_DIR=<path> Override the state directory
|
|
|
218
222
|
PORT Ephemeral port the child should listen on
|
|
219
223
|
HOST Usually 127.0.0.1 (omitted for Expo in LAN mode)
|
|
220
224
|
PORTLESS_URL Public URL (e.g. https://myapp.localhost)
|
|
225
|
+
NODE_EXTRA_CA_CERTS Path to the portless CA (when HTTPS is active)
|
|
221
226
|
```
|
|
222
227
|
|
|
223
228
|
> **Reserved names:** `run`, `get`, `alias`, `hosts`, `list`, `trust`, `clean`, and `proxy` 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.
|
|
@@ -275,7 +280,7 @@ devServer: {
|
|
|
275
280
|
}
|
|
276
281
|
```
|
|
277
282
|
|
|
278
|
-
|
|
283
|
+
Portless automatically sets `NODE_EXTRA_CA_CERTS` in child processes so Node.js trusts the portless CA. If you run a separate Node.js process outside portless, point it at the CA manually: `NODE_EXTRA_CA_CERTS=/tmp/portless/ca.pem` (or `~/.portless/ca.pem` when the proxy runs on a non-privileged port like 1355). Alternatively, use `--no-tls` for plain HTTP.
|
|
279
284
|
|
|
280
285
|
Portless detects this misconfiguration and responds with `508 Loop Detected` along with a message pointing to this fix.
|
|
281
286
|
|
|
@@ -392,8 +392,10 @@ function createProxyServer(options) {
|
|
|
392
392
|
proxyRes.on("error", () => {
|
|
393
393
|
if (!res.headersSent) {
|
|
394
394
|
res.writeHead(502, { "Content-Type": "text/plain" });
|
|
395
|
+
res.end();
|
|
396
|
+
} else {
|
|
397
|
+
res.destroy();
|
|
395
398
|
}
|
|
396
|
-
res.end();
|
|
397
399
|
});
|
|
398
400
|
proxyRes.pipe(res);
|
|
399
401
|
}
|
|
@@ -516,9 +518,19 @@ function createProxyServer(options) {
|
|
|
516
518
|
cert: tls.ca ? Buffer.concat([tls.cert, tls.ca]) : tls.cert,
|
|
517
519
|
key: tls.key,
|
|
518
520
|
allowHTTP1: true,
|
|
521
|
+
// Tolerate high rates of RST_STREAM from browsers during HMR and
|
|
522
|
+
// page navigations. Without this, Node sends GOAWAY INTERNAL_ERROR
|
|
523
|
+
// after ~1000 cumulative stream resets and kills the session,
|
|
524
|
+
// surfacing as ERR_HTTP2_PROTOCOL_ERROR in Chrome. Available in
|
|
525
|
+
// Node 22.11+; silently ignored on older versions.
|
|
526
|
+
...{ streamResetBurst: 1e4, streamResetRate: 100 },
|
|
519
527
|
...tls.SNICallback ? { SNICallback: tls.SNICallback } : {}
|
|
520
528
|
});
|
|
529
|
+
h2Server.on("sessionError", () => {
|
|
530
|
+
});
|
|
521
531
|
h2Server.on("request", (req, res) => {
|
|
532
|
+
req.stream?.on("error", () => {
|
|
533
|
+
});
|
|
522
534
|
handleRequest(req, res);
|
|
523
535
|
});
|
|
524
536
|
h2Server.on("upgrade", (req, socket, head) => {
|
|
@@ -901,6 +913,24 @@ function isLanEnvEnabled() {
|
|
|
901
913
|
const val = process.env.PORTLESS_LAN;
|
|
902
914
|
return val === "1" || val === "true";
|
|
903
915
|
}
|
|
916
|
+
function readPersistedProxyState() {
|
|
917
|
+
const dirs = [];
|
|
918
|
+
if (process.env.PORTLESS_STATE_DIR) {
|
|
919
|
+
dirs.push(process.env.PORTLESS_STATE_DIR);
|
|
920
|
+
} else {
|
|
921
|
+
dirs.push(USER_STATE_DIR, SYSTEM_STATE_DIR);
|
|
922
|
+
}
|
|
923
|
+
for (const dir of dirs) {
|
|
924
|
+
const port = readPortFromDir(dir);
|
|
925
|
+
if (port !== null) {
|
|
926
|
+
const tls = readTlsMarker(dir);
|
|
927
|
+
const tld = readTldFromDir(dir);
|
|
928
|
+
const lanIp = readLanMarker(dir);
|
|
929
|
+
return { port, tls, tld, lanMode: lanIp !== null || tld === "local" };
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
904
934
|
function buildProxyStartConfig(options) {
|
|
905
935
|
const effectiveTld = options.lanMode ? "local" : options.tld;
|
|
906
936
|
const args = [];
|
|
@@ -934,6 +964,9 @@ function buildProxyStartConfig(options) {
|
|
|
934
964
|
if (options.useWildcard) {
|
|
935
965
|
args.push("--wildcard");
|
|
936
966
|
}
|
|
967
|
+
if (options.skipTrust) {
|
|
968
|
+
args.push("--skip-trust");
|
|
969
|
+
}
|
|
937
970
|
return { effectiveTld, args };
|
|
938
971
|
}
|
|
939
972
|
async function discoverState() {
|
|
@@ -1002,8 +1035,8 @@ async function discoverState() {
|
|
|
1002
1035
|
return {
|
|
1003
1036
|
dir,
|
|
1004
1037
|
port: configuredPort,
|
|
1005
|
-
tls:
|
|
1006
|
-
tld:
|
|
1038
|
+
tls: readTlsMarker(dir),
|
|
1039
|
+
tld: readTldFromDir(dir),
|
|
1007
1040
|
lanMode: readLanMarker(dir) !== null,
|
|
1008
1041
|
lanIp: null
|
|
1009
1042
|
};
|
|
@@ -1272,8 +1305,9 @@ function prompt(question) {
|
|
|
1272
1305
|
import * as fs4 from "fs";
|
|
1273
1306
|
import * as path3 from "path";
|
|
1274
1307
|
var STALE_LOCK_THRESHOLD_MS = 1e4;
|
|
1275
|
-
var
|
|
1276
|
-
var
|
|
1308
|
+
var LOCK_TIMEOUT_MS = 5e3;
|
|
1309
|
+
var LOCK_RETRY_BASE_MS = 10;
|
|
1310
|
+
var LOCK_RETRY_CAP_MS = 500;
|
|
1277
1311
|
var FILE_MODE = 420;
|
|
1278
1312
|
var DIR_MODE = 493;
|
|
1279
1313
|
var SYSTEM_DIR_MODE = 1023;
|
|
@@ -1337,8 +1371,10 @@ var RouteStore = class _RouteStore {
|
|
|
1337
1371
|
syncSleep(ms) {
|
|
1338
1372
|
Atomics.wait(_RouteStore.sleepBuffer, 0, 0, ms);
|
|
1339
1373
|
}
|
|
1340
|
-
acquireLock(
|
|
1341
|
-
|
|
1374
|
+
acquireLock() {
|
|
1375
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
1376
|
+
let delay = LOCK_RETRY_BASE_MS;
|
|
1377
|
+
while (Date.now() < deadline) {
|
|
1342
1378
|
try {
|
|
1343
1379
|
fs4.mkdirSync(this.lockPath);
|
|
1344
1380
|
return true;
|
|
@@ -1353,7 +1389,9 @@ var RouteStore = class _RouteStore {
|
|
|
1353
1389
|
} catch {
|
|
1354
1390
|
continue;
|
|
1355
1391
|
}
|
|
1356
|
-
|
|
1392
|
+
const jitter = Math.floor(Math.random() * delay);
|
|
1393
|
+
this.syncSleep(delay + jitter);
|
|
1394
|
+
delay = Math.min(delay * 2, LOCK_RETRY_CAP_MS);
|
|
1357
1395
|
} else {
|
|
1358
1396
|
return false;
|
|
1359
1397
|
}
|
|
@@ -1507,6 +1545,7 @@ export {
|
|
|
1507
1545
|
isHttpsEnvDisabled,
|
|
1508
1546
|
isWildcardEnvEnabled,
|
|
1509
1547
|
isLanEnvEnabled,
|
|
1548
|
+
readPersistedProxyState,
|
|
1510
1549
|
buildProxyStartConfig,
|
|
1511
1550
|
discoverState,
|
|
1512
1551
|
findFreePort,
|
package/dist/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
parseHostname,
|
|
36
36
|
prompt,
|
|
37
37
|
readLanMarker,
|
|
38
|
+
readPersistedProxyState,
|
|
38
39
|
readTldFromDir,
|
|
39
40
|
readTlsMarker,
|
|
40
41
|
resolveStateDir,
|
|
@@ -46,7 +47,7 @@ import {
|
|
|
46
47
|
writeLanMarker,
|
|
47
48
|
writeTldFile,
|
|
48
49
|
writeTlsMarker
|
|
49
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-EZJWUTUA.js";
|
|
50
51
|
|
|
51
52
|
// src/colors.ts
|
|
52
53
|
function supportsColor() {
|
|
@@ -90,6 +91,9 @@ var SERVER_VALIDITY_DAYS = 365;
|
|
|
90
91
|
var EXPIRY_BUFFER_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
91
92
|
var CA_COMMON_NAME = "portless Local CA";
|
|
92
93
|
var OPENSSL_TIMEOUT_MS = 15e3;
|
|
94
|
+
var MACOS_SECURITY_TIMEOUT_MS = 15e3;
|
|
95
|
+
var MACOS_SECURITY_AUTH_TIMEOUT_MS = 12e4;
|
|
96
|
+
var MACOS_SECURITY_ROOT_TIMEOUT_MS = 6e4;
|
|
93
97
|
var CA_KEY_FILE = "ca-key.pem";
|
|
94
98
|
var CA_CERT_FILE = "ca.pem";
|
|
95
99
|
var SERVER_KEY_FILE = "server-key.pem";
|
|
@@ -338,13 +342,13 @@ function isCATrustedMacOS(caCertPath) {
|
|
|
338
342
|
["-u", sudoUser, "security", "verify-cert", "-c", caCertPath, "-L", "-p", "ssl"],
|
|
339
343
|
{
|
|
340
344
|
stdio: "pipe",
|
|
341
|
-
timeout:
|
|
345
|
+
timeout: MACOS_SECURITY_TIMEOUT_MS
|
|
342
346
|
}
|
|
343
347
|
);
|
|
344
348
|
} else {
|
|
345
349
|
execFileSync("security", ["verify-cert", "-c", caCertPath, "-L", "-p", "ssl"], {
|
|
346
350
|
stdio: "pipe",
|
|
347
|
-
timeout:
|
|
351
|
+
timeout: MACOS_SECURITY_TIMEOUT_MS
|
|
348
352
|
});
|
|
349
353
|
}
|
|
350
354
|
return true;
|
|
@@ -356,7 +360,7 @@ function loginKeychainPath() {
|
|
|
356
360
|
try {
|
|
357
361
|
const result = execFileSync("security", ["default-keychain"], {
|
|
358
362
|
encoding: "utf-8",
|
|
359
|
-
timeout:
|
|
363
|
+
timeout: MACOS_SECURITY_TIMEOUT_MS
|
|
360
364
|
}).trim();
|
|
361
365
|
const match = result.match(/"(.+)"/);
|
|
362
366
|
if (match) return match[1];
|
|
@@ -572,14 +576,14 @@ function trustCA(stateDir) {
|
|
|
572
576
|
"/Library/Keychains/System.keychain",
|
|
573
577
|
caCertPath
|
|
574
578
|
],
|
|
575
|
-
{ stdio: "pipe", timeout:
|
|
579
|
+
{ stdio: "pipe", timeout: MACOS_SECURITY_ROOT_TIMEOUT_MS }
|
|
576
580
|
);
|
|
577
581
|
} else {
|
|
578
582
|
const keychain = loginKeychainPath();
|
|
579
583
|
execFileSync(
|
|
580
584
|
"security",
|
|
581
585
|
["add-trusted-cert", "-r", "trustRoot", "-k", keychain, caCertPath],
|
|
582
|
-
{ stdio: "pipe", timeout:
|
|
586
|
+
{ stdio: "pipe", timeout: MACOS_SECURITY_AUTH_TIMEOUT_MS }
|
|
583
587
|
);
|
|
584
588
|
}
|
|
585
589
|
return { trusted: true };
|
|
@@ -602,6 +606,10 @@ function trustCA(stateDir) {
|
|
|
602
606
|
return { trusted: false, error: `Unsupported platform: ${process.platform}` };
|
|
603
607
|
} catch (err) {
|
|
604
608
|
const message = err instanceof Error ? err.message : String(err);
|
|
609
|
+
if (message.includes("ETIMEDOUT")) {
|
|
610
|
+
const hint = process.platform === "darwin" ? "The macOS security command timed out. This can happen when the Keychain Services daemon is unresponsive or a system authorization dialog was not dismissed in time. Try restarting Keychain Access (or run: sudo killall securityd) and then: portless trust" : "The trust command timed out. Try: portless trust";
|
|
611
|
+
return { trusted: false, error: hint };
|
|
612
|
+
}
|
|
605
613
|
if (message.includes("authorization") || message.includes("permission") || message.includes("EACCES")) {
|
|
606
614
|
return {
|
|
607
615
|
trusted: false,
|
|
@@ -639,7 +647,7 @@ function untrustCAMacOS(caCertPath) {
|
|
|
639
647
|
const errors = [];
|
|
640
648
|
const tryExec = (args) => {
|
|
641
649
|
try {
|
|
642
|
-
execFileSync("security", args, { stdio: "pipe", timeout:
|
|
650
|
+
execFileSync("security", args, { stdio: "pipe", timeout: MACOS_SECURITY_ROOT_TIMEOUT_MS });
|
|
643
651
|
return true;
|
|
644
652
|
} catch (err) {
|
|
645
653
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -663,12 +671,12 @@ function isCATrustedMacOSAfterAttempt(caCertPath) {
|
|
|
663
671
|
execFileSync(
|
|
664
672
|
"sudo",
|
|
665
673
|
["-u", sudoUser, "security", "verify-cert", "-c", caCertPath, "-L", "-p", "ssl"],
|
|
666
|
-
{ stdio: "pipe", timeout:
|
|
674
|
+
{ stdio: "pipe", timeout: MACOS_SECURITY_TIMEOUT_MS }
|
|
667
675
|
);
|
|
668
676
|
} else {
|
|
669
677
|
execFileSync("security", ["verify-cert", "-c", caCertPath, "-L", "-p", "ssl"], {
|
|
670
678
|
stdio: "pipe",
|
|
671
|
-
timeout:
|
|
679
|
+
timeout: MACOS_SECURITY_TIMEOUT_MS
|
|
672
680
|
});
|
|
673
681
|
}
|
|
674
682
|
return true;
|
|
@@ -1695,11 +1703,30 @@ portless
|
|
|
1695
1703
|
const proxyResponsive = await isProxyRunning(proxyPort, tls2);
|
|
1696
1704
|
const proxyListeningFromStateDir = !!process.env.PORTLESS_STATE_DIR && await isPortListening(proxyPort);
|
|
1697
1705
|
if (!proxyResponsive && !proxyListeningFromStateDir) {
|
|
1698
|
-
const
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1706
|
+
const persisted = readPersistedProxyState();
|
|
1707
|
+
const startConfig = { ...desiredConfig };
|
|
1708
|
+
let startPort;
|
|
1709
|
+
if (persisted) {
|
|
1710
|
+
if (!explicit.useHttps && persisted.tls !== desiredConfig.useHttps) {
|
|
1711
|
+
startConfig.useHttps = persisted.tls;
|
|
1712
|
+
}
|
|
1713
|
+
if (!explicit.tld && persisted.tld !== desiredConfig.tld) {
|
|
1714
|
+
startConfig.tld = persisted.tld;
|
|
1715
|
+
}
|
|
1716
|
+
if (!explicit.lanMode && persisted.lanMode !== desiredConfig.lanMode) {
|
|
1717
|
+
startConfig.lanMode = persisted.lanMode;
|
|
1718
|
+
}
|
|
1719
|
+
const envPort = getDefaultPort(startConfig.useHttps);
|
|
1720
|
+
if (persisted.port !== envPort) {
|
|
1721
|
+
startPort = persisted.port;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
const effectivePort = startPort ?? getDefaultPort(startConfig.useHttps);
|
|
1725
|
+
const needsSudo = !isWindows && effectivePort < PRIVILEGED_PORT_THRESHOLD;
|
|
1726
|
+
const manualStartCommand = formatProxyStartCommand(effectivePort, startConfig);
|
|
1727
|
+
const fallbackStartCommand = formatProxyStartCommand(FALLBACK_PROXY_PORT, startConfig);
|
|
1728
|
+
const isInteractive = !!process.stdin.isTTY && !process.env.CI;
|
|
1729
|
+
if (needsSudo && !isInteractive) {
|
|
1703
1730
|
console.error(colors_default.red("Proxy is not running and no TTY is available for sudo."));
|
|
1704
1731
|
console.error(colors_default.blue("Option 1: start the proxy in a terminal (will prompt for sudo):"));
|
|
1705
1732
|
console.error(colors_default.cyan(` ${manualStartCommand}`));
|
|
@@ -1711,7 +1738,7 @@ portless
|
|
|
1711
1738
|
console.error(colors_default.cyan(` ${fallbackStartCommand}`));
|
|
1712
1739
|
process.exit(1);
|
|
1713
1740
|
}
|
|
1714
|
-
if (needsSudo
|
|
1741
|
+
if (needsSudo) {
|
|
1715
1742
|
const answer = await prompt(colors_default.yellow("Proxy not running. Start it? [Y/n/skip] "));
|
|
1716
1743
|
if (answer === "n" || answer === "no") {
|
|
1717
1744
|
console.log(colors_default.gray("Cancelled."));
|
|
@@ -1723,16 +1750,26 @@ portless
|
|
|
1723
1750
|
return;
|
|
1724
1751
|
}
|
|
1725
1752
|
}
|
|
1726
|
-
|
|
1753
|
+
if (persisted && startPort !== void 0) {
|
|
1754
|
+
console.log(
|
|
1755
|
+
colors_default.yellow(
|
|
1756
|
+
`Starting proxy with previous configuration (port ${startPort}, ${startConfig.useHttps ? "HTTPS" : "HTTP"})...`
|
|
1757
|
+
)
|
|
1758
|
+
);
|
|
1759
|
+
} else {
|
|
1760
|
+
console.log(colors_default.yellow("Starting proxy..."));
|
|
1761
|
+
}
|
|
1727
1762
|
const proxyStartConfig = buildProxyStartConfig({
|
|
1728
|
-
useHttps:
|
|
1729
|
-
customCertPath:
|
|
1730
|
-
customKeyPath:
|
|
1731
|
-
lanMode:
|
|
1732
|
-
lanIp:
|
|
1733
|
-
lanIpExplicit:
|
|
1734
|
-
tld:
|
|
1735
|
-
useWildcard:
|
|
1763
|
+
useHttps: startConfig.useHttps,
|
|
1764
|
+
customCertPath: startConfig.customCertPath,
|
|
1765
|
+
customKeyPath: startConfig.customKeyPath,
|
|
1766
|
+
lanMode: startConfig.lanMode,
|
|
1767
|
+
lanIp: startConfig.lanIpExplicit ? startConfig.lanIp : null,
|
|
1768
|
+
lanIpExplicit: startConfig.lanIpExplicit,
|
|
1769
|
+
tld: startConfig.tld,
|
|
1770
|
+
useWildcard: startConfig.useWildcard,
|
|
1771
|
+
includePort: startPort !== void 0,
|
|
1772
|
+
proxyPort: startPort
|
|
1736
1773
|
});
|
|
1737
1774
|
const startArgs = [getEntryScript(), "proxy", "start", ...proxyStartConfig.args];
|
|
1738
1775
|
const result = spawnSync2(process.execPath, startArgs, {
|
|
@@ -1752,7 +1789,7 @@ portless
|
|
|
1752
1789
|
}
|
|
1753
1790
|
if (!discovered) {
|
|
1754
1791
|
console.error(colors_default.red("Failed to start proxy."));
|
|
1755
|
-
const fallbackDir = resolveStateDir(
|
|
1792
|
+
const fallbackDir = resolveStateDir(effectivePort);
|
|
1756
1793
|
const logPath = path4.join(fallbackDir, "proxy.log");
|
|
1757
1794
|
console.error(colors_default.blue("Try starting it manually:"));
|
|
1758
1795
|
console.error(colors_default.cyan(` ${manualStartCommand}`));
|
|
@@ -1837,9 +1874,17 @@ portless
|
|
|
1837
1874
|
process.env.PORTLESS_LAN = "1";
|
|
1838
1875
|
}
|
|
1839
1876
|
injectFrameworkFlags(commandArgs, port);
|
|
1877
|
+
const caEnv = {};
|
|
1878
|
+
if (tls2 && !process.env.NODE_EXTRA_CA_CERTS) {
|
|
1879
|
+
const caPath = path4.join(stateDir, "ca.pem");
|
|
1880
|
+
if (fs4.existsSync(caPath)) {
|
|
1881
|
+
caEnv.NODE_EXTRA_CA_CERTS = caPath;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
const caFragment = caEnv.NODE_EXTRA_CA_CERTS ? ` NODE_EXTRA_CA_CERTS="${caEnv.NODE_EXTRA_CA_CERTS}"` : "";
|
|
1840
1885
|
console.log(
|
|
1841
1886
|
chalk.gray(
|
|
1842
|
-
`Running: PORT=${port}${hostBind ? ` HOST=${hostBind}` : ""} PORTLESS_URL=${finalUrl} ${commandArgs.join(" ")}
|
|
1887
|
+
`Running: PORT=${port}${hostBind ? ` HOST=${hostBind}` : ""} PORTLESS_URL=${finalUrl}${caFragment} ${commandArgs.join(" ")}
|
|
1843
1888
|
`
|
|
1844
1889
|
)
|
|
1845
1890
|
);
|
|
@@ -1853,7 +1898,8 @@ portless
|
|
|
1853
1898
|
// Note: EXPO_PACKAGER_PROXY_URL is not used — expo-dev-client removed
|
|
1854
1899
|
// baked-in pinging, making this env var ineffective. Expo handles its
|
|
1855
1900
|
// own LAN discovery natively.
|
|
1856
|
-
...lanMode ? { PORTLESS_LAN: "1" } : {}
|
|
1901
|
+
...lanMode ? { PORTLESS_LAN: "1" } : {},
|
|
1902
|
+
...caEnv
|
|
1857
1903
|
},
|
|
1858
1904
|
onCleanup: () => {
|
|
1859
1905
|
try {
|
|
@@ -2055,7 +2101,8 @@ ${colors_default.bold("LAN mode:")}
|
|
|
2055
2101
|
Expo keeps Metro's default LAN host behavior in this mode.
|
|
2056
2102
|
Auto-detected LAN IPs follow network changes automatically.
|
|
2057
2103
|
Stopped LAN proxies keep LAN mode for the next start via proxy.lan.
|
|
2058
|
-
|
|
2104
|
+
All proxy settings are persisted and reused on auto-start unless
|
|
2105
|
+
overridden by explicit flags or env vars.
|
|
2059
2106
|
Use PORTLESS_LAN=0 for one start to switch back to .localhost mode.
|
|
2060
2107
|
If a proxy is already running with different explicit LAN/TLS/TLD settings,
|
|
2061
2108
|
stop it first.
|
|
@@ -2098,6 +2145,7 @@ ${colors_default.bold("Child process environment:")}
|
|
|
2098
2145
|
HOST Usually 127.0.0.1 (omitted for Expo in LAN mode)
|
|
2099
2146
|
PORTLESS_URL Public URL of the app (e.g. https://myapp.localhost)
|
|
2100
2147
|
PORTLESS_LAN Set to 1 when proxy is in LAN mode
|
|
2148
|
+
NODE_EXTRA_CA_CERTS Path to the portless CA (set when HTTPS is active)
|
|
2101
2149
|
|
|
2102
2150
|
${colors_default.bold("Safari / DNS:")}
|
|
2103
2151
|
.localhost subdomains auto-resolve in Chrome, Firefox, and Edge.
|
|
@@ -2119,7 +2167,7 @@ ${colors_default.bold("Reserved names:")}
|
|
|
2119
2167
|
process.exit(0);
|
|
2120
2168
|
}
|
|
2121
2169
|
function printVersion() {
|
|
2122
|
-
console.log("0.10.
|
|
2170
|
+
console.log("0.10.2");
|
|
2123
2171
|
process.exit(0);
|
|
2124
2172
|
}
|
|
2125
2173
|
async function handleTrust() {
|
|
@@ -2517,6 +2565,7 @@ ${colors_default.bold("LAN mode (--lan):")}
|
|
|
2517
2565
|
process.exit(isProxyHelp || !args[1] ? 0 : 1);
|
|
2518
2566
|
}
|
|
2519
2567
|
const isForeground = args.includes("--foreground");
|
|
2568
|
+
const skipTrust = args.includes("--skip-trust");
|
|
2520
2569
|
const hasHttpsFlag = args.includes("--https");
|
|
2521
2570
|
const hasNoTls = args.includes("--no-tls") || isHttpsEnvDisabled();
|
|
2522
2571
|
const wantHttps = !hasNoTls;
|
|
@@ -2816,7 +2865,7 @@ ${colors_default.bold("LAN mode (--lan):")}
|
|
|
2816
2865
|
if (certs.caGenerated) {
|
|
2817
2866
|
console.log(colors_default.green("Generated local CA certificate."));
|
|
2818
2867
|
}
|
|
2819
|
-
if (!isCATrusted(stateDir)) {
|
|
2868
|
+
if (!skipTrust && !isCATrusted(stateDir)) {
|
|
2820
2869
|
console.log(colors_default.yellow("Adding CA to system trust store..."));
|
|
2821
2870
|
const trustResult = trustCA(stateDir);
|
|
2822
2871
|
if (trustResult.trusted) {
|
|
@@ -2874,7 +2923,8 @@ ${colors_default.bold("LAN mode (--lan):")}
|
|
|
2874
2923
|
useWildcard: desiredWildcard,
|
|
2875
2924
|
foreground: true,
|
|
2876
2925
|
includePort: true,
|
|
2877
|
-
proxyPort
|
|
2926
|
+
proxyPort,
|
|
2927
|
+
skipTrust: true
|
|
2878
2928
|
}).args
|
|
2879
2929
|
];
|
|
2880
2930
|
const child = spawn2(process.execPath, daemonArgs, {
|
package/dist/index.js
CHANGED