portless 0.9.6 → 0.10.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 +34 -3
- package/dist/{chunk-OPTRASOS.js → chunk-WXEE5QH6.js} +133 -14
- package/dist/cli.js +757 -106
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,7 +32,7 @@ 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--4999) is assigned via the `PORT` environment variable. Most frameworks (Next.js, Express, Nuxt, etc.) respect this automatically. For frameworks that ignore `PORT` (Vite, Astro, React Router, Angular, Expo, React Native), portless auto-injects `--port` and `--host`
|
|
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
36
|
|
|
37
37
|
## Use in package.json
|
|
38
38
|
|
|
@@ -129,6 +129,33 @@ portless trust
|
|
|
129
129
|
|
|
130
130
|
On Linux, `portless trust` supports Debian/Ubuntu, Arch, Fedora/RHEL/CentOS, and openSUSE (via `update-ca-certificates` or `update-ca-trust`). On Windows, it uses `certutil` to add the CA to the system trust store.
|
|
131
131
|
|
|
132
|
+
## LAN mode
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
portless proxy start --lan
|
|
136
|
+
portless proxy start --lan --https
|
|
137
|
+
portless proxy start --lan --ip 192.168.1.42
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`--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
|
+
|
|
142
|
+
Portless remembers LAN mode via `proxy.lan`, so if you stop a LAN proxy and start it again, it stays in LAN mode. Other proxy settings still follow the current flags and 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
|
+
|
|
144
|
+
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
|
+
|
|
146
|
+
### Framework notes
|
|
147
|
+
|
|
148
|
+
- **Next.js**: add your `.local` hostnames to `allowedDevOrigins`:
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
// next.config.js
|
|
152
|
+
module.exports = {
|
|
153
|
+
allowedDevOrigins: ["myapp.local", "*.myapp.local"],
|
|
154
|
+
};
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
- **Expo / React Native**: portless always injects `--port`. React Native also gets `--host 127.0.0.1`. Expo gets `--host localhost` outside LAN mode, but in LAN mode portless leaves Metro on its default LAN host behavior instead of forcing `--host` or `HOST`.
|
|
158
|
+
|
|
132
159
|
## Commands
|
|
133
160
|
|
|
134
161
|
```bash
|
|
@@ -148,6 +175,7 @@ PORTLESS=0 pnpm dev # Bypasses proxy, uses default port
|
|
|
148
175
|
# Proxy control
|
|
149
176
|
portless proxy start # Start the HTTPS proxy (port 443, daemon)
|
|
150
177
|
portless proxy start --no-tls # Start without HTTPS (port 80)
|
|
178
|
+
portless proxy start --lan # Start in LAN mode (mDNS .local for devices)
|
|
151
179
|
portless proxy start -p 1355 # Start on a custom port (no sudo)
|
|
152
180
|
portless proxy start --foreground # Start in foreground (for debugging)
|
|
153
181
|
portless proxy start --wildcard # Allow unregistered subdomains to fall back to parent
|
|
@@ -160,6 +188,8 @@ portless proxy stop # Stop the proxy
|
|
|
160
188
|
-p, --port <number> Port for the proxy (default: 443, or 80 with --no-tls)
|
|
161
189
|
--no-tls Disable HTTPS (use plain HTTP on port 80)
|
|
162
190
|
--https Enable HTTPS (default, accepted for compatibility)
|
|
191
|
+
--lan Enable LAN mode (mDNS .local for real devices)
|
|
192
|
+
--ip <address> Pin a specific LAN IP (disables auto-follow; use with --lan)
|
|
163
193
|
--cert <path> Use a custom TLS certificate
|
|
164
194
|
--key <path> Use a custom TLS private key
|
|
165
195
|
--foreground Run proxy in foreground instead of daemon
|
|
@@ -176,7 +206,8 @@ portless proxy stop # Stop the proxy
|
|
|
176
206
|
# Configuration
|
|
177
207
|
PORTLESS_PORT=<number> Override the default proxy port
|
|
178
208
|
PORTLESS_APP_PORT=<number> Use a fixed port for the app (same as --app-port)
|
|
179
|
-
PORTLESS_HTTPS
|
|
209
|
+
PORTLESS_HTTPS=0 Disable HTTPS (same as --no-tls)
|
|
210
|
+
PORTLESS_LAN=1 Enable LAN mode when set to 1 (auto-detects LAN IP)
|
|
180
211
|
PORTLESS_TLD=<tld> Use a custom TLD (e.g. test; default: localhost)
|
|
181
212
|
PORTLESS_WILDCARD=1 Allow unregistered subdomains to fall back to parent route
|
|
182
213
|
PORTLESS_SYNC_HOSTS=1 Auto-sync /etc/hosts (auto-enabled for custom TLDs)
|
|
@@ -184,7 +215,7 @@ PORTLESS_STATE_DIR=<path> Override the state directory
|
|
|
184
215
|
|
|
185
216
|
# Injected into child processes
|
|
186
217
|
PORT Ephemeral port the child should listen on
|
|
187
|
-
HOST
|
|
218
|
+
HOST Usually 127.0.0.1 (omitted for Expo in LAN mode)
|
|
188
219
|
PORTLESS_URL Public URL (e.g. https://myapp.localhost)
|
|
189
220
|
```
|
|
190
221
|
|
|
@@ -670,6 +670,8 @@ import { execSync, spawn } from "child_process";
|
|
|
670
670
|
var isWindows2 = process.platform === "win32";
|
|
671
671
|
var FALLBACK_PROXY_PORT = 1355;
|
|
672
672
|
var PRIVILEGED_PORT_THRESHOLD = 1024;
|
|
673
|
+
var INTERNAL_LAN_IP_ENV = "PORTLESS_INTERNAL_LAN_IP";
|
|
674
|
+
var INTERNAL_LAN_IP_FLAG = "--lan-ip-auto";
|
|
673
675
|
var SYSTEM_STATE_DIR = isWindows2 ? path2.join(os.tmpdir(), "portless") : "/tmp/portless";
|
|
674
676
|
var USER_STATE_DIR = path2.join(os.homedir(), ".portless");
|
|
675
677
|
var MIN_APP_PORT = 4e3;
|
|
@@ -816,6 +818,26 @@ function writeTlsMarker(dir, enabled) {
|
|
|
816
818
|
}
|
|
817
819
|
}
|
|
818
820
|
}
|
|
821
|
+
var LAN_MARKER_FILE = "proxy.lan";
|
|
822
|
+
function readLanMarker(dir) {
|
|
823
|
+
try {
|
|
824
|
+
const raw = fs3.readFileSync(path2.join(dir, LAN_MARKER_FILE), "utf-8").trim();
|
|
825
|
+
return raw || null;
|
|
826
|
+
} catch {
|
|
827
|
+
return null;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
function writeLanMarker(dir, ip) {
|
|
831
|
+
const markerPath = path2.join(dir, LAN_MARKER_FILE);
|
|
832
|
+
if (!ip) {
|
|
833
|
+
try {
|
|
834
|
+
fs3.unlinkSync(markerPath);
|
|
835
|
+
} catch {
|
|
836
|
+
}
|
|
837
|
+
} else {
|
|
838
|
+
fs3.writeFileSync(markerPath, ip, { mode: 420 });
|
|
839
|
+
}
|
|
840
|
+
}
|
|
819
841
|
var DEFAULT_TLD = "localhost";
|
|
820
842
|
var RISKY_TLDS = /* @__PURE__ */ new Map([
|
|
821
843
|
["local", "conflicts with mDNS/Bonjour on macOS"],
|
|
@@ -872,20 +894,78 @@ function isWildcardEnvEnabled() {
|
|
|
872
894
|
const val = process.env.PORTLESS_WILDCARD;
|
|
873
895
|
return val === "1" || val === "true";
|
|
874
896
|
}
|
|
897
|
+
function isLanEnvEnabled() {
|
|
898
|
+
const val = process.env.PORTLESS_LAN;
|
|
899
|
+
return val === "1" || val === "true";
|
|
900
|
+
}
|
|
901
|
+
function buildProxyStartConfig(options) {
|
|
902
|
+
const effectiveTld = options.lanMode ? "local" : options.tld;
|
|
903
|
+
const args = [];
|
|
904
|
+
if (options.foreground) {
|
|
905
|
+
args.push("--foreground");
|
|
906
|
+
}
|
|
907
|
+
if (options.includePort && options.proxyPort !== void 0) {
|
|
908
|
+
args.push("--port", options.proxyPort.toString());
|
|
909
|
+
}
|
|
910
|
+
if (options.useHttps) {
|
|
911
|
+
if (options.customCertPath && options.customKeyPath) {
|
|
912
|
+
args.push("--cert", options.customCertPath, "--key", options.customKeyPath);
|
|
913
|
+
} else {
|
|
914
|
+
args.push("--https");
|
|
915
|
+
}
|
|
916
|
+
} else {
|
|
917
|
+
args.push("--no-tls");
|
|
918
|
+
}
|
|
919
|
+
if (options.lanMode) {
|
|
920
|
+
args.push("--lan");
|
|
921
|
+
if (options.lanIp) {
|
|
922
|
+
if (options.lanIpExplicit) {
|
|
923
|
+
args.push("--ip", options.lanIp);
|
|
924
|
+
} else {
|
|
925
|
+
args.push(INTERNAL_LAN_IP_FLAG, options.lanIp);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
} else if (effectiveTld !== DEFAULT_TLD) {
|
|
929
|
+
args.push("--tld", effectiveTld);
|
|
930
|
+
}
|
|
931
|
+
if (options.useWildcard) {
|
|
932
|
+
args.push("--wildcard");
|
|
933
|
+
}
|
|
934
|
+
return { effectiveTld, args };
|
|
935
|
+
}
|
|
875
936
|
async function discoverState() {
|
|
876
937
|
if (process.env.PORTLESS_STATE_DIR) {
|
|
877
|
-
const
|
|
878
|
-
const port = readPortFromDir(
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
938
|
+
const dir2 = process.env.PORTLESS_STATE_DIR;
|
|
939
|
+
const port = readPortFromDir(dir2) ?? getDefaultPort();
|
|
940
|
+
const lanIp = readLanMarker(dir2);
|
|
941
|
+
if (await isProxyRunning(port) || await isPortListening(port)) {
|
|
942
|
+
const tls = readTlsMarker(dir2);
|
|
943
|
+
const tld = readTldFromDir(dir2);
|
|
944
|
+
return { dir: dir2, port, tls, tld, lanMode: lanIp !== null || tld === "local", lanIp };
|
|
945
|
+
}
|
|
946
|
+
return {
|
|
947
|
+
dir: dir2,
|
|
948
|
+
port,
|
|
949
|
+
tls: readTlsMarker(dir2),
|
|
950
|
+
tld: readTldFromDir(dir2),
|
|
951
|
+
lanMode: lanIp !== null,
|
|
952
|
+
lanIp: null
|
|
953
|
+
};
|
|
882
954
|
}
|
|
883
955
|
const userPort = readPortFromDir(USER_STATE_DIR);
|
|
884
956
|
if (userPort !== null) {
|
|
885
957
|
if (await isProxyRunning(userPort)) {
|
|
886
958
|
const tls = readTlsMarker(USER_STATE_DIR);
|
|
887
959
|
const tld = readTldFromDir(USER_STATE_DIR);
|
|
888
|
-
|
|
960
|
+
const lanIp = readLanMarker(USER_STATE_DIR);
|
|
961
|
+
return {
|
|
962
|
+
dir: USER_STATE_DIR,
|
|
963
|
+
port: userPort,
|
|
964
|
+
tls,
|
|
965
|
+
tld,
|
|
966
|
+
lanMode: lanIp !== null || tld === "local",
|
|
967
|
+
lanIp
|
|
968
|
+
};
|
|
889
969
|
}
|
|
890
970
|
}
|
|
891
971
|
const systemPort = readPortFromDir(SYSTEM_STATE_DIR);
|
|
@@ -893,24 +973,36 @@ async function discoverState() {
|
|
|
893
973
|
if (await isProxyRunning(systemPort)) {
|
|
894
974
|
const tls = readTlsMarker(SYSTEM_STATE_DIR);
|
|
895
975
|
const tld = readTldFromDir(SYSTEM_STATE_DIR);
|
|
896
|
-
|
|
976
|
+
const lanIp = readLanMarker(SYSTEM_STATE_DIR);
|
|
977
|
+
return {
|
|
978
|
+
dir: SYSTEM_STATE_DIR,
|
|
979
|
+
port: systemPort,
|
|
980
|
+
tls,
|
|
981
|
+
tld,
|
|
982
|
+
lanMode: lanIp !== null || tld === "local",
|
|
983
|
+
lanIp
|
|
984
|
+
};
|
|
897
985
|
}
|
|
898
986
|
}
|
|
899
987
|
const configuredPort = getDefaultPort();
|
|
900
988
|
const probePorts = /* @__PURE__ */ new Set([443, 80, FALLBACK_PROXY_PORT, configuredPort]);
|
|
901
989
|
for (const port of probePorts) {
|
|
902
990
|
if (await isProxyRunning(port)) {
|
|
903
|
-
const
|
|
904
|
-
const tls = readTlsMarker(
|
|
905
|
-
const tld = readTldFromDir(
|
|
906
|
-
|
|
991
|
+
const dir2 = resolveStateDir(port);
|
|
992
|
+
const tls = readTlsMarker(dir2);
|
|
993
|
+
const tld = readTldFromDir(dir2);
|
|
994
|
+
const lanIp = readLanMarker(dir2);
|
|
995
|
+
return { dir: dir2, port, tls, tld, lanMode: lanIp !== null || tld === "local", lanIp };
|
|
907
996
|
}
|
|
908
997
|
}
|
|
998
|
+
const dir = resolveStateDir(configuredPort);
|
|
909
999
|
return {
|
|
910
|
-
dir
|
|
1000
|
+
dir,
|
|
911
1001
|
port: configuredPort,
|
|
912
1002
|
tls: false,
|
|
913
|
-
tld: getDefaultTld()
|
|
1003
|
+
tld: getDefaultTld(),
|
|
1004
|
+
lanMode: readLanMarker(dir) !== null,
|
|
1005
|
+
lanIp: null
|
|
914
1006
|
};
|
|
915
1007
|
}
|
|
916
1008
|
async function findFreePort(minPort = MIN_APP_PORT, maxPort = MAX_APP_PORT) {
|
|
@@ -964,6 +1056,22 @@ function isProxyRunning(port, tls = false) {
|
|
|
964
1056
|
req.end();
|
|
965
1057
|
});
|
|
966
1058
|
}
|
|
1059
|
+
function isPortListening(port) {
|
|
1060
|
+
return new Promise((resolve) => {
|
|
1061
|
+
const socket = net2.createConnection({ host: "127.0.0.1", port });
|
|
1062
|
+
let settled = false;
|
|
1063
|
+
const finish = (result) => {
|
|
1064
|
+
if (settled) return;
|
|
1065
|
+
settled = true;
|
|
1066
|
+
socket.destroy();
|
|
1067
|
+
resolve(result);
|
|
1068
|
+
};
|
|
1069
|
+
socket.setTimeout(SOCKET_TIMEOUT_MS);
|
|
1070
|
+
socket.once("connect", () => finish(true));
|
|
1071
|
+
socket.once("error", () => finish(false));
|
|
1072
|
+
socket.once("timeout", () => finish(false));
|
|
1073
|
+
});
|
|
1074
|
+
}
|
|
967
1075
|
function parsePidFromNetstat(output, port) {
|
|
968
1076
|
for (const line of output.split(/\r?\n/)) {
|
|
969
1077
|
if (!line.includes("LISTENING")) continue;
|
|
@@ -1091,6 +1199,7 @@ function spawnCommand(commandArgs, options) {
|
|
|
1091
1199
|
}
|
|
1092
1200
|
var FRAMEWORKS_NEEDING_PORT = {
|
|
1093
1201
|
vite: { strictPort: true },
|
|
1202
|
+
vp: { strictPort: true },
|
|
1094
1203
|
"react-router": { strictPort: true },
|
|
1095
1204
|
astro: { strictPort: false },
|
|
1096
1205
|
ng: { strictPort: false },
|
|
@@ -1136,6 +1245,8 @@ function injectFrameworkFlags(commandArgs, port) {
|
|
|
1136
1245
|
}
|
|
1137
1246
|
}
|
|
1138
1247
|
if (!commandArgs.includes("--host")) {
|
|
1248
|
+
const isExpoLan = basename2 === "expo" && isLanEnvEnabled();
|
|
1249
|
+
if (isExpoLan) return;
|
|
1139
1250
|
const hostValue = basename2 === "expo" ? "localhost" : "127.0.0.1";
|
|
1140
1251
|
commandArgs.push("--host", hostValue);
|
|
1141
1252
|
}
|
|
@@ -1371,22 +1482,30 @@ export {
|
|
|
1371
1482
|
isWindows2 as isWindows,
|
|
1372
1483
|
FALLBACK_PROXY_PORT,
|
|
1373
1484
|
PRIVILEGED_PORT_THRESHOLD,
|
|
1485
|
+
INTERNAL_LAN_IP_ENV,
|
|
1486
|
+
INTERNAL_LAN_IP_FLAG,
|
|
1374
1487
|
WAIT_FOR_PROXY_MAX_ATTEMPTS,
|
|
1375
1488
|
WAIT_FOR_PROXY_INTERVAL_MS,
|
|
1376
|
-
getProtocolPort,
|
|
1377
1489
|
getDefaultPort,
|
|
1378
1490
|
resolveStateDir,
|
|
1491
|
+
readTlsMarker,
|
|
1379
1492
|
writeTlsMarker,
|
|
1493
|
+
readLanMarker,
|
|
1494
|
+
writeLanMarker,
|
|
1380
1495
|
DEFAULT_TLD,
|
|
1381
1496
|
RISKY_TLDS,
|
|
1382
1497
|
validateTld,
|
|
1498
|
+
readTldFromDir,
|
|
1383
1499
|
writeTldFile,
|
|
1384
1500
|
getDefaultTld,
|
|
1385
1501
|
isHttpsEnvDisabled,
|
|
1386
1502
|
isWildcardEnvEnabled,
|
|
1503
|
+
isLanEnvEnabled,
|
|
1504
|
+
buildProxyStartConfig,
|
|
1387
1505
|
discoverState,
|
|
1388
1506
|
findFreePort,
|
|
1389
1507
|
isProxyRunning,
|
|
1508
|
+
isPortListening,
|
|
1390
1509
|
findPidOnPort,
|
|
1391
1510
|
waitForProxy,
|
|
1392
1511
|
spawnCommand,
|