portless 0.13.0 → 0.14.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 CHANGED
@@ -255,11 +255,16 @@ Install the proxy as an OS startup service so clean HTTPS URLs are available aft
255
255
 
256
256
  ```bash
257
257
  portless service install
258
+ portless service install --lan
259
+ portless service install --wildcard
260
+ PORTLESS_STATE_DIR=~/.portless-lan PORTLESS_LAN=1 portless service install
258
261
  portless service status
259
262
  portless service uninstall
260
263
  ```
261
264
 
262
- The service uses portless defaults: HTTPS on port 443 with `.localhost` names. macOS and Linux install a root-owned service so port 443 can bind at boot. Windows installs a Task Scheduler startup task that runs as SYSTEM. Installation and removal may require administrator privileges. `portless clean` automatically removes the service.
265
+ The service uses portless defaults unless install options or `PORTLESS_*` environment variables are provided: HTTPS on port 443 with `.localhost` names. `service install` accepts the proxy options you would use with `proxy start`, including `--port`, `--no-tls`, `--lan`, `--ip`, `--tld`, `--wildcard`, `--cert`, and `--key`. Use `--state-dir <path>` or `PORTLESS_STATE_DIR=<path>` to choose where service state and logs are written.
266
+
267
+ The chosen service configuration is written into launchd, systemd, or Task Scheduler and reused after reboot. `portless service status` reports the installed port, HTTPS mode, TLD, LAN mode, wildcard mode, and state directory. macOS and Linux install a root-owned service so port 443 can bind at boot. Windows installs a Task Scheduler startup task that runs as SYSTEM. Installation and removal may require administrator privileges. `portless clean` automatically removes the service.
263
268
 
264
269
  ## LAN mode
265
270
 
@@ -318,6 +323,20 @@ Set `PORTLESS_TAILSCALE=1` in your shell profile or `.env` to share every app by
318
323
 
319
324
  Requires the Tailscale CLI to be installed and connected (`tailscale up`), with Tailscale HTTPS certificates enabled.
320
325
 
326
+ ## ngrok sharing
327
+
328
+ Expose your dev server to the public internet with [ngrok](https://ngrok.com):
329
+
330
+ ```bash
331
+ portless myapp --ngrok next dev
332
+ # -> https://myapp.localhost (local)
333
+ # -> https://abc123.ngrok.app (public)
334
+ ```
335
+
336
+ Set `PORTLESS_NGROK=1` in your shell profile or `.env` to enable ngrok by default when portless runs an app. `portless list` shows both local and ngrok URLs. The ngrok tunnel is cleaned up automatically when the app exits.
337
+
338
+ Requires the ngrok CLI to be installed and authenticated. If ngrok reports an authentication error, run `ngrok config add-authtoken <token>` and try again.
339
+
321
340
  ## Commands
322
341
 
323
342
  ```bash
@@ -349,6 +368,8 @@ portless proxy stop # Stop the proxy
349
368
 
350
369
  # OS startup service
351
370
  portless service install # Start HTTPS proxy when the OS starts
371
+ portless service install --lan # Start service in LAN mode
372
+ portless service install --wildcard # Persist wildcard routing in the service
352
373
  portless service status # Show service and proxy status
353
374
  portless service uninstall # Remove the startup service
354
375
  ```
@@ -366,10 +387,12 @@ portless service uninstall # Remove the startup service
366
387
  --foreground Run proxy in foreground instead of daemon
367
388
  --tld <tld> Use a custom TLD instead of .localhost (e.g. test)
368
389
  --wildcard Allow unregistered subdomains to fall back to parent route
390
+ --state-dir <path> Use a custom state directory with service install
369
391
  --script <name> Run a specific package.json script (default: dev)
370
392
  --app-port <number> Use a fixed port for the app (skip auto-assignment)
371
393
  --tailscale Share the app on your Tailscale network (tailnet)
372
394
  --funnel Share the app publicly via Tailscale Funnel
395
+ --ngrok Share the app publicly via ngrok
373
396
  --force Kill the existing process and take over its route
374
397
  --name <name> Use <name> as the app name
375
398
  ```
@@ -382,11 +405,13 @@ PORTLESS_PORT=<number> Override the default proxy port
382
405
  PORTLESS_APP_PORT=<number> Use a fixed port for the app (same as --app-port)
383
406
  PORTLESS_HTTPS=0 Disable HTTPS (same as --no-tls)
384
407
  PORTLESS_LAN=1 Enable LAN mode when set to 1 (auto-detects LAN IP)
408
+ PORTLESS_LAN_IP=<address> Pin a specific LAN IP for LAN mode
385
409
  PORTLESS_TLD=<tld> Use a custom TLD (e.g. test; default: localhost)
386
410
  PORTLESS_WILDCARD=1 Allow unregistered subdomains to fall back to parent route
387
411
  PORTLESS_SYNC_HOSTS=0 Disable auto-sync of /etc/hosts (on by default)
388
412
  PORTLESS_TAILSCALE=1 Share apps on your Tailscale network (same as --tailscale)
389
413
  PORTLESS_FUNNEL=1 Share apps publicly via Tailscale Funnel (same as --funnel)
414
+ PORTLESS_NGROK=1 Share apps publicly via ngrok (same as --ngrok)
390
415
  PORTLESS_STATE_DIR=<path> Override the state directory
391
416
 
392
417
  # Injected into child processes
@@ -394,6 +419,7 @@ PORT Ephemeral port the child should listen on
394
419
  HOST Usually 127.0.0.1 (omitted for Expo in LAN mode)
395
420
  PORTLESS_URL Public URL (e.g. https://myapp.localhost)
396
421
  PORTLESS_TAILSCALE_URL Tailscale URL of the app (when --tailscale is active)
422
+ PORTLESS_NGROK_URL ngrok URL of the app (when --ngrok is active)
397
423
  NODE_EXTRA_CA_CERTS Path to the portless CA (when HTTPS is active)
398
424
  ```
399
425
 
@@ -460,6 +486,8 @@ Portless detects this misconfiguration and responds with `508 Loop Detected` alo
460
486
 
461
487
  This repo is a pnpm workspace monorepo using [Turborepo](https://turbo.build). The publishable package lives in `packages/portless/`.
462
488
 
489
+ Use Node.js 24+ and pnpm 11 for repository development. The `.node-version` file pins the Node major for version managers.
490
+
463
491
  ```bash
464
492
  pnpm install # Install all dependencies
465
493
  pnpm build # Build all packages
@@ -472,6 +500,7 @@ pnpm format # Format all files with Prettier
472
500
 
473
501
  ## Requirements
474
502
 
475
- - Node.js 20+
503
+ - Node.js 24+
476
504
  - macOS, Linux, or Windows
477
505
  - Tailscale CLI (optional, for `--tailscale` and `--funnel`)
506
+ - ngrok CLI (optional, for `--ngrok`)
@@ -920,10 +920,28 @@ var RouteStore = class _RouteStore {
920
920
  const routes = this.loadRoutes(true);
921
921
  const route = routes.find((r) => r.hostname === hostname);
922
922
  if (!route) return;
923
- if (fields.tailscaleUrl !== void 0) route.tailscaleUrl = fields.tailscaleUrl;
924
- if (fields.tailscaleHttpsPort !== void 0)
925
- route.tailscaleHttpsPort = fields.tailscaleHttpsPort;
926
- if (fields.tailscaleFunnel !== void 0) route.tailscaleFunnel = fields.tailscaleFunnel;
923
+ if ("tailscaleUrl" in fields) {
924
+ if (fields.tailscaleUrl === null) delete route.tailscaleUrl;
925
+ else if (fields.tailscaleUrl !== void 0) route.tailscaleUrl = fields.tailscaleUrl;
926
+ }
927
+ if ("tailscaleHttpsPort" in fields) {
928
+ if (fields.tailscaleHttpsPort === null) delete route.tailscaleHttpsPort;
929
+ else if (fields.tailscaleHttpsPort !== void 0)
930
+ route.tailscaleHttpsPort = fields.tailscaleHttpsPort;
931
+ }
932
+ if ("tailscaleFunnel" in fields) {
933
+ if (fields.tailscaleFunnel === null) delete route.tailscaleFunnel;
934
+ else if (fields.tailscaleFunnel !== void 0)
935
+ route.tailscaleFunnel = fields.tailscaleFunnel;
936
+ }
937
+ if ("ngrokUrl" in fields) {
938
+ if (fields.ngrokUrl === null) delete route.ngrokUrl;
939
+ else if (fields.ngrokUrl !== void 0) route.ngrokUrl = fields.ngrokUrl;
940
+ }
941
+ if ("ngrokPid" in fields) {
942
+ if (fields.ngrokPid === null) delete route.ngrokPid;
943
+ else if (fields.ngrokPid !== void 0) route.ngrokPid = fields.ngrokPid;
944
+ }
927
945
  this.saveRoutes(routes);
928
946
  } finally {
929
947
  this.releaseLock();