Commit Graph

105 Commits

Author SHA1 Message Date
Railly Hugo 2013e0792f fix(proxy): dial upstreams over both loopback families
The proxy hardcoded 127.0.0.1 as the upstream address, so apps that
bind localhost on Node 17+ (which resolves verbatim and may yield ::1
only, e.g. Vite's default server.host) got ECONNREFUSED and a 502 on
every request. Dial with autoSelectFamily over a fixed loopback address
list in both the request and WebSocket upgrade paths, and use the same
connection helper in isPortListening so ::1-only apps are not reported
dead.

Fixes #320.
2026-07-09 21:31:46 -03:00
Chris Tate 5940d3cb88 Prepare v0.15.1 release (#345)
* Prepare v0.15.1 release

- Bump the portless package version to 0.15.1
- Add release notes for multi-TLD proxy support
- Move release markers to the latest changelog entry

* Fix Windows route lock contention

- Extend route lock acquisition under heavy contention
- Reduce retry backoff cap for more frequent lock checks
- Give the concurrent route test enough time on hosted Windows
2026-06-30 11:15:53 -05:00
Chris Tate 527578e155 Add multi-TLD proxy support (#344)
* Add multi-TLD proxy support

- Allow repeated --tld flags and comma separated PORTLESS_TLD values

- Register routes, service state, and TLS handling across configured TLDs

- Update docs, CLI help, and tests for multi-TLD behavior

* Fix multi-TLD workspace env handling
2026-06-30 10:58:42 -05:00
Chris Tate 8404575c30 Prepare v0.15.0 release (#338)
- Bump the portless package version to 0.15.0

- Add release notes for doctor diagnostics and proxy fixes

- Mirror the changelog entry in the docs site
2026-06-24 14:14:34 -05:00
Chris Tate a112baef25 feat: add portless doctor (#337)
* feat: add portless doctor

- Add read-only health diagnostics for proxy, routes, DNS, CA trust, and LAN prerequisites

- Document the doctor command in CLI help, README, docs, and the portless skill

- Cover doctor behavior with focused CLI tests

* fix: harden doctor diagnostics

* fix: handle custom certs in doctor

* Fix doctor diagnostics edge cases

* Fix doctor nested state directory check
2026-06-24 12:55:52 -05:00
Chris Tate e4f65fa715 Fix HTTP/2 Host forwarding and --force takeover race (#328)
- Restore Host from :authority when forwarding h2 requests to HTTP/1.1 backends; Host-dependent apps saw 127.0.0.1 for all browser traffic
- Ownership-check route removal during exit cleanup so a --force takeover no longer ends with the new owner's route deregistered (dead URL)
- Note proxy auto-start in --help; regression tests for both fixes
2026-06-10 11:28:13 -05:00
Chris Tate 8cf5440504 Prepare v0.14.0 release (#324)
- Bump portless package version to 0.14.0

- Add release notes for ngrok sharing and tunnel lifecycle

- Add matching docs changelog entry
2026-06-04 15:44:12 -05:00
Chris Tate cd0329dd84 Add ngrok sharing support (#323)
* Add ngrok sharing support

- Add an ngrok tunnel provider with CLI preflight, URL capture, and cleanup.

- Wire --ngrok, PORTLESS_NGROK, route metadata, and child URL env support.

- Document usage and cover behavior with provider, route, and CLI tests.

* Fix ngrok tunnel lifecycle
2026-06-04 14:49:51 -05:00
Chris Tate 551434f0e2 Prepare v0.13.1 release (#316)
* feat(service): support configurable startup services

* Persist service install proxy options across launchd, systemd, and Task Scheduler.
* Report installed service port, TLS, LAN, TLD, wildcard, and state directory in status.
* Document configurable service installs in CLI help, README, docs, and agent skill.

Co-authored-by: Houssam Elbadissi <35589610+skaldebane@users.noreply.github.com>
Co-authored-by: Romeo Scagliarini <22684678+ItalianScallian@users.noreply.github.com>
Co-authored-by: Neef Rehman <29544565+neefrehman@users.noreply.github.com>

* Fix service install config handling

* fix(service): reject unsupported LAN service installs

* fix(service): stop existing proxy on port changes

* fix windows ci failures

* Prepare v0.13.1 release

---------

Co-authored-by: Houssam Elbadissi <35589610+skaldebane@users.noreply.github.com>
Co-authored-by: Romeo Scagliarini <22684678+ItalianScallian@users.noreply.github.com>
Co-authored-by: Neef Rehman <29544565+neefrehman@users.noreply.github.com>
2026-05-27 18:04:28 -05:00
Chris Tate cd26f70a69 feat(service): support configurable startup services (#315)
* feat(service): support configurable startup services

* Persist service install proxy options across launchd, systemd, and Task Scheduler.
* Report installed service port, TLS, LAN, TLD, wildcard, and state directory in status.
* Document configurable service installs in CLI help, README, docs, and agent skill.

Co-authored-by: Houssam Elbadissi <35589610+skaldebane@users.noreply.github.com>
Co-authored-by: Romeo Scagliarini <22684678+ItalianScallian@users.noreply.github.com>
Co-authored-by: Neef Rehman <29544565+neefrehman@users.noreply.github.com>

* Fix service install config handling

* fix(service): reject unsupported LAN service installs

* fix(service): stop existing proxy on port changes

* fix windows ci failures

---------

Co-authored-by: Houssam Elbadissi <35589610+skaldebane@users.noreply.github.com>
Co-authored-by: Romeo Scagliarini <22684678+ItalianScallian@users.noreply.github.com>
Co-authored-by: Neef Rehman <29544565+neefrehman@users.noreply.github.com>
2026-05-27 17:48:34 -05:00
Chris Tate 974ae781cb chore: require pnpm release age policy (#307)
* chore: require pnpm release age policy

- Add pnpm minimum release age 2880 and deny dependency build scripts.

- Require Node 24 and pnpm 11 across engines, CI, and version files.

- Refresh docs and lockfile for the runtime upgrade.

* ci: install pnpm after node setup

- Put Node 24 on PATH before pnpm/action-setup installs pnpm 11.

- Keep setup-node cache restore after pnpm is installed.

* ci: run javascript actions on node 24

- Opt CI and release workflows into GitHub JavaScript action execution on Node 24.

- Unblock pnpm/action-setup from installing pnpm 11.

* ci: avoid engine strict pnpm setup

- Remove the root engine-strict npm config.

- Drop the temporary Node 24 JavaScript action runtime override.

* test: use valid ca pem fixture

- Replace the fake CA text used in NODE_EXTRA_CA_CERTS tests with a valid PEM.

- Avoid Windows Node 24 aborting before the capture script runs.
2026-05-20 16:51:31 -05:00
Chris Tate d564c2c097 Prepare v0.13.0 release (#290) 2026-05-07 19:17:39 -05:00
Chris Tate 402e19ddc8 Add OS startup service support (#289)
* Add OS startup service support

Install a native startup service for the HTTPS proxy so clean localhost URLs are available after reboot without repeated sudo prompts.

* Harden service support and integrate with portless clean

- Change requireUnixElevation return type from boolean to void
- Switch macOS KeepAlive to unconditional restart
- Wrap ensureCerts with actionable error message mentioning OpenSSL
- Escape percent signs in Windows batch env values
- Make portless clean uninstall the startup service before removing state
- Add tests for KeepAlive, percent escaping, and tryUninstallService
- Document that portless clean removes the service automatically

* Fix hardcoded task name in tryUninstallService and add handleService tests

Use spec.taskName instead of a literal string in the Windows branch of
tryUninstallService so the query stays consistent if the constant changes.

Add unit tests for handleService dispatch: help output, no-subcommand
help, and unknown subcommand error exit.

* Fix implicit any in service test mock.calls map callbacks

* Fix flaky tryUninstallService test on machines without service installed

Mock fs.existsSync so the runner is actually invoked regardless of
whether the service file exists on the test machine.

* Fix Windows CI failures in service tests

Use path.posix.join for macOS/Linux paths so they produce forward
slashes regardless of the build platform. Fix the "not installed" test
to use a non-zero runner status so it works on win32 (where the code
path checks schtasks query status rather than fs.existsSync).

* Fix startup service lifecycle ownership

Ensure service installation takes over any existing proxy and make clean elevate when protected service entries need removal.

* Fix service uninstall runner error test

* Fix service clean elevation and Windows PATH

Keep clean running in the original user context after elevated service removal, and preserve PATH for Windows startup tasks so runtime OpenSSL lookups work.
2026-05-07 19:02:08 -05:00
Anshuman Bhardwaj 524b3732ea fix: add preflight check for funnel on tailscale (#282) 2026-05-06 11:11:37 -05:00
Chris Tate 4522cf70c9 Prepare v0.12.0 release (#265)
Bump version to 0.12.0 and add changelog entries for Tailscale sharing
and Funnel support.
2026-04-29 11:05:17 -05:00
Chris Tate f8c7edce3c Add Tailscale sharing and Funnel support (#262)
* Add Tailscale sharing and Funnel support

Add --tailscale and --funnel flags that let any portless app be shared
over Tailscale with zero framework config. Each app is root-mounted on
its own Tailscale HTTPS port (443, 8443, 8444, ...) so no basePath
configuration is needed.

- New tailscale.ts module with readiness checks, port allocation,
  serve/funnel registration, and injectable TailscaleCommandRunner
  for testing
- --tailscale / --funnel flags work as global flags, per-app flags
  (portless myapp --tailscale next dev), and env vars
  (PORTLESS_TAILSCALE=1, PORTLESS_FUNNEL=1)
- Tailscale readiness is checked before proxy auto-start so failures
  are reported immediately with actionable messages
- portless list shows tailnet URLs alongside local URLs
- portless prune and portless clean remove stale tailscale serve
  registrations
- PORTLESS_TAILSCALE_URL injected into child process environment
- RouteMapping extended with optional tailscale metadata
- 41 new tests covering the tailscale module, CLI flags, and route
  metadata persistence

* fixes

* fixes
2026-04-28 20:54:45 -05:00
Chris Tate b4f89a5031 Prepare v0.11.1 release (#256)
* Fix zombie process orphaning on CLI crash

Spawn child processes with detached:true on Unix so they get their own
process group. Signal handlers now kill the entire group via
process.kill(-pid) instead of just the immediate child, preventing
orphaned dev servers from surviving CLI crashes or kill -9.

Add `portless prune` command as a safety net to find and kill orphaned
dev servers left behind by dead CLI sessions. It reads stale route
entries, checks if something is still listening on each port, and
terminates the orphan.

Add e2e tests covering both the SIGKILL cleanup path and the prune
command.

* Prepare v0.11.1 release
2026-04-28 01:19:14 -05:00
Chris Tate 8252927d7e Fix zombie process orphaning on CLI crash (#255)
Spawn child processes with detached:true on Unix so they get their own
process group. Signal handlers now kill the entire group via
process.kill(-pid) instead of just the immediate child, preventing
orphaned dev servers from surviving CLI crashes or kill -9.

Add `portless prune` command as a safety net to find and kill orphaned
dev servers left behind by dead CLI sessions. It reads stale route
entries, checks if something is still listening on each port, and
terminates the orphan.

Add e2e tests covering both the SIGKILL cleanup path and the prune
command.
2026-04-28 01:07:09 -05:00
Chris Tate 3dfcb3cb5a Prepare v0.11.0 release (#253)
Bump version to 0.11.0 and add changelog entries for config file
support, zero-arg mode, multi-app orchestration, turborepo
integration, Rsbuild support, and the state directory security fix.
2026-04-27 15:34:21 -05:00
Chris Tate a183e394a8 Config file support, zero-arg mode, and multi-app orchestration (#251)
* Add portless.json config file and zero-arg mode

Adds an optional portless.json for configuring app name, script, and
port without embedding portless in package.json scripts. Running bare
`portless` from a workspace root auto-discovers pnpm packages and
starts all dev scripts concurrently through the proxy. The --script
flag overrides the default "dev" script for a single invocation.

* Use npm scope as project base for multi-app names

In multi-app mode, package names are now composed as subdomains of the
npm scope (e.g., @og-sdk/docs becomes docs.og-sdk.localhost). Falls
back to inferring the project name from the workspace root when
packages are unscoped.

* Pass through scripts that already invoke portless

In multi-app mode, if a package's dev script starts with `portless`,
run it as-is without registering a route or injecting PORT/HOST. The
embedded portless invocation handles its own name and routing.

* Fix multi-app PATH and deduplicate project name

Augment PATH with node_modules/.bin from each package's directory so
local binaries (tsup, next, vite, etc.) are found. Skip the project
suffix when the package name already matches it (avoids names like
portless.portless).

* Fix proxy start prompting for trust on every launch

Cache the CA fingerprint in a marker file (ca.trusted) after a
successful trust so subsequent proxy starts skip the OS security
check and avoid re-triggering the macOS authentication dialog.
Also switch docs and example dev scripts to clean "next dev".

* Skip proxy route for build-only tools, support portless config in package.json

Non-server commands (tsup, tsc, esbuild, etc.) are auto-detected and
run without a proxy route. Adds "proxy" config field for explicit
control. Also supports a "portless" key in package.json — either as
an object or a string shorthand for the app name.

* Address PR review: config fixes, proxy auto-start, and refactors

- Rewrite splitCommand to respect quoted arguments
- Use frequency count for commonScope instead of first match
- Extract ensureProxyRunning helper; call from multi-app mode
- Remove loadConfig directory walk-up (check cwd only)
- Deduplicate clearTrustMarker call in generateCA
- Remove proxy field from config (system-level, not app-level)
- Fix early script filter to respect per-app overrides
- Add portless key to google-oauth example
- Replace invalid JSON comments with markdown headings in docs
- Extract spawnProxiedApp/spawnTaskApp helpers from handleDefaultMulti

* Address PR review: docs fixes, config precedence, proxy option

- Fix docs claiming config walks up directories (it does not)
- Clean up resolveAppConfig to use explicit field return
- Add JSDoc to isServerCommand explaining denylist design
- Add comment on sequential proxy spawning (port collision)
- Handle backslash escapes in splitCommand
- Remove non-null assertion on discovered proxy state
- Pass appConfig through to avoid redundant disk read
- Fix config precedence: package.json portless key wins (closest)
- Implement proxy: false config option for non-server scripts

* Add package manager delegation, turborepo support, and fix TLS state discovery

- Add detectPackageManager and resolveScriptCommand to delegate script
  execution to the correct package manager (pnpm/yarn/bun/npm) instead
  of parsing shell scripts directly. This enables reliable turborepo
  integration where `portless` is the dev script.
- Fix discoverState inferring tls=false when the proxy is running on
  port 443 but state files in /tmp were cleaned by the OS.
- Re-discover proxy state in multi-app mode when proxy is already running.
- Add turborepo integration docs and tests.

* Address PR 251 review: child lifecycle, shell safety, line buffering, proxy dedup, multi-PM workspaces, config errors

- Log exit codes per child and print summary in multi-app mode
- Spawn child processes directly without shell for PM-delegated commands
- Buffer partial lines in prefixed output streams, flush on end
- Extract resolveProxyDesiredState helper to deduplicate proxy config
- Support npm/yarn/bun workspaces via package.json "workspaces" field
- Replace process.exit in config validation with ConfigValidationError

* Address PR review: SIGKILL fallback, YAML flow sequences, glob wildcards, stream fixes

- Add 5s SIGKILL timeout after SIGTERM in multi-app cleanup to prevent
  hanging when children ignore SIGTERM
- Handle YAML flow sequence syntax (packages: [a, b]) in pnpm workspace
  parser, with JSDoc documenting the supported YAML subset
- Fall through from multi-app to single-app mode when no workspace
  packages have the target script, so workspace roots with their own
  portless.json still work
- Refactor expandSingleGlob into recursive expandSegments to support
  mid-path wildcards (packages/*/src) and prefix wildcards (apps/team-*)
- Use StringDecoder in prefixStream for correct multi-byte UTF-8 handling
  across chunk boundaries, and preserve empty lines from child output

* Eliminate /tmp state dir, fix symlink LPE, and clean up multi-app output

Move all proxy state from /tmp/portless to ~/.portless, fixing both the
macOS /tmp cleanup problem (repeated trust prompts) and the symlink LPE
vulnerability (GHSA-p67g-wf2j-g3h5). Add lstatSync symlink guard in
fixOwnership as defense-in-depth. Remove the interactive "Start proxy?"
prompt in favor of auto-starting. Rewrite multi-app startup output so
URLs stand out clearly. Also addresses PR 251 review items: re-throw
ConfigValidationError, fix splitCommand backslash handling in single
quotes, warn on unknown config keys, and remove unused export.

* Fix macOS CA cleanup when multiple certs share the same CN

Loop delete-certificate calls to remove all matching "portless Local CA"
entries from each keychain. The previous single call failed with
"is ambiguous, matches more than one certificate" when duplicate CAs
had accumulated from repeated trust prompts.

* Auto-trust CA after proxy auto-start

When the proxy is auto-started in single-app or multi-app mode and the
CA is not yet trusted, run handleTrust which has proper sudo elevation
logic. This prevents the scenario where a new user cancels the trust
dialog during proxy start and is left with browser cert errors and no
clear recovery path.

* Fix prettier formatting in configuration docs

* Use turbo for multi-app orchestration

When turbo.json is present, portless delegates to `turbo run <script>`
instead of spawning each app individually. Per-app PORT, HOST, and
PORTLESS_URL are injected via a lightweight --require loader so turbo
retains dependency ordering, output grouping, and task graph awareness.

Falls back to direct spawning when turbo is absent or when the config
sets `"turbo": false`.

* Fix turbo loader double-escape, cleanup guards, and direct-spawn route cleanup

- Remove redundant manual backslash escape in loaderSource; JSON.stringify
  already handles escaping for JS string literals
- Quote loader path in buildNodeOptions when it contains spaces
- Add cleanedUp guard to runWithTurbo and runWithDirectSpawn cleanup
  functions to prevent double execution on signal + explicit call
- Strip trailing \r in prefixStream so Windows \r\n output renders cleanly
- Track routes in runWithDirectSpawn and remove them in the signal handler,
  matching the pattern already used by runWithTurbo

* Fix doc gaps: state dir, CA path, turbo field, pkg portless key, hostname convention

- State directory docs still showed the old 3-tier table (port < 1024
  -> /tmp/portless). All state now lives in ~/.portless; updated
  SKILL.md and docs configuration page.
- CA path references in README and SKILL.md still mentioned
  /tmp/portless/ca.pem; simplified to ~/.portless/ca.pem.
- Added the `turbo` config field to all three config tables.
- Documented the package.json "portless" key as a standalone feature
  (was only shown in the Turborepo section).
- Documented the multi-app hostname convention
  (<package>.<project>.localhost derived from common npm scope).

* Make bare portless just work everywhere, monochrome output

- Remove config gates: bare `portless` and `portless run` resolve the
  dev script from package.json without requiring portless.json
- Extend PORTLESS=0 skip path to handle zero-arg mode
- Monochrome CLI output: bold for headers/errors, dim for warnings/muted,
  no color codes
- Multi-app display: show full package name (with scope), alphabetized,
  URLs dimmed
- Update docs, help text, and tests to reflect zero-config defaults

* Improve multi-app output and turbo integration

- Show full scoped package names in startup list, alphabetized
- Hide task-only packages from output (they still run)
- Use root package.json dev script for turbo when available,
  preserving user's flags like --concurrency and --filter
- Pass extra args after -- through to turbo

* Restore red/yellow ANSI colors for errors and warnings
2026-04-27 15:21:57 -05:00
Chris Tate adfe8d3a62 Add Rsbuild support (#244) (#250)
Rsbuild ignores PORT/HOST environment variables and requires --port and
--host CLI flags. Add rsbuild to the framework flag injection mapping so
portless can control which port and host Rsbuild binds to.
2026-04-26 01:03:41 -05:00
Chris Tate 0ddac0f995 Prepare v0.10.3 (#236) 2026-04-15 23:55:47 -05:00
Chris Tate 7e8a4945ea Fix stale TLD persisting across proxy restart (#235)
* Fix stale TLD persisting across proxy restart

proxy stop removed PID and port files but left proxy.tld, proxy.tls,
and proxy.lan on disk. On the next proxy start without flags,
discoverState read the stale proxy.lan marker, set persistedLanMode
to true, and defaultProxyConfig forced the TLD back to .local.

Clear all transient proxy state markers (TLD, TLS, LAN) in both the
daemon's SIGTERM cleanup handler and every success path in stopProxy.

* Clear state markers on corrupted PID file cleanup
2026-04-15 23:39:23 -05:00
Chris Tate aec0dd7e16 Prepare v0.10.2 release (#234) 2026-04-13 18:16:47 -05:00
Chris Tate 9afe81268c Fix CI failures on Node 20 and non-interactive auto-start (#233)
* Fix CI failures on Node 20 and non-interactive auto-start

Two fixes:

1. proxy.test.ts: Skip the stream reset cancellation test on Node < 22.11
   where streamResetBurst/streamResetRate options are not available (silently
   ignored, causing GOAWAY after ~1000 resets).

2. cli.ts: Restore non-interactive auto-start for unprivileged ports. PR #232
   changed the non-interactive guard to block all auto-start when stdin is not
   a TTY, but sudo is only needed for privileged ports. When sudo is not
   required, no user prompt is needed, so auto-start should proceed regardless
   of TTY status.

* Skip stream reset test on Windows

The rapid 1500-request burst overwhelms the test backend on Windows CI,
causing a 502 on the final alive-check request. The feature itself is
OS-independent; this is a test infrastructure limitation.
2026-04-13 18:04:56 -05:00
Chris Tate 82a3b93bae Fix proxy auto-start in non-interactive terminals and with previous config (#232)
- Non-interactive environments (no TTY, CI=1) now exit with a clear error instead of prompting, so task runners like turborepo fail early with manual start instructions (#224)
- Auto-start reads persisted state files (proxy.port, proxy.tls, proxy.tld, proxy.lan) so a reboot does not silently revert to default configuration; explicit env vars and flags still take priority (#225)
- Fix discoverState() fallback to read TLS/TLD markers from disk instead of hardcoding tls:false and the default TLD

Closes #224, closes #225
2026-04-13 17:25:30 -05:00
Chris Tate ed3aa18e7c Fix ERR_HTTP2_PROTOCOL_ERROR from stream reset flood during HMR (#231)
Browsers send rapid RST_STREAM CANCEL frames during Vite/Nuxt HMR and
page navigations. Node's default stream reset tolerance (~1000 cumulative
resets) causes GOAWAY INTERNAL_ERROR, killing the HTTP/2 session and
surfacing as ERR_HTTP2_PROTOCOL_ERROR in Chrome.

- Add streamResetBurst/streamResetRate to createSecureServer (Node 22.11+;
  silently ignored on older versions)
- Absorb session-level and per-stream errors from abrupt disconnects
- Use res.destroy() for mid-stream backend errors to send proper RST_STREAM
  instead of a content-length mismatch

Fixes #217. Fixes #221.
2026-04-13 17:20:54 -05:00
Chris Tate 203a3e0cf5 Fix lock contention when running parallel commands (#174) (#230)
* Fix lock contention when running parallel commands (#174)

Replace the fixed-delay retry strategy (20 retries * 50ms = 1s budget)
with exponential backoff and jitter (10ms-500ms delays, 5s budget).
The old approach caused "Failed to acquire route lock" errors in
monorepos running many portless commands in parallel.

* Fix Windows CI: use file URL for ESM import in worker script

On Windows, path.resolve produces backslash-separated paths which
Node.js ESM does not accept in import specifiers. Convert to a
file:// URL with pathToFileURL so the worker script loads correctly
on all platforms.

Also reorder assertions so lock failures surface before the ENOENT
on routes.json.
2026-04-13 17:14:40 -05:00
Chris Tate 01cba606a4 Fix proxy startup failure when macOS security command is slow (#228) (#229)
The macOS `security` CLI can hang or be very slow when the Keychain
Services daemon (`securityd`) is unresponsive, causing ETIMEDOUT errors
that cascade into a "Proxy failed to start" failure.

Three changes fix this:

1. Increase macOS `security` timeouts: verify-cert 5s to 15s,
   add-trusted-cert (with GUI dialog) 30s to 120s, root trust 30s to 60s.

2. Pass --skip-trust to the daemon process so it does not re-run the
   same trust flow the parent already attempted. This eliminates the
   double-timeout that caused waitForProxy to give up.

3. Return an actionable ETIMEDOUT error message that suggests restarting
   securityd instead of the raw "spawnSync security ETIMEDOUT".

Closes #228
2026-04-13 16:50:21 -05:00
Chris Tate 34c2c5dff3 Automatically inject NODE_EXTRA_CA_CERTS for child processes (#220)
* Automatically inject NODE_EXTRA_CA_CERTS for child processes

Fixes Node.js server-side fetches failing with "self-signed certificate in certificate chain" when making HTTPS requests to portless-proxied services.

## Problem
Node.js uses its own hardcoded CA bundle and ignores the system trust store. When server-side code (like Next.js Server Components) makes fetch requests to portless HTTPS URLs, Node.js rejects the portless CA as untrusted, even though browsers work fine.

## Changes
- Automatically inject `NODE_EXTRA_CA_CERTS` pointing to the portless CA cert when spawning child processes
- Only set when TLS is active and user hasn't already configured it
- Added comprehensive tests covering TLS enabled/disabled and user override scenarios
- Updated documentation to reflect automatic injection

## Implementation
The fix adds the CA cert path to the child process environment alongside existing variables like `PORT`, `HOST`, and `PORTLESS_URL`. This tells Node.js to trust the portless CA in addition to its built-in certificate bundle.

Fixes #218

* Fix CI: use script files for CA cert tests, show var in log line

- Write capture script to a file instead of using `node -e` with inline
  JS, fixing cmd.exe quoting failures on Windows
- Extract runWithMockProxy helper to deduplicate test setup
- Move caEnv computation before the Running: log line so
  NODE_EXTRA_CA_CERTS is visible in the status output

* Address review feedback on NODE_EXTRA_CA_CERTS injection

- Quote the CA path in the Running: log line to handle spaces
- Add test for ca.pem missing (exercises the fs.existsSync guard)
- Add comment noting process.env coupling for future-proofing
2026-04-11 08:55:57 -05:00
Chris Tate f9af69fbfb Prepare v0.10.1 release (#214) 2026-04-07 10:40:18 -05:00
Chris Tate 591ad871b0 feat: default hosts sync with opt-out; add portless clean (#213)
* auto-sync by default

* clean
2026-04-07 10:31:52 -05:00
Chris Tate b5a71d9490 Prepare v0.10.0 (#210)
* Prepare v0.10.0 release

* Remove old release markers, document the practice in AGENTS.md
2026-04-07 02:22:14 -05:00
Chris Arderne b958553bf2 Support auto-inject port for VitePlus (#147)
Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-04-07 01:58:05 -05:00
gabimoncha 4aa42753ab Enhance portless with local network --lan mode (#168)
* update expo injected args, implement --lan flag for portless using mDNS

* enhance expo docs and add --lan flag docs

* update skills and readme

* fix nextjs allowedDevOrigins example

* persist proxy config, follow LAN IP changes, and fix Expo host handling

* implement proxy.lan for sticky lan config

* implement PR review

* fix lan-ip.test.ts type check
2026-04-07 01:50:38 -05:00
Chris Tate 468d2d05e9 Prepare v0.9.6 release (#209) 2026-04-04 11:59:36 -05:00
Chris Tate d377707a32 Fix WebSocket proxy memory leak by adding socket close/end handlers (#208)
## Summary

Fixes a memory leak in the WebSocket proxy where sockets would not be properly cleaned up on normal disconnection, causing unbounded memory growth during development sessions with HMR or automated testing.

## Problem

The WebSocket upgrade handler only registered `error` event handlers on the socket pair. When WebSockets closed normally (browser refresh, HMR reconnect, tab close), Node.js fires `close` and `end` events rather than `error`. Without handlers for these events, both sockets would remain in memory indefinitely.

## Changes

- Added a shared `cleanup()` function that destroys both sockets in the pair
- Registered the cleanup function for `error`, `close`, and `end` events on both client and proxy sockets
- Used `destroy()` which is idempotent, making duplicate calls from multiple events safe

## Implementation Details

The fix covers all three socket disconnection scenarios:
- `error`: Socket failures (already handled)
- `close`: Full socket destruction 
- `end`: Normal EOF/FIN packets

This ensures proper cleanup regardless of how the WebSocket connection terminates.

Fixes #207
2026-04-04 11:53:44 -05:00
Chris Tate 2357a5da7a v0.9.5 (#205) 2026-04-04 09:19:41 -05:00
Chris Tate 26dbbe785e fix: --force kills the existing process before starting a new one (#204)
Previously `--force` only replaced the route entry on disk without
terminating the existing process, so the old server kept running and
held its port. Frameworks like Next.js would then refuse to start
("Another server is already running").

`addRoute()` now sends SIGTERM to the conflicting process when
`force` is true, and the CLI logs which PID was killed.

Fixes #201

Co-authored-by: ctate <366502+ctate@users.noreply.github.com>
2026-04-04 09:09:26 -05:00
Chris Tate 5ba016a56a Include CA certificate in TLS chain to fix UNABLE_TO_VERIFY_LEAF_SIGNATURE (#203)
Fixes TLS certificate verification errors in Node.js when making HTTPS requests to portless-proxied services. The error `UNABLE_TO_VERIFY_LEAF_SIGNATURE` occurred because the server was only sending the leaf certificate without the CA certificate in the chain.

## Changes Made

- **Modified `createSNICallback()`** in `certs.ts` to accept an optional `caCert` parameter and concatenate it with host certificates when creating secure contexts
- **Updated TLS options** in `cli.ts` to read and pass the CA certificate to the SNI callback
- **Enhanced proxy server** in `proxy.ts` to include CA certificate in the main server's certificate chain
- **Extended types** in `types.ts` to support the optional `ca` field in TLS options

The fix ensures that both the leaf certificate and CA certificate are sent during TLS handshakes, allowing Node.js clients to properly verify the certificate chain. This resolves issues with server-side `fetch()` calls in frameworks like Next.js and Nuxt.

Fixes #202
2026-04-04 08:58:32 -05:00
Chris Tate 3b4f907e5e prepare v0.9.4 release (#198) 2026-04-02 17:59:32 -05:00
Chris Tate 906061b71f Include README in published npm package (#197)
The published npm package was missing its README because `packages/portless/.gitignore` lists `README.md`. When no `.npmignore` exists, npm uses `.gitignore` as a denylist during packing, which excluded the README even though npm normally auto-includes it.

Adds a `.npmignore` so npm stops consulting `.gitignore` for packaging. The `.gitignore` entry stays so the copied README (from `prepublishOnly`) doesn't appear in `git status`.

Verified with `npm pack --dry-run` — README.md now appears in the tarball.
2026-04-02 17:52:21 -05:00
Chris Tate 2b964b7cf5 prepare v0.9.3 release (#196) 2026-04-02 17:37:36 -05:00
Chris Tate c66fec0753 remove origin rewrite (#195) 2026-04-02 17:31:06 -05:00
Chris Tate 73a30a6cd2 prepare v0.9.2 release (#193) 2026-04-01 19:01:12 -05:00
Chris Tate 1e1a58c6d5 fix(windows): auto-detect openssl.cnf for broken OPENSSLDIR builds (#183)
* fix(windows): auto-detect openssl.cnf to fix broken OPENSSLDIR on Windows

Some Windows OpenSSL builds (e.g. ShiningLight.OpenSSL.Dev) compile in a
non-existent OPENSSLDIR, causing "Can't open openssl.cnf" errors. Portless
now probes common install locations and injects OPENSSL_CONF into the
child-process environment when needed.

Also updates the error message to suggest winget install on Windows.

Fixes #181

* fix(windows-debug): add Git mingw64 bin to PATH in run.sh

OpenSSL ships inside Git's mingw64/bin directory, but run.sh only
included Git's cmd directory. Tests that invoke openssl failed with
ENOENT on the Windows debug instance.

* docs(skill): add Windows openssl install guidance to requirements
2026-04-01 18:42:05 -05:00
Chris Tate 42c227ca72 fix: exclude browser-blocked ports from auto-selection (#185) (#192)
- `findFreePort` can pick ports like 4045 (npp) or 4190 (sieve) that fall
  within the default 4000-4999 range but are blocked by browsers per the
  WHATWG fetch spec. Frameworks like Next.js also reject these, causing
  startup failures.
- Add the full WHATWG "bad port" list and skip those ports in both the
  random and sequential scan paths of `findFreePort`.

Closes #185
2026-04-01 18:38:39 -05:00
Chris Tate fa1a5a698b update domain (#191)
* update domain

* fix header
2026-04-01 18:27:34 -05:00
Chris Tate 847aa40d5a fix(trust): preserve state dir when elevating to sudo (#187)
* fix(trust): preserve state dir when elevating to sudo

Fixes CA certificate mismatch when `portless trust` auto-elevates with sudo.

## Problem
When `portless trust` required elevated privileges, the sudo subprocess would resolve a different state directory (e.g., `/root/.portless/` instead of `~/.portless/`) due to HOME being reset under sudo. This caused the system trust store to receive a different CA certificate than the one actually used by the proxy.

## Changes
- Modified the sudo spawn call in `handleTrust()` to explicitly pass `PORTLESS_STATE_DIR` pointing to the original resolved state directory
- Added `collectPortlessEnvArgs()` to forward other PORTLESS_* environment variables, matching the pattern used by other sudo elevations in the codebase
- Used `env` to preserve the environment context when running the elevated trust command

This ensures the sudo subprocess uses the same CA certificates that were already generated in the user's state directory.

Fixes #186

* format
2026-04-01 18:03:16 -05:00
Chris Tate 8592bbc1e9 Rewrite Origin/Referer headers in proxy for portless-managed hostnames (#189)
* Rewrite Origin/Referer headers in proxy for portless-managed hostnames

- Frameworks like Next.js 16.2+ block cross-origin requests to dev endpoints (e.g. /_next/webpack-hmr) when the Origin header doesn't match their allowlist. Next.js auto-allows *.localhost, but * only matches a single subdomain level, so multi-level names like admin.cmd.localhost are blocked.
- The proxy now rewrites Origin and Referer headers to http://localhost:{port} when the hostname belongs to the portless TLD. The original host is preserved in X-Forwarded-Host.
- This is framework-agnostic and works for both HTTP requests and WebSocket upgrades (HMR), so any framework with dev-origin checks benefits automatically.

* increase timeout
2026-04-01 17:49:02 -05:00