portless 0.7.2 → 0.9.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 +45 -50
- package/dist/{chunk-ROBZDJST.js → chunk-5BR7NCNI.js} +88 -37
- package/dist/cli.js +599 -392
- package/dist/index.d.ts +12 -1
- package/dist/index.js +3 -1
- package/package.json +13 -15
- package/LICENSE +0 -201
package/README.md
CHANGED
|
@@ -18,17 +18,12 @@ npm install -g portless
|
|
|
18
18
|
## Run your app
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
-
# Enable HTTPS (one-time setup, auto-generates certs)
|
|
22
|
-
portless proxy start --https
|
|
23
|
-
|
|
24
21
|
portless myapp next dev
|
|
25
22
|
# -> https://myapp.localhost
|
|
26
|
-
|
|
27
|
-
# Without --https, runs on port 1355
|
|
28
|
-
portless myapp next dev
|
|
29
|
-
# -> http://myapp.localhost:1355
|
|
30
23
|
```
|
|
31
24
|
|
|
25
|
+
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.
|
|
26
|
+
|
|
32
27
|
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` flags.
|
|
33
28
|
|
|
34
29
|
## Use in package.json
|
|
@@ -47,45 +42,45 @@ Organize services with subdomains:
|
|
|
47
42
|
|
|
48
43
|
```bash
|
|
49
44
|
portless api.myapp pnpm start
|
|
50
|
-
# ->
|
|
45
|
+
# -> https://api.myapp.localhost
|
|
51
46
|
|
|
52
47
|
portless docs.myapp next dev
|
|
53
|
-
# ->
|
|
48
|
+
# -> https://docs.myapp.localhost
|
|
54
49
|
```
|
|
55
50
|
|
|
56
|
-
|
|
51
|
+
By default, only explicitly registered subdomains are routed (strict mode). Use `--wildcard` when starting the proxy to allow any subdomain of a registered route to fall back to that app (e.g. `tenant1.myapp.localhost` routes to the `myapp` app without extra registration).
|
|
57
52
|
|
|
58
53
|
## Git Worktrees
|
|
59
54
|
|
|
60
55
|
`portless run` automatically detects git worktrees. In a linked worktree, the branch name is prepended as a subdomain so each worktree gets its own URL without any config changes:
|
|
61
56
|
|
|
62
57
|
```bash
|
|
63
|
-
# Main worktree
|
|
64
|
-
portless run next dev # ->
|
|
58
|
+
# Main worktree (no prefix)
|
|
59
|
+
portless run next dev # -> https://myapp.localhost
|
|
65
60
|
|
|
66
61
|
# Linked worktree on branch "fix-ui"
|
|
67
|
-
portless run next dev # ->
|
|
62
|
+
portless run next dev # -> https://fix-ui.myapp.localhost
|
|
68
63
|
```
|
|
69
64
|
|
|
70
65
|
Use `--name` to override the inferred base name while keeping the worktree prefix:
|
|
71
66
|
|
|
72
67
|
```bash
|
|
73
|
-
portless run --name myapp next dev # ->
|
|
68
|
+
portless run --name myapp next dev # -> https://fix-ui.myapp.localhost
|
|
74
69
|
```
|
|
75
70
|
|
|
76
|
-
Put `portless run` in your `package.json` once and it works everywhere
|
|
71
|
+
Put `portless run` in your `package.json` once and it works everywhere. The main checkout uses the plain name, each worktree gets a unique subdomain. No collisions, no `--force`.
|
|
77
72
|
|
|
78
73
|
## Custom TLD
|
|
79
74
|
|
|
80
75
|
By default, portless uses `.localhost` which auto-resolves to `127.0.0.1` in most browsers. If you prefer a different TLD (e.g. `.test`), use `--tld`:
|
|
81
76
|
|
|
82
77
|
```bash
|
|
83
|
-
|
|
78
|
+
portless proxy start --tld test
|
|
84
79
|
portless myapp next dev
|
|
85
80
|
# -> https://myapp.test
|
|
86
81
|
```
|
|
87
82
|
|
|
88
|
-
The proxy auto-syncs `/etc/hosts` for custom TLDs
|
|
83
|
+
The proxy auto-syncs `/etc/hosts` for custom TLDs, so `.test` domains resolve correctly.
|
|
89
84
|
|
|
90
85
|
Recommended: `.test` (IANA-reserved, no collision risk). Avoid `.local` (conflicts with mDNS/Bonjour) and `.dev` (Google-owned, forces HTTPS via HSTS).
|
|
91
86
|
|
|
@@ -93,40 +88,35 @@ Recommended: `.test` (IANA-reserved, no collision risk). Avoid `.local` (conflic
|
|
|
93
88
|
|
|
94
89
|
```mermaid
|
|
95
90
|
flowchart TD
|
|
96
|
-
Browser["Browser<br>myapp.localhost
|
|
97
|
-
Proxy["portless proxy<br>(port
|
|
91
|
+
Browser["Browser<br>myapp.localhost"]
|
|
92
|
+
Proxy["portless proxy<br>(port 80 or 443)"]
|
|
98
93
|
App1[":4123<br>myapp"]
|
|
99
94
|
App2[":4567<br>api"]
|
|
100
95
|
|
|
101
|
-
Browser
|
|
96
|
+
Browser --> Proxy
|
|
102
97
|
Proxy --> App1
|
|
103
98
|
Proxy --> App2
|
|
104
99
|
```
|
|
105
100
|
|
|
106
|
-
1. **Start the proxy
|
|
107
|
-
2. **Run apps
|
|
108
|
-
3. **Access via URL
|
|
101
|
+
1. **Start the proxy**: auto-starts when you run an app, or start explicitly with `portless proxy start`
|
|
102
|
+
2. **Run apps**: `portless <name> <command>` assigns a free port and registers with the proxy
|
|
103
|
+
3. **Access via URL**: `https://<name>.localhost` routes through the proxy to your app
|
|
109
104
|
|
|
110
105
|
## HTTP/2 + HTTPS
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
# Start with HTTPS/2 -- generates certs and trusts them automatically
|
|
116
|
-
portless proxy start --https
|
|
117
|
-
|
|
118
|
-
# First run prompts for sudo once to add the CA to your system trust store.
|
|
119
|
-
# After that, no prompts. No browser warnings.
|
|
107
|
+
HTTPS with HTTP/2 is enabled by default. Browsers limit HTTP/1.1 to 6 connections per host, which bottlenecks dev servers that serve many unbundled files (Vite, Nuxt, etc.). HTTP/2 multiplexes all requests over a single connection.
|
|
120
108
|
|
|
121
|
-
|
|
122
|
-
export PORTLESS_HTTPS=1
|
|
123
|
-
portless proxy start # HTTPS by default now
|
|
109
|
+
On first run, portless generates a local CA and adds it to your system trust store. No browser warnings. No manual setup.
|
|
124
110
|
|
|
111
|
+
```bash
|
|
125
112
|
# Use your own certs (e.g., from mkcert)
|
|
126
113
|
portless proxy start --cert ./cert.pem --key ./key.pem
|
|
127
114
|
|
|
128
|
-
#
|
|
129
|
-
|
|
115
|
+
# Disable HTTPS (plain HTTP on port 80)
|
|
116
|
+
portless proxy start --no-tls
|
|
117
|
+
|
|
118
|
+
# If you skipped the trust prompt on first run, trust the CA later
|
|
119
|
+
portless trust
|
|
130
120
|
```
|
|
131
121
|
|
|
132
122
|
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.
|
|
@@ -135,7 +125,7 @@ On Linux, `portless trust` supports Debian/Ubuntu, Arch, Fedora/RHEL/CentOS, and
|
|
|
135
125
|
|
|
136
126
|
```bash
|
|
137
127
|
portless run [--name <name>] <cmd> [args...] # Infer name (or override with --name), run through proxy
|
|
138
|
-
portless <name> <cmd> [args...] # Run app at
|
|
128
|
+
portless <name> <cmd> [args...] # Run app at https://<name>.localhost
|
|
139
129
|
portless alias <name> <port> # Register a static route (e.g. for Docker)
|
|
140
130
|
portless alias <name> <port> --force # Overwrite an existing route
|
|
141
131
|
portless alias --remove <name> # Remove a static route
|
|
@@ -148,23 +138,25 @@ portless hosts clean # Remove portless entries from /etc/hosts
|
|
|
148
138
|
PORTLESS=0 pnpm dev # Bypasses proxy, uses default port
|
|
149
139
|
|
|
150
140
|
# Proxy control
|
|
151
|
-
portless proxy start # Start the proxy (port
|
|
152
|
-
portless proxy start --
|
|
153
|
-
portless proxy start -p
|
|
141
|
+
portless proxy start # Start the HTTPS proxy (port 443, daemon)
|
|
142
|
+
portless proxy start --no-tls # Start without HTTPS (port 80)
|
|
143
|
+
portless proxy start -p 1355 # Start on a custom port (no sudo)
|
|
154
144
|
portless proxy start --foreground # Start in foreground (for debugging)
|
|
145
|
+
portless proxy start --wildcard # Allow unregistered subdomains to fall back to parent
|
|
155
146
|
portless proxy stop # Stop the proxy
|
|
156
147
|
```
|
|
157
148
|
|
|
158
149
|
### Options
|
|
159
150
|
|
|
160
151
|
```
|
|
161
|
-
-p, --port <number> Port for the proxy (default:
|
|
162
|
-
--
|
|
163
|
-
--
|
|
164
|
-
--
|
|
165
|
-
--
|
|
152
|
+
-p, --port <number> Port for the proxy (default: 443, or 80 with --no-tls)
|
|
153
|
+
--no-tls Disable HTTPS (use plain HTTP on port 80)
|
|
154
|
+
--https Enable HTTPS (default, accepted for compatibility)
|
|
155
|
+
--cert <path> Use a custom TLS certificate
|
|
156
|
+
--key <path> Use a custom TLS private key
|
|
166
157
|
--foreground Run proxy in foreground instead of daemon
|
|
167
158
|
--tld <tld> Use a custom TLD instead of .localhost (e.g. test)
|
|
159
|
+
--wildcard Allow unregistered subdomains to fall back to parent route
|
|
168
160
|
--app-port <number> Use a fixed port for the app (skip auto-assignment)
|
|
169
161
|
--force Override a route registered by another process
|
|
170
162
|
--name <name> Use <name> as the app name
|
|
@@ -176,8 +168,9 @@ portless proxy stop # Stop the proxy
|
|
|
176
168
|
# Configuration
|
|
177
169
|
PORTLESS_PORT=<number> Override the default proxy port
|
|
178
170
|
PORTLESS_APP_PORT=<number> Use a fixed port for the app (same as --app-port)
|
|
179
|
-
PORTLESS_HTTPS
|
|
171
|
+
PORTLESS_HTTPS HTTPS on by default; set to 0 to disable (same as --no-tls)
|
|
180
172
|
PORTLESS_TLD=<tld> Use a custom TLD (e.g. test; default: localhost)
|
|
173
|
+
PORTLESS_WILDCARD=1 Allow unregistered subdomains to fall back to parent route
|
|
181
174
|
PORTLESS_SYNC_HOSTS=1 Auto-sync /etc/hosts (auto-enabled for custom TLDs)
|
|
182
175
|
PORTLESS_STATE_DIR=<path> Override the state directory
|
|
183
176
|
|
|
@@ -196,8 +189,8 @@ PORTLESS_URL Public URL (e.g. https://myapp.localhost)
|
|
|
196
189
|
If Safari can't find your `.localhost` URL:
|
|
197
190
|
|
|
198
191
|
```bash
|
|
199
|
-
|
|
200
|
-
|
|
192
|
+
portless hosts sync # Add current routes to /etc/hosts
|
|
193
|
+
portless hosts clean # Clean up later
|
|
201
194
|
```
|
|
202
195
|
|
|
203
196
|
Auto-syncs `/etc/hosts` for custom TLDs (e.g. `--tld test`). For `.localhost`, set `PORTLESS_SYNC_HOSTS=1` to enable. Disable with `PORTLESS_SYNC_HOSTS=0`.
|
|
@@ -212,7 +205,7 @@ If your frontend dev server (e.g. Vite, webpack) proxies API requests to another
|
|
|
212
205
|
server: {
|
|
213
206
|
proxy: {
|
|
214
207
|
"/api": {
|
|
215
|
-
target: "
|
|
208
|
+
target: "https://api.myapp.localhost",
|
|
216
209
|
changeOrigin: true,
|
|
217
210
|
ws: true,
|
|
218
211
|
},
|
|
@@ -226,12 +219,14 @@ server: {
|
|
|
226
219
|
devServer: {
|
|
227
220
|
proxy: [{
|
|
228
221
|
context: ["/api"],
|
|
229
|
-
target: "
|
|
222
|
+
target: "https://api.myapp.localhost",
|
|
230
223
|
changeOrigin: true,
|
|
231
224
|
}],
|
|
232
225
|
}
|
|
233
226
|
```
|
|
234
227
|
|
|
228
|
+
If your tooling doesn't trust the portless CA, point Node.js at it: `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.
|
|
229
|
+
|
|
235
230
|
Portless detects this misconfiguration and responds with `508 Loop Detected` along with a message pointing to this fix.
|
|
236
231
|
|
|
237
232
|
## Development
|
|
@@ -286,6 +286,9 @@ function getRequestHost(req) {
|
|
|
286
286
|
if (typeof authority === "string" && authority) return authority;
|
|
287
287
|
return req.headers.host || "";
|
|
288
288
|
}
|
|
289
|
+
function isEncrypted(req) {
|
|
290
|
+
return !!req.socket.encrypted;
|
|
291
|
+
}
|
|
289
292
|
function buildForwardedHeaders(req, tls) {
|
|
290
293
|
const headers = {};
|
|
291
294
|
const remoteAddress = req.socket.remoteAddress || "127.0.0.1";
|
|
@@ -300,20 +303,21 @@ function buildForwardedHeaders(req, tls) {
|
|
|
300
303
|
}
|
|
301
304
|
var PORTLESS_HOPS_HEADER = "x-portless-hops";
|
|
302
305
|
var MAX_PROXY_HOPS = 5;
|
|
303
|
-
function findRoute(routes, host) {
|
|
304
|
-
return routes.find((r) => r.hostname === host) || routes.find((r) => host.endsWith("." + r.hostname));
|
|
306
|
+
function findRoute(routes, host, strict) {
|
|
307
|
+
return routes.find((r) => r.hostname === host) || (strict ? void 0 : routes.find((r) => host.endsWith("." + r.hostname)));
|
|
305
308
|
}
|
|
306
309
|
function createProxyServer(options) {
|
|
307
310
|
const {
|
|
308
311
|
getRoutes,
|
|
309
312
|
proxyPort,
|
|
310
313
|
tld = "localhost",
|
|
314
|
+
strict = true,
|
|
311
315
|
onError = (msg) => console.error(msg),
|
|
312
316
|
tls
|
|
313
317
|
} = options;
|
|
314
318
|
const tldSuffix = `.${tld}`;
|
|
315
|
-
const isTls = !!tls;
|
|
316
319
|
const handleRequest = (req, res) => {
|
|
320
|
+
const reqTls = isEncrypted(req);
|
|
317
321
|
res.setHeader(PORTLESS_HEADER, "1");
|
|
318
322
|
const routes = getRoutes();
|
|
319
323
|
const host = getRequestHost(req).split(":")[0];
|
|
@@ -334,7 +338,7 @@ function createProxyServer(options) {
|
|
|
334
338
|
"Loop Detected",
|
|
335
339
|
`<div class="content"><p class="desc">This request has passed through portless ${hops} times. This usually means a dev server (Vite, webpack, etc.) is proxying requests back through portless without rewriting the Host header.</p><div class="section"><p class="label">Fix: add changeOrigin to your proxy config</p><pre class="terminal">proxy: {
|
|
336
340
|
"/api": {
|
|
337
|
-
target: "http://<backend>${escapeHtml(tldSuffix)}:<port>",
|
|
341
|
+
target: "${reqTls ? "https" : "http"}://<backend>${escapeHtml(tldSuffix)}${reqTls ? "" : ":<port>"}",
|
|
338
342
|
changeOrigin: true,
|
|
339
343
|
},
|
|
340
344
|
}</pre></div></div>`
|
|
@@ -342,12 +346,12 @@ function createProxyServer(options) {
|
|
|
342
346
|
);
|
|
343
347
|
return;
|
|
344
348
|
}
|
|
345
|
-
const route = findRoute(routes, host);
|
|
349
|
+
const route = findRoute(routes, host, strict);
|
|
346
350
|
if (!route) {
|
|
347
351
|
const safeHost = escapeHtml(host);
|
|
348
352
|
const strippedHost = host.endsWith(tldSuffix) ? host.slice(0, -tldSuffix.length) : host;
|
|
349
353
|
const safeSuggestion = escapeHtml(strippedHost);
|
|
350
|
-
const routesList = routes.length > 0 ? `<div class="section"><p class="label">Active apps</p><ul class="card">${routes.map((r) => `<li><a href="${escapeHtml(formatUrl(r.hostname, proxyPort,
|
|
354
|
+
const routesList = routes.length > 0 ? `<div class="section"><p class="label">Active apps</p><ul class="card">${routes.map((r) => `<li><a href="${escapeHtml(formatUrl(r.hostname, proxyPort, reqTls))}" class="card-link"><span class="name">${escapeHtml(r.hostname)}</span><span class="meta"><code class="port">127.0.0.1:${escapeHtml(String(r.port))}</code><span class="arrow">${ARROW_SVG}</span></span></a></li>`).join("")}</ul></div>` : '<p class="empty">No apps running.</p>';
|
|
351
355
|
res.writeHead(404, { "Content-Type": "text/html" });
|
|
352
356
|
res.end(
|
|
353
357
|
renderPage(
|
|
@@ -358,7 +362,7 @@ function createProxyServer(options) {
|
|
|
358
362
|
);
|
|
359
363
|
return;
|
|
360
364
|
}
|
|
361
|
-
const forwardedHeaders = buildForwardedHeaders(req,
|
|
365
|
+
const forwardedHeaders = buildForwardedHeaders(req, reqTls);
|
|
362
366
|
const proxyReqHeaders = { ...req.headers };
|
|
363
367
|
for (const [key, value] of Object.entries(forwardedHeaders)) {
|
|
364
368
|
proxyReqHeaders[key] = value;
|
|
@@ -379,7 +383,7 @@ function createProxyServer(options) {
|
|
|
379
383
|
},
|
|
380
384
|
(proxyRes) => {
|
|
381
385
|
const responseHeaders = { ...proxyRes.headers };
|
|
382
|
-
if (
|
|
386
|
+
if (reqTls) {
|
|
383
387
|
for (const h of HOP_BY_HOP_HEADERS) {
|
|
384
388
|
delete responseHeaders[h];
|
|
385
389
|
}
|
|
@@ -436,12 +440,12 @@ function createProxyServer(options) {
|
|
|
436
440
|
}
|
|
437
441
|
const routes = getRoutes();
|
|
438
442
|
const host = getRequestHost(req).split(":")[0];
|
|
439
|
-
const route = findRoute(routes, host);
|
|
443
|
+
const route = findRoute(routes, host, strict);
|
|
440
444
|
if (!route) {
|
|
441
445
|
socket.destroy();
|
|
442
446
|
return;
|
|
443
447
|
}
|
|
444
|
-
const forwardedHeaders = buildForwardedHeaders(req,
|
|
448
|
+
const forwardedHeaders = buildForwardedHeaders(req, isEncrypted(req));
|
|
445
449
|
const proxyReqHeaders = { ...req.headers };
|
|
446
450
|
for (const [key, value] of Object.entries(forwardedHeaders)) {
|
|
447
451
|
proxyReqHeaders[key] = value;
|
|
@@ -512,8 +516,19 @@ function createProxyServer(options) {
|
|
|
512
516
|
h2Server.on("upgrade", (req, socket, head) => {
|
|
513
517
|
handleUpgrade(req, socket, head);
|
|
514
518
|
});
|
|
515
|
-
const plainServer = http.createServer(
|
|
516
|
-
|
|
519
|
+
const plainServer = http.createServer((req, res) => {
|
|
520
|
+
const host = getRequestHost(req).split(":")[0] || "localhost";
|
|
521
|
+
const location = `https://${host}${proxyPort === 443 ? "" : `:${proxyPort}`}${req.url || "/"}`;
|
|
522
|
+
res.writeHead(302, { Location: location, [PORTLESS_HEADER]: "1" });
|
|
523
|
+
res.end();
|
|
524
|
+
});
|
|
525
|
+
plainServer.on("upgrade", (req, socket) => {
|
|
526
|
+
const host = getRequestHost(req);
|
|
527
|
+
console.warn(
|
|
528
|
+
`[portless] Dropped plain-HTTP WebSocket upgrade for ${host}; use wss:// instead`
|
|
529
|
+
);
|
|
530
|
+
socket.destroy();
|
|
531
|
+
});
|
|
517
532
|
const wrapper = net.createServer((socket) => {
|
|
518
533
|
socket.on("error", () => {
|
|
519
534
|
socket.destroy();
|
|
@@ -544,6 +559,15 @@ function createProxyServer(options) {
|
|
|
544
559
|
httpServer.on("upgrade", handleUpgrade);
|
|
545
560
|
return httpServer;
|
|
546
561
|
}
|
|
562
|
+
function createHttpRedirectServer(httpsPort) {
|
|
563
|
+
return http.createServer((req, res) => {
|
|
564
|
+
const host = (req.headers.host || "localhost").split(":")[0];
|
|
565
|
+
const portSuffix = httpsPort === 443 ? "" : `:${httpsPort}`;
|
|
566
|
+
const location = `https://${host}${portSuffix}${req.url || "/"}`;
|
|
567
|
+
res.writeHead(302, { Location: location, [PORTLESS_HEADER]: "1" });
|
|
568
|
+
res.end();
|
|
569
|
+
});
|
|
570
|
+
}
|
|
547
571
|
|
|
548
572
|
// src/hosts.ts
|
|
549
573
|
import * as fs2 from "fs";
|
|
@@ -636,7 +660,7 @@ import * as path2 from "path";
|
|
|
636
660
|
import * as readline from "readline";
|
|
637
661
|
import { execSync, spawn } from "child_process";
|
|
638
662
|
var isWindows2 = process.platform === "win32";
|
|
639
|
-
var
|
|
663
|
+
var FALLBACK_PROXY_PORT = 1355;
|
|
640
664
|
var PRIVILEGED_PORT_THRESHOLD = 1024;
|
|
641
665
|
var SYSTEM_STATE_DIR = isWindows2 ? path2.join(os.tmpdir(), "portless") : "/tmp/portless";
|
|
642
666
|
var USER_STATE_DIR = path2.join(os.homedir(), ".portless");
|
|
@@ -655,13 +679,16 @@ var SIGNAL_CODES = {
|
|
|
655
679
|
SIGKILL: 9,
|
|
656
680
|
SIGTERM: 15
|
|
657
681
|
};
|
|
658
|
-
function
|
|
682
|
+
function getProtocolPort(tls) {
|
|
683
|
+
return tls ? 443 : 80;
|
|
684
|
+
}
|
|
685
|
+
function getDefaultPort(tls) {
|
|
659
686
|
const envPort = process.env.PORTLESS_PORT;
|
|
660
687
|
if (envPort) {
|
|
661
688
|
const port = parseInt(envPort, 10);
|
|
662
689
|
if (!isNaN(port) && port >= 1 && port <= 65535) return port;
|
|
663
690
|
}
|
|
664
|
-
return
|
|
691
|
+
return tls === void 0 ? FALLBACK_PROXY_PORT : getProtocolPort(tls);
|
|
665
692
|
}
|
|
666
693
|
function resolveStateDir(port) {
|
|
667
694
|
if (process.env.PORTLESS_STATE_DIR) return process.env.PORTLESS_STATE_DIR;
|
|
@@ -700,15 +727,15 @@ var DEFAULT_TLD = "localhost";
|
|
|
700
727
|
var RISKY_TLDS = /* @__PURE__ */ new Map([
|
|
701
728
|
["local", "conflicts with mDNS/Bonjour on macOS"],
|
|
702
729
|
["dev", "Google-owned; browsers force HTTPS via preloaded HSTS"],
|
|
703
|
-
["com", "public TLD
|
|
704
|
-
["org", "public TLD
|
|
705
|
-
["net", "public TLD
|
|
706
|
-
["io", "public TLD
|
|
707
|
-
["app", "public TLD
|
|
708
|
-
["edu", "public TLD
|
|
709
|
-
["gov", "public TLD
|
|
710
|
-
["mil", "public TLD
|
|
711
|
-
["int", "public TLD
|
|
730
|
+
["com", "public TLD; DNS requests will leak to the internet"],
|
|
731
|
+
["org", "public TLD; DNS requests will leak to the internet"],
|
|
732
|
+
["net", "public TLD; DNS requests will leak to the internet"],
|
|
733
|
+
["io", "public TLD; DNS requests will leak to the internet"],
|
|
734
|
+
["app", "public TLD; DNS requests will leak to the internet"],
|
|
735
|
+
["edu", "public TLD; DNS requests will leak to the internet"],
|
|
736
|
+
["gov", "public TLD; DNS requests will leak to the internet"],
|
|
737
|
+
["mil", "public TLD; DNS requests will leak to the internet"],
|
|
738
|
+
["int", "public TLD; DNS requests will leak to the internet"]
|
|
712
739
|
]);
|
|
713
740
|
function validateTld(tld) {
|
|
714
741
|
if (!tld) return "TLD cannot be empty";
|
|
@@ -744,8 +771,12 @@ function getDefaultTld() {
|
|
|
744
771
|
if (err) throw new Error(`PORTLESS_TLD: ${err}`);
|
|
745
772
|
return val;
|
|
746
773
|
}
|
|
747
|
-
function
|
|
774
|
+
function isHttpsEnvDisabled() {
|
|
748
775
|
const val = process.env.PORTLESS_HTTPS;
|
|
776
|
+
return val === "0" || val === "false";
|
|
777
|
+
}
|
|
778
|
+
function isWildcardEnvEnabled() {
|
|
779
|
+
const val = process.env.PORTLESS_WILDCARD;
|
|
749
780
|
return val === "1" || val === "true";
|
|
750
781
|
}
|
|
751
782
|
async function discoverState() {
|
|
@@ -772,8 +803,8 @@ async function discoverState() {
|
|
|
772
803
|
return { dir: SYSTEM_STATE_DIR, port: systemPort, tls, tld };
|
|
773
804
|
}
|
|
774
805
|
}
|
|
775
|
-
const
|
|
776
|
-
const probePorts = /* @__PURE__ */ new Set([
|
|
806
|
+
const configuredPort = getDefaultPort();
|
|
807
|
+
const probePorts = /* @__PURE__ */ new Set([443, 80, FALLBACK_PROXY_PORT, configuredPort]);
|
|
777
808
|
for (const port of probePorts) {
|
|
778
809
|
if (await isProxyRunning(port)) {
|
|
779
810
|
const dir = resolveStateDir(port);
|
|
@@ -782,7 +813,12 @@ async function discoverState() {
|
|
|
782
813
|
return { dir, port, tls, tld };
|
|
783
814
|
}
|
|
784
815
|
}
|
|
785
|
-
return {
|
|
816
|
+
return {
|
|
817
|
+
dir: resolveStateDir(configuredPort),
|
|
818
|
+
port: configuredPort,
|
|
819
|
+
tls: false,
|
|
820
|
+
tld: getDefaultTld()
|
|
821
|
+
};
|
|
786
822
|
}
|
|
787
823
|
async function findFreePort(minPort = MIN_APP_PORT, maxPort = MAX_APP_PORT) {
|
|
788
824
|
if (minPort > maxPort) {
|
|
@@ -905,11 +941,20 @@ function augmentedPath(env) {
|
|
|
905
941
|
return allBins.join(path2.delimiter) + path2.delimiter + base;
|
|
906
942
|
}
|
|
907
943
|
function spawnCommand(commandArgs, options) {
|
|
908
|
-
const env = {
|
|
909
|
-
|
|
944
|
+
const env = {
|
|
945
|
+
...options?.env ?? process.env,
|
|
946
|
+
PATH: augmentedPath(options?.env)
|
|
947
|
+
};
|
|
948
|
+
if (isWindows2) {
|
|
949
|
+
for (const key of Object.keys(env)) {
|
|
950
|
+
if (key !== "PATH" && key.toUpperCase() === "PATH") {
|
|
951
|
+
delete env[key];
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
const child = isWindows2 ? spawn("cmd.exe", ["/d", "/s", "/c", commandArgs.join(" ")], {
|
|
910
956
|
stdio: "inherit",
|
|
911
|
-
env
|
|
912
|
-
shell: true
|
|
957
|
+
env
|
|
913
958
|
}) : spawn("/bin/sh", ["-c", commandArgs.map(shellEscape).join(" ")], {
|
|
914
959
|
stdio: "inherit",
|
|
915
960
|
env
|
|
@@ -1079,7 +1124,8 @@ var RouteStore = class _RouteStore {
|
|
|
1079
1124
|
getRoutesPath() {
|
|
1080
1125
|
return this.routesPath;
|
|
1081
1126
|
}
|
|
1082
|
-
//
|
|
1127
|
+
// Locking
|
|
1128
|
+
// ---------------------------------------------------------------------------
|
|
1083
1129
|
static sleepBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
1084
1130
|
syncSleep(ms) {
|
|
1085
1131
|
Atomics.wait(_RouteStore.sleepBuffer, 0, 0, ms);
|
|
@@ -1114,7 +1160,8 @@ var RouteStore = class _RouteStore {
|
|
|
1114
1160
|
} catch {
|
|
1115
1161
|
}
|
|
1116
1162
|
}
|
|
1117
|
-
//
|
|
1163
|
+
// Route I/O
|
|
1164
|
+
// ---------------------------------------------------------------------------
|
|
1118
1165
|
isProcessAlive(pid) {
|
|
1119
1166
|
try {
|
|
1120
1167
|
process.kill(pid, 0);
|
|
@@ -1205,6 +1252,7 @@ export {
|
|
|
1205
1252
|
parseHostname,
|
|
1206
1253
|
PORTLESS_HEADER,
|
|
1207
1254
|
createProxyServer,
|
|
1255
|
+
createHttpRedirectServer,
|
|
1208
1256
|
extractManagedBlock,
|
|
1209
1257
|
removeBlock,
|
|
1210
1258
|
buildBlock,
|
|
@@ -1213,18 +1261,21 @@ export {
|
|
|
1213
1261
|
getManagedHostnames,
|
|
1214
1262
|
checkHostResolution,
|
|
1215
1263
|
isWindows2 as isWindows,
|
|
1264
|
+
FALLBACK_PROXY_PORT,
|
|
1216
1265
|
PRIVILEGED_PORT_THRESHOLD,
|
|
1266
|
+
WAIT_FOR_PROXY_MAX_ATTEMPTS,
|
|
1267
|
+
WAIT_FOR_PROXY_INTERVAL_MS,
|
|
1268
|
+
getProtocolPort,
|
|
1217
1269
|
getDefaultPort,
|
|
1218
1270
|
resolveStateDir,
|
|
1219
|
-
readTlsMarker,
|
|
1220
1271
|
writeTlsMarker,
|
|
1221
1272
|
DEFAULT_TLD,
|
|
1222
1273
|
RISKY_TLDS,
|
|
1223
1274
|
validateTld,
|
|
1224
|
-
readTldFromDir,
|
|
1225
1275
|
writeTldFile,
|
|
1226
1276
|
getDefaultTld,
|
|
1227
|
-
|
|
1277
|
+
isHttpsEnvDisabled,
|
|
1278
|
+
isWildcardEnvEnabled,
|
|
1228
1279
|
discoverState,
|
|
1229
1280
|
findFreePort,
|
|
1230
1281
|
isProxyRunning,
|