* 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
* 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
* 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.
* 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.
* 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
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.
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.
* 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
Replace the hardcoded "6k" star count with a server-side fetch from
the GitHub API, revalidated every 24 hours. Gracefully hides the
count if the fetch fails.
* 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
portless already works as a local dev dependency (state lives in
~/.portless, not relative to the install path, and there are no
postinstall hooks), but the docs said "don't do this" and the CLI
blocked npx/dlx even for locally-installed packages.
- Add isLocallyInstalled() helper that walks up from cwd looking for
node_modules/portless; the npx/dlx block now only fires for one-off
downloads, not project dependencies
- Update install sections in README, docs site, CLI --help, and agent
skill to show both global and project dev dependency options
- Note the pre-1.0 state-format caveat for per-project installs
Closes#135
The documentation lists only four frameworks that need --port/--host
injection (Vite, Astro, React Router, Angular), but the code in
FRAMEWORKS_NEEDING_PORT (cli-utils.ts) and the --help output both
include six: Expo and React Native were dropped during the README
rewrite in #93.
Document automatic worktree detection in portless run -- branch name
is prepended as a subdomain prefix so each worktree gets a unique URL
with no config changes. Added to README, SKILL.md, docs site getting
started page, and changelog.
* feat(docs): replace mobile pill nav with slide-in sheet drawer
Use a sticky trigger bar (shows current page title + list icon) that
opens a Radix Sheet from the left. Matches the webreel docs pattern.
The sidebar now hides below lg instead of md.
* style: fix prettier formatting in sheet.tsx