portless 0.11.1 → 0.13.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
@@ -249,6 +249,18 @@ portless trust
249
249
 
250
250
  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.
251
251
 
252
+ ## Start at OS startup
253
+
254
+ Install the proxy as an OS startup service so clean HTTPS URLs are available after reboot without starting the proxy from a terminal:
255
+
256
+ ```bash
257
+ portless service install
258
+ portless service status
259
+ portless service uninstall
260
+ ```
261
+
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.
263
+
252
264
  ## LAN mode
253
265
 
254
266
  ```bash
@@ -276,6 +288,36 @@ LAN mode depends on the system mDNS tools that portless already spawns: macOS sh
276
288
 
277
289
  - **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`.
278
290
 
291
+ ## Tailscale sharing
292
+
293
+ Share your dev server with teammates on your [Tailscale](https://tailscale.com) network:
294
+
295
+ ```bash
296
+ portless myapp --tailscale next dev
297
+ # -> https://myapp.localhost (local)
298
+ # -> https://devbox.yourteam.ts.net (tailnet)
299
+ ```
300
+
301
+ Each `--tailscale` app is root-mounted on its own Tailscale HTTPS port, so no framework `basePath` configuration is needed. The first app gets port 443, subsequent apps get 8443, 8444, etc.
302
+
303
+ ```bash
304
+ portless myapp --tailscale next dev # -> https://devbox.ts.net
305
+ portless api --tailscale pnpm start # -> https://devbox.ts.net:8443
306
+ ```
307
+
308
+ Use `--funnel` to expose your dev server to the public internet via [Tailscale Funnel](https://tailscale.com/kb/1223/funnel/):
309
+
310
+ ```bash
311
+ portless myapp --funnel next dev
312
+ # -> https://devbox.yourteam.ts.net (public)
313
+ ```
314
+
315
+ Tailscale HTTPS certificates must be enabled before `--tailscale` or `--funnel` can register HTTPS URLs. Funnel must also be enabled for the tailnet and node before `--funnel` can register the public URL. If either setting is missing, portless exits before starting the child process.
316
+
317
+ Set `PORTLESS_TAILSCALE=1` in your shell profile or `.env` to share every app by default. `portless list` shows both local and tailnet URLs. Tailscale serve registrations are cleaned up automatically when the app exits.
318
+
319
+ Requires the Tailscale CLI to be installed and connected (`tailscale up`), with Tailscale HTTPS certificates enabled.
320
+
279
321
  ## Commands
280
322
 
281
323
  ```bash
@@ -304,6 +346,11 @@ portless proxy start -p 1355 # Start on a custom port (no sudo)
304
346
  portless proxy start --foreground # Start in foreground (for debugging)
305
347
  portless proxy start --wildcard # Allow unregistered subdomains to fall back to parent
306
348
  portless proxy stop # Stop the proxy
349
+
350
+ # OS startup service
351
+ portless service install # Start HTTPS proxy when the OS starts
352
+ portless service status # Show service and proxy status
353
+ portless service uninstall # Remove the startup service
307
354
  ```
308
355
 
309
356
  ### Options
@@ -321,6 +368,8 @@ portless proxy stop # Stop the proxy
321
368
  --wildcard Allow unregistered subdomains to fall back to parent route
322
369
  --script <name> Run a specific package.json script (default: dev)
323
370
  --app-port <number> Use a fixed port for the app (skip auto-assignment)
371
+ --tailscale Share the app on your Tailscale network (tailnet)
372
+ --funnel Share the app publicly via Tailscale Funnel
324
373
  --force Kill the existing process and take over its route
325
374
  --name <name> Use <name> as the app name
326
375
  ```
@@ -336,16 +385,19 @@ PORTLESS_LAN=1 Enable LAN mode when set to 1 (auto-detects LAN
336
385
  PORTLESS_TLD=<tld> Use a custom TLD (e.g. test; default: localhost)
337
386
  PORTLESS_WILDCARD=1 Allow unregistered subdomains to fall back to parent route
338
387
  PORTLESS_SYNC_HOSTS=0 Disable auto-sync of /etc/hosts (on by default)
388
+ PORTLESS_TAILSCALE=1 Share apps on your Tailscale network (same as --tailscale)
389
+ PORTLESS_FUNNEL=1 Share apps publicly via Tailscale Funnel (same as --funnel)
339
390
  PORTLESS_STATE_DIR=<path> Override the state directory
340
391
 
341
392
  # Injected into child processes
342
393
  PORT Ephemeral port the child should listen on
343
394
  HOST Usually 127.0.0.1 (omitted for Expo in LAN mode)
344
395
  PORTLESS_URL Public URL (e.g. https://myapp.localhost)
396
+ PORTLESS_TAILSCALE_URL Tailscale URL of the app (when --tailscale is active)
345
397
  NODE_EXTRA_CA_CERTS Path to the portless CA (when HTTPS is active)
346
398
  ```
347
399
 
348
- > **Reserved names:** `run`, `get`, `alias`, `hosts`, `list`, `trust`, `clean`, `prune`, 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.
400
+ > **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.
349
401
 
350
402
  ## Uninstall / reset
351
403
 
@@ -422,3 +474,4 @@ pnpm format # Format all files with Prettier
422
474
 
423
475
  - Node.js 20+
424
476
  - macOS, Linux, or Windows
477
+ - Tailscale CLI (optional, for `--tailscale` and `--funnel`)
@@ -846,7 +846,8 @@ var RouteStore = class _RouteStore {
846
846
  }
847
847
  }
848
848
  const filtered = routes.filter((r) => r.hostname !== hostname);
849
- filtered.push({ hostname, port, pid });
849
+ const entry = { hostname, port, pid };
850
+ filtered.push(entry);
850
851
  this.saveRoutes(filtered);
851
852
  } finally {
852
853
  this.releaseLock();
@@ -906,6 +907,28 @@ var RouteStore = class _RouteStore {
906
907
  this.releaseLock();
907
908
  }
908
909
  }
910
+ /**
911
+ * Update metadata on an existing route entry. Only provided fields are
912
+ * merged; the route must already exist (matched by hostname).
913
+ */
914
+ updateRoute(hostname, fields) {
915
+ this.ensureDir();
916
+ if (!this.acquireLock()) {
917
+ throw new Error("Failed to acquire route lock");
918
+ }
919
+ try {
920
+ const routes = this.loadRoutes(true);
921
+ const route = routes.find((r) => r.hostname === hostname);
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;
927
+ this.saveRoutes(routes);
928
+ } finally {
929
+ this.releaseLock();
930
+ }
931
+ }
909
932
  removeRoute(hostname) {
910
933
  this.ensureDir();
911
934
  if (!this.acquireLock()) {