OpenSource-Hub

portless

CLI ツール

vercel-labs/portless

ポート番号の代わりに安定したローカルドメイン名を使用して開発を行う。

概要

Portless は、ランダムなポートの代わりに、ローカル開発サーバーに myapp.localhost のような安定した HTTPS ドメインを提供するコマンドラインツールです。プロキシを自動的に起動し、TLS 証明書の信頼を処理し、一般的なフレームワークにポート引数を注入します。サブドメイン、Monorepo、Turbo パイプライン、Git Worktree をサポートしています。

README プレビュー

# portless\n\nReplace port numbers with stable, named .localhost URLs for local development. For humans and agents.\n\n```diff\n- "dev": "next dev"                  # http://localhost:3000\n+ "dev": "portless run next dev"     # https://myapp.localhost\n```\n\n## Install\n\n**Global (recommended):**\n\n```bash\nnpm install -g portless\n```\n\n**Or as a project dev dependency:**\n\n```bash\nnpm install -D portless\n```\n\n> portless is pre-1.0. When installed per-project, different contributors may run different versions. The state directory format may change between releases, which can require re-running `portless trust`.\n\n## Run your app\n\n```bash\nportless myapp next dev\n# -> https://myapp.localhost\n```\n\nHTTPS 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.\n\nThe 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, VitePlus, Astro, React Router, Angular, Expo, React Native), portless auto-injects the right `--port` flag and, when needed, a matching `--host` flag. Injection reaches through a package script whose command starts with the framework or a known runner (`"dev": "vite"`, `"dev": "bunx vite"`). Only the framework's server commands get the flags (`dev`, `serve`, `preview`, `start`, a bare `vite`, or `vite [root]`); a command that does not serve, such as `vite build`, `vite optimize`, `vp test` or `astro check`, rejects them and is left alone. Expo connection modes (`--localhost`, `--lan`, `--tunnel`) are preserved while the assigned port is still injected. A script portless cannot classify is left alone too: a flag before the subcommand on a CLI whose flag grammar it does not track (`vp --mode dev build`). Portless also leaves a script alone w

FAQ (5)

トラブル対応
keep-alive リクエスト後にバックエンドが再起動した際に、Node.js プロキシでハンドルされない 'read ECONNRESET' クラッシュが発生する問題を修正するにはどうすればよいですか?

これは portless 0.10.3–0.15.6 に影響します。プロキシはクライアントの Connection: keep-alive ヘッダーをバックエンドに転送しますが、レスポンス完了後、バックエンドのソケットには agent も error リスナーもありません。バックエンドが再起動してソケットがリセットされると、Node はハンドルされていない ECONNRESET を発行して終了します。

http.request() を呼び出す前にホップバイホップヘッダーを削除して、通常の HTTP プロキシパスを修正します:

for (const name of String(proxyReqHeaders.connection || '').split(',')) {
  const header = name.trim().toLowerCase();
  if (header) delete proxyReqHeaders[header];
}

for (const header of HOP_BY_HOP_HEADERS) {
  delete proxyReqHeaders[header];
}

典型的なホップバイホップヘッダーは次のとおりです:

const HOP_BY_HOP_HEADERS = [
  'connection',
  'keep-alive',
  'proxy-authenticate',
  'proxy-authorization',
  'te',
  'trailer',
  'transfer-encoding',
  'upgrade',
];

これを WebSocket のアップグレードパスには適用しないでください。そちらは意図的に Connection: Upgrade を送信しており、すでにソケットの error リスナーをアタッチしています。

一時的な回避策として、createLoopbackConnection() が返すソケットに no-op のエラーハンドラーをアタッチします:

createConnection: () => {
  const socket = createLoopbackConnection(route.port);
  socket.on('error', () => {});
  return socket;
}

利用可能になったら、issue #434 の修正を含むリリースに portless をアップグレードしてください。

参照 Issue #434
トラブル対応
Portlessのワイルドカードサブドメインが間違った親アプリにルーティングされる問題を修正するにはどうすればよいですか?

PORTLESS_WILDCARD=1 を設定すると、Portless 0.15.3/0.15.5 はワイルドカードフォールバックに routes.find() を使用するため、最も具体的な親ではなく、routes.json 内で最初に一致した親が優先されます。findRoute を修正して、一致する親ホスト名のうち最も長いものを選択するようにします:

routes
  .filter((r) => hostname.endsWith("." + r.hostname.toLowerCase()))
  .sort((a, b) => b.hostname.length - a.hostname.length)[0]

たとえば、admin.api.acme.localhost は acme.localhost より先に api.acme.localhost にマッチするようになります。一時的な回避策としては、より具体的なルートを routes.json の先頭に配置できますが、これは順序に依存し、パッチよりも信頼性が低くなります。修正版リリースには「fix(proxy): pick the most specific parent route in wildcard fallback」というタイトルの PR が含まれるはずです。

参照 Issue #380
トラブル対応
Portless が /etc/hosts を上書きして localhost やその他のシステムエントリを削除してしまう問題を修正するには?

PR #424 を含む Portless リリースにアップグレードしてください(issue #419 を修正)。影響を受けるバージョンでは、/etc/hosts を読み取れなかった場合に readHostsFile() が空の文字列を返していたため、syncHostsFile() が Portless 管理のブロックのみを書き込んでいました。この修正により、最初の読み取りが失敗した場合に sync と cleanup が中断され、書き込み後の検証読み取りが失敗した場合に失敗を返すようになります。

破損した hosts ファイルを復元するには、管理者権限で編集してください:

sudo nano /etc/hosts

少なくとも標準的なループバックエントリーを復元してください。例:

127.0.0.1	localhost
127.0.0.1	myhost
::1		localhost ip6-localhost ip6-loopback
ff02::1		ip6-allnodes
ff02::2		ip6-allrouters

バックアップが存在する場合は、代わりにバックアップから復元してください:

sudo cp /etc/hosts.bak /etc/hosts

一時的な回避策として、/etc/hosts がロックされている、利用できない、または読み取れない可能性がある場合は、影響を受ける Portless バージョンの実行を避け、プロセスがファイルの読み取りと書き込みに必要な権限を持っていることを確認してください。

参照 Issue #419
トラブル対応
なぜ Portless は macOS 上で占有済みのポートを割り当て、レディネスチェックを誤ったプロセスにルーティングするのですか?

これは、Portless 0.12.0 が 127.0.0.1:<port> へのプロキシ中に、ホストを指定しない net.createServer().listen(port) バインドで空きポートをプローブすることが原因で発生します。macOS では、別のプロセスがすでに IPv4 ループバックでリッスンしている場合でも、このプローブが成功することがあります。

当面の回避策:
1. 残存しているリスナーを特定します:
lsof -nP -iTCP:<port> -sTCP:LISTEN
2. kill <PID> で停止するか、PORT=<free-port> で空きが確認できているポートを明示的に指定します。
3. 再起動する前に、両方のループバックスタックでポートが空いていることを確認します:
lsof -nP -iTCP@127.0.0.1:<port> -sTCP:LISTEN
lsof -nP -iTCP@[::1]:<port> -sTCP:LISTEN

恒久対策: issue #288 の修正を含む Portless リリースにアップグレードしてください(PR #441、#302 を基盤としています)。修正後のポートプローブは、ホストを指定しないバインドを使用する代わりに、127.0.0.1、::1、0.0.0.0 を含む関連するローカルアドレス上で候補ポートをチェックします。

参照 Issue #288
トラブル対応
portless が asdf、nvm、fnm、または mise の Node バージョンではなく Homebrew の Node 25 を使ってしまう問題を修正するにはどうすればよいですか?

これは、影響を受ける portless のバージョンが、子プロセスの PATH の先頭に path.dirname(process.execPath) を追加するために発生します。その結果、portless を実行している Node が、バージョンマネージャーで選択したバージョンよりも優先されてしまいます。

portless を issue #241 の修正を含むリリースにアップグレードしてください。この修正により、ユーザーの既存の PATH が portless 自身の Node ディレクトリよりも前に配置される(または Windows で必要な場合にのみそのディレクトリが挿入される)ため、バージョンマネージャーの shim が先に解決されるようになります。

brew upgrade portless
# または、管理下の Node で npm からインストールした場合:
npm update -g portless

portless myapp sh -c 'node --version'

アップグレードできない場合の一時的な回避策:Homebrew でインストールした portless を削除し、asdf/nvm/fnm/mise で管理している Node を使ってインストールしてください:

brew uninstall portless
nvm use 24          # または: fnm use 24
# asdf: asdf shell nodejs 24.13.0
# mise: mise use -g node@24
npm install -g portless
portless myapp sh -c 'node --version'

参照 Issue #241