mcpField
P
portless

portless

github.com/vercel-labs开发12.8k6.0k 次安装3 天前更新
代码仓库

关于 portless

portless 是一项开发技能,用稳定的命名 .localhost URL 取代本地 Web 应用变化的端口号。内容涵盖代理配置、Monorepo、Git worktree 和框架端口处理。

主要功能

  1. 01稳定的命名 .localhost URL
  2. 02HTTPS 反向代理
  3. 03发现 Monorepo 软件包
  4. 04为 Git worktree 分配独立 URL
  5. 05注入框架端口参数

适用场景

  1. 01同时运行多个本地应用以避免端口冲突
  2. 02按应用名称路由 Monorepo 服务
  3. 03为不同 Git worktree 保留独立 URL

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/vercel-labs/portless --skill portless

安装后在 Claude Code 中输入/portless确认已加载。

详细介绍

Portless

用稳定的命名 .localhost URL 替代端口号。适用于人类和 Agent。

为什么使用 portless

  • 端口冲突:两个项目默认使用同一端口时,会出现 EADDRINUSE
  • 记不住端口:哪个应用运行在 3001,哪个运行在 8080?
  • 刷新后显示错误的应用:停止一个服务,再在同一端口启动另一个服务,旧标签页会显示错误的内容
  • 单体仓库问题倍增:仓库中的每个服务都会让这些问题加剧
  • Agent 测试了错误的端口:AI Agent 会猜错或硬编码错误的端口
  • Cookie/存储冲突:localhost 上的 Cookie 会在应用间串用;端口变更后,localStorage 中的数据会丢失
  • 配置中硬编码的端口:CORS 允许列表、OAuth 重定向和 .env 文件会在端口变更后出错
  • 与队友共享 URL:“那个应用用的是什么端口?”成了 Slack 上的问题
  • 浏览器历史记录毫无用处:localhost:3000 的历史记录混杂着互不相关的项目

安装

全局安装(推荐),或将其作为项目开发依赖安装。不要使用 npx 或 pnpm dlx 临时执行。

# Global (available everywhere)
npm install -g portless

# Or per-project dev dependency
npm install -D portless

在项目内安装时,通过 package.json 脚本或 npx portless 调用(由于该软件包已在本地,npx 不会下载任何内容)。

快速入门

# Install globally (or add -D to a project)
npm install -g portless

# Run your app (auto-starts the HTTPS proxy on port 443)
portless run next dev
# -> https://<project>.localhost

# Or with an explicit name
portless myapp next dev
# -> https://myapp.localhost

运行应用时,代理会自动启动。你也可以通过 portless proxy start 显式启动。自动启动会沿用最近一次代理运行时的配置(端口、TLS、TLD),因此重启或重新启动计算机时不会悄悄恢复默认值。显式设置的环境变量始终优先。

在非交互环境中(没有 TTY,或 CI=1),portless 会显示描述性错误并退出,而不会提示用户。turborepo 等任务运行器应预先启动代理。

集成模式

零配置(推荐)

portless 开箱即用。它会通过代理运行 package.json 中的 "dev" 脚本,并从软件包名称、git 根目录或目录推断应用名称:

portless        # -> runs "dev" script, https://<project>.localhost
pnpm dev        # -> works without portless, plain "next dev"

可以使用可选的 portless.json 覆盖默认设置(名称、脚本、端口):

{ "name": "myapp" }
portless        # -> runs "dev" script, https://myapp.localhost
单体仓库

在仓库根目录放置一个 portless.json。Portless 会从 pnpm-workspace.yaml 或 package.json 中的 "workspaces" 字段(npm、yarn、bun)发现软件包:

{
  "apps": {
    "apps/web": { "name": "myapp" },
    "apps/api": { "name": "api.myapp" }
  }
}
portless                  # from repo root: start all packages with a "dev" script
cd apps/web && portless   # start just one package
portless --script start   # run "start" instead of "dev"

apps 映射可选,仅用于覆盖名称。未列出的软件包会自动发现,并使用推断出的名称。

没有 apps 映射时,主机名遵循 <package>.<project>.localhost 格式。项目名称取最常见的 npm 作用域(例如,@myorg/web 和 @myorg/api 会生成 myorg),如果没有则使用工作区根目录名称。如果某个软件包的简短名称与项目名称相同,则使用不带前缀的 <project>.localhost。

Turborepo

对于 turborepo 项目,将 portless 用作 dev 脚本,并将实际命令放在单独的脚本中:

{
  "scripts": { "dev": "portless", "dev:app": "next dev" },
  "portless": { "name": "myapp", "script": "dev:app" }
}

pnpm dev 会运行 turbo,而 turbo 会在每个软件包中运行 portless。Portless 会检测软件包管理器,并通过代理运行 pnpm run dev:app。

当 portless 从工作区根目录运行时,只要 turbo.json 或 turbo.jsonc 可读,它就会使用现有的 Turbo 集成来保留任务顺序。在根目录的 portless 配置中设置 "turbo": false,即可改用直接启动。

package.json 脚本

你仍然可以在脚本中直接使用 portless:

{
  "scripts": {
    "dev": "portless run next dev"
  }
}

运行应用时,代理会自动启动。也可以显式启动:portless proxy start。

使用子域名的多应用配置
portless myapp next dev          # https://myapp.localhost
portless api.myapp pnpm start    # https://api.myapp.localhost
portless docs.myapp next dev     # https://docs.myapp.localhost

默认情况下,只会路由显式注册的子域名(严格模式)。启动代理时加上 --wildcard,即可允许任何已注册路由的子域名回退到对应应用(例如,tenant1.myapp.localhost 会路由到 myapp 应用)。精确匹配始终优先于通配符。如果主机匹配多个已注册路由的父级,则使用最具体的路由(admin.api.myapp.localhost 会路由到 api.myapp,而不是 myapp)。

Git worktree

portless run 会自动检测 Git worktree。在关联的 worktree 中,分支名称会作为子域名前缀,因此每个 worktree 都有唯一的 URL:

# Main worktree (no prefix)
portless run next dev   # -> https://myapp.localhost

# Linked worktree on branch "fix-ui"
portless run next dev   # -> https://fix-ui.myapp.localhost

无需更改配置。在 package.json 中添加一次 portless run,即可在所有 worktree 中使用。

绕过 portless

设置 PORTLESS=0,即可绕过代理直接运行命令:

PORTLESS=0 pnpm dev   # Bypasses proxy, uses default port

使用 Ctrl+C 停止通过代理运行的命令时,portless 会等待其进程树退出。再次按下 Ctrl+C 会转发另一个中断信号,剩余的子进程会在短暂的宽限期后终止。

工作原理

  1. portless proxy start 会在端口 443 上启动 HTTPS 反向代理,并将其作为后台守护进程运行。在 macOS/Linux 上会自动提权使用 sudo;如果 sudo 不可用,则回退到端口 1355。使用 --no-tls 可在端口 80 上启用纯 HTTP。可通过 -p / --port 或 PORTLESS_PORT 环境变量进行配置。运行应用时,代理也会自动启动。
  2. portless <name> <cmd> 会通过 PORT 环境变量分配一个随机可用端口(4000-4999),并向代理注册应用。
  3. 浏览器访问 https://<name>.localhost;代理会将请求转发到分配给应用的端口。

在 LAN 模式之外,代理及其 HTTP 重定向监听器仅绑定到 IPv4 和 IPv6 回环地址 127.0.0.1 和 ::1。它们不会接受来自 LAN、VPN 或其他网络接口的连接。

当 portless 自身返回错误时(未知主机返回 404、请求目标错误返回 421、应用无法访问时返回 502、请求循环时返回 508),响应正文取决于 Accept 标头。浏览器(text/html)会收到 HTML 页面。curl、fetch 和其他客户端会收到 JSON,例如 {"error":"bad_gateway","upstream":"myapp.localhost","message":"..."},其中 error 的值为 not_found、misdirected_request、bad_gateway 或 loop_detected。请解析该 JSON,而不是抓取 HTML。应用自身的响应会原样转发。对于 portless TLD 之外的主机名,例如 DNS 屏蔽列表将其指向 127.0.0.1 的网站,会返回 421 Misdirected Request,而不是应用列表。

.localhost 域名在 Chrome、Firefox 和 Edge 中会原生解析到 127.0.0.1。Safari 依赖系统 DNS 解析器,某些配置下可能无法处理 .localhost 子域名。如有需要,运行 portless hosts sync 将条目添加到 /etc/hosts。

使用 portless proxy start --tld localhost --tld test,即可通过一个代理在多个 TLD 下提供相同的应用名称。PORTLESS_URL 使用第一个已配置的 TLD。当配置的 TLD 存在重叠(例如 example.com 和 dev.example.com)时,无论配置顺序如何,都会优先匹配最长的 TLD。PORTLESS_TLD 接受相同的逗号分隔列表格式,例如 PORTLESS_TLD=localhost,test。

TLD 可以是多段 DNS 名称,例如 dev.example.com,因此本地 URL 可以映射生产环境的结构(myapp.dev.example.com)。每个标签都必须符合 DNS 规则:小写字母、数字、内部连字符;每个标签最多 63 个字符,总长度最多 253 个字符。拒绝 .localhost 重定向 URI 的严格 OAuth 提供商,可以接受 https://myapp.dev.example.com/api/auth/callback/google 这样的真实域名。

大多数框架(Next.js、Express、Nuxt 等)都会自动遵循 PORT 环境变量。对于忽略 PORT 的框架(Vite、VitePlus、VitePress、Astro、React Router、Angular、Expo、React Native、Wrangler),portless 会自动注入正确的 --port 标志,并在需要时注入匹配的 --host CLI 标志(Wrangler 使用 --ip)。如果包脚本中的命令以框架或已知运行器开头("dev": "vite"、"dev": "bunx vite"),注入会继续向下传递。只有框架的服务器命令会获得这些标志(dev、serve、preview、start、单独的 vite 或 vite [root]);不提供服务的命令(如 vite build、vite optimize、vp test 或 astro check)不接受这些标志,因此不会修改。如果 Expo 连接模式使用 --localhost、--lan 或 --tunnel,这些模式会保留,同时仍会注入分配的端口。portless 无法分类的脚本也不会修改,例如,在 portless 不追踪其标志语法的 CLI 中,子命令之前带有标志的命令(vp --mode dev build)。如果向脚本追加标志不起作用,portless 也会保持脚本不变:复合命令(&&、|、;)、末尾带有 # 注释、自带 -- 选项终止符、带有环境变量前缀(NODE_ENV=production vite)、委托给另一个脚本("dev": "npm run dev:vite"),或运行器标志位于脚本名称之前(bun run --bun dev)。这些脚本会使用自身的端口,因此请在脚本中自行设置端口。

状态目录

Portless 将其状态(路由、PID 文件、端口文件)存储在 ~/.portless 中。代理在 sudo 下运行时,状态目录仍为调用者的主目录,因此非特权应用和代理可以共享路由注册信息。可通过环境变量 PORTLESS_STATE_DIR 覆盖此设置。

环境变量
变量描述
PORTLESS_PORT覆盖代理的默认端口(默认值:启用 HTTPS 时为 443,否则为 80)
PORTLESS_APP_PORT为应用指定固定端口(跳过自动分配)
PORTLESS_HTTPS默认启用 HTTPS;设为 0 可禁用(等同于 --no-tls)
PORTLESS_LAN设为 1 可始终启用 LAN 模式(自动检测 LAN IP)
PORTLESS_LAN_IP为 LAN 模式指定固定的 LAN IP
PORTLESS_TLD使用一个或多个 TLD,可为单段或多段(例如 localhost、dev.example.com)
PORTLESS_WILDCARD设为 1 可允许未注册的子域名回退到父域名
PORTLESS_SYNC_HOSTS设为 0 可禁用 /etc/hosts 的自动同步(默认启用)
PORTLESS_TAILSCALE设为 1 可在 Tailscale 网络上共享应用(等同于 --tailscale)
PORTLESS_FUNNEL设为 1 可通过 Tailscale Funnel 公开共享应用(等同于 --funnel)
PORTLESS_NGROK设为 1 可通过 ngrok 公开共享应用(等同于 --ngrok)
PORTLESS_CLOUDFLARE设为 1 可通过 Cloudflare 公开共享应用(等同于 --cloudflare)
PORTLESS_STATE_DIR覆盖状态目录
PORTLESS=0绕过代理,直接运行命令
HTTP/2 + HTTPS

默认启用带 HTTP/2 的 HTTPS(可加快包含许多文件的开发服务器页面加载速度)。WebSocket 可通过 HTTP/1.1(Upgrade)和 HTTP/2(RFC 8441 扩展 CONNECT)运行,因此开发服务器的 HMR 可通过代理工作。首次运行时会生成本地 CA,并将其添加到系统信任存储区。此后不会再出现提示或浏览器警告。

portless proxy start --cert ./c.pem --key ./k.pem  # Use custom certs
portless proxy start --no-tls                       # Disable HTTPS (plain HTTP)
portless trust                                      # Add CA to trust store later

在 Linux 上,portless trust 支持 Debian/Ubuntu、Arch、Fedora/RHEL/CentOS 和 openSUSE(通过 update-ca-certificates 或 update-ca-trust)。在 Windows 上,它使用 certutil 将 CA 添加到系统信任存储区。在 WSL 上,它会同时更新 Linux 信任存储区和 Windows 当前用户的 Root 存储区,以便 Windows 浏览器信任 portless HTTPS 证书。WSL2 主机上的 Linux 容器仅使用 Linux 信任存储区。

LAN 模式
portless proxy start --lan
portless proxy start --lan --https
portless proxy start --lan --ip 192.168.1.42

--lan 会显式将代理绑定到 IPv4 和 IPv6 未指定地址 0.0.0.0 和 ::,并通过 mDNS 广播 <name>.local 主机名,让同一 Wi-Fi 网络中的设备能够访问你的应用。Portless 会自动检测 LAN IP,并自动跟随网络变化;你也可以使用 --ip <address> 或环境变量 PORTLESS_LAN_IP 指定一个地址。设置 PORTLESS_LAN=1,即可让代理每次启动时默认进入 LAN 模式。

Portless 会通过 proxy.lan 记住 LAN 模式,因此停止 LAN 代理后再次启动时,它仍会保持 LAN 模式。除非显式标志或环境变量另有指定,所有代理设置(端口、TLS、TLD、LAN)都会持久保存,并在自动启动时沿用。使用 PORTLESS_LAN=0 可在某次启动时切回 .localhost 模式。如果代理已在运行,且其显式 LAN/TLS/TLD 设置不同,portless 会发出警告并要求你先停止代理。

LAN 模式依赖 portless 启动的系统 mDNS 辅助工具:macOS 内置 dns-sd;Linux 使用 avahi-utils 中的 avahi-publish-address(可通过 sudo apt install avahi-utils 或发行版提供的工具安装)。

  • Next.js:将你的 .local 主机名添加到 allowedDevOrigins:

    // next.config.js
    module.exports = {
      allowedDevOrigins: ["myapp.local", "*.myapp.local"],
    };
    
  • Expo / React Native:portless 始终会注入 --port。React Native 还会获得 --host 127.0.0.1。在 LAN 模式之外,Expo 会获得 --host localhost;而在 LAN 模式下,portless 会让 Metro 保持默认的 LAN 主机行为,不强制设置 --host 或 HOST。

Tailscale 共享

使用 --tailscale 与 Tailscale 网络中的队友共享开发服务器,或使用 --funnel 将其公开到互联网:

portless myapp --tailscale next dev
# -> https://myapp.localhost           (local)
# -> https://devbox.yourteam.ts.net    (tailnet)

portless myapp --funnel next dev
# -> https://myapp.localhost           (local)
# -> https://devbox.yourteam.ts.net    (public internet)

必须先启用 Tailscale HTTPS 证书,--tailscale 或 --funnel 才能注册 HTTPS URL。必须先为 tailnet 和节点启用 Funnel,--funnel 才能注册公共 URL。如果缺少任一设置,portless 会在启动子进程前退出。

每个 --tailscale 应用都会挂载在各自的 Tailscale HTTPS 端口根路径上(443、然后是 8443、8444 等),因此无需配置框架的 basePath。设置 PORTLESS_TAILSCALE=1,即可默认共享每个应用。portless list 会显示本地和 tailnet URL。应用退出时,会清理 Tailscale serve 注册项。需要安装并连接 tailscale CLI,且启用 Tailscale HTTPS 证书。

ngrok 共享

使用 --ngrok 将开发服务器公开到互联网:

portless myapp --ngrok next dev
# -> https://myapp.localhost           (local)
# -> https://abc123.ngrok.app          (public internet)

设置 PORTLESS_NGROK=1,即可在 portless 运行应用时默认启用 ngrok。portless list 会显示本地和 ngrok URL。应用退出时,会清理 ngrok 隧道。需要安装 ngrok CLI,并通过 ngrok config add-authtoken <token> 完成身份验证。

Cloudflare 共享
portless myapp --cloudflare next dev
# -> https://<random>.trycloudflare.com (public internet)

--cloudflare 会开启 Cloudflare 快速隧道;无需 Cloudflare 账户。设置 PORTLESS_CLOUDFLARE=1,即可默认启用。应用会获得 PORTLESS_CLOUDFLARE_URL,portless list 会显示一行 cloudflare:,应用退出时隧道会停止。它可与 --ngrok、--tailscale 和 --funnel 组合使用。快速隧道不支持 Server-Sent Events,最多允许 200 个进行中的请求,且每次运行都会获得一个新 URL。需要 PATH 中有 cloudflared CLI(https://developers.cloudflare.com/tunnel/downloads/)。

OS 启动服务

用户希望代理在重启后自动启动时,请使用 service 命令:

portless service install
portless service install --lan
portless service install --wildcard
PORTLESS_STATE_DIR=~/.portless-lan PORTLESS_LAN=1 portless service install
portless service status
portless service uninstall

除非提供安装选项或 PORTLESS_* 环境变量,否则该服务使用 portless 默认设置:通过 HTTPS 在端口 443 上运行,并使用 .localhost 名称。service install 接受代理选项,包括 --port、--no-tls、--lan、--ip、--tld、--wildcard、--cert 和 --key。使用 --state-dir <path> 或 PORTLESS_STATE_DIR=<path> 来选择服务状态和日志的写入位置。

所选服务配置会写入 launchd、systemd 或 Task Scheduler,并在重启后继续使用。portless service status 会报告已安装的端口、HTTPS 模式、TLD、LAN 模式、通配符模式和状态目录。macOS 和 Linux 会安装由 root 所有的服务,以便在启动时绑定端口 443。Windows 会安装一个以 SYSTEM 身份运行的 Task Scheduler 启动任务。安装和移除可能需要管理员权限。portless clean 会自动移除该服务。

CLI 参考

命令描述
portless通过代理运行开发脚本
portless从 monorepo 根目录运行:运行所有工作区包
portless --script <name>运行指定的 package.json 脚本(默认:dev)
portless run [cmd] [args...]根据项目推断名称,通过代理运行(自动启动)
portless run --name <name> <cmd>覆盖推断出的基础名称(仍会应用 worktree 前缀)
portless <name> <cmd> [args...]在 https://<name>.localhost 运行应用(自动启动代理)
portless get <name>输出服务的 URL(用于跨服务连接)
portless get <name> --no-worktree输出不带 worktree 前缀的 URL
portless list显示活动路由和运行时长
portless doctor检查代理、路由、DNS、CA 信任和 LAN 前置条件
portless trust将本地 CA 添加到系统信任存储(用于 HTTPS)
portless clean移除状态、CA 信任条目和 /etc/hosts 区块
portless prune终止孤立的开发服务器进程组;run 启动时也会执行此操作
portless prune --force使用 SIGKILL 而非 SIGTERM 终止孤立进程
portless proxy start将 HTTPS 代理作为守护进程启动(端口 443,自动提升权限)
portless proxy start --no-tls不启用 HTTPS 启动(在端口 80 上使用普通 HTTP)
portless proxy start --lan以 LAN 模式启动(mDNS .local,自动跟随 LAN IP 变化)
portless proxy start -p <number>在自定义端口启动代理
portless proxy start --tld test使用 .test 替代 .localhost
portless proxy start --tld localhost --tld test通过同一个代理提供这两个 TLD
portless proxy start --tld dev.example.com使用多段 TLD,提供与生产环境一致的 URL
portless proxy start --foreground在前台启动代理(用于调试)
portless proxy start --wildcard允许未注册的子域名回退到父路由
portless proxy stop停止代理
portless service install在操作系统启动时启动 HTTPS 代理
portless service install --lan以 LAN 模式启动服务
portless service install --wildcard在启动服务中持久启用通配符路由
portless service status显示服务和代理状态
portless service uninstall移除启动服务
portless alias <name> <port>注册静态路由(例如,用于 Docker 容器)
portless alias <name> <port> --force覆盖现有路由
portless alias --remove <name>移除静态路由
portless hosts sync将路由与 /etc/hosts 同步(修复 Safari 问题)
portless hosts clean从 /etc/hosts 移除 portless 条目
portless <name> --app-port <n> <cmd>为应用使用固定端口,而非自动分配
portless <name> --tailscale <cmd>在你的 Tailscale 网络(tailnet)上共享应用
portless <name> --funnel <cmd>通过 Tailscale Funnel 公开共享应用
portless <name> --ngrok <cmd>通过 ngrok 公开共享应用
portless <name> --cloudflare <cmd>通过 Cloudflare 快速隧道公开共享应用
portless <name> --force <cmd>终止现有进程并接管其路由
portless --name <name> <cmd>强制将 <name> 设为应用名称(绕过子命令分派)
portless <name> -- <cmd> [args...]停止解析标志;-- 之后的所有内容都会传递给子进程
portless --help / -h显示帮助
portless run --help显示子命令帮助(另有:alias、hosts、clean)
portless --version / -v显示版本
保留名称: run、get、alias、hosts、list、doctor、trust、clean、prune、proxy、service 和 mcp 都是子命令,不能直接用作应用名称。使用 portless run <cmd> 推断名称,或使用 portless --name <name> <cmd> 强制指定名称,包括保留名称。

MCP 服务

portless mcp [--tools <profiles>] 会启动一个 stdio MCP 服务。默认使用 core,提供路由列表、URL 查询、静态别名管理和配置发现功能。按需添加以逗号分隔的 proxy、diagnostics 或 all 配置。default 是 core 的别名,full 是 all 的别名。tools_profiles 工具会列出可用配置。共享工具参数为 cwd、extraArgs 和 timeoutMs;参数无效(未知或类型错误)以及调用未启用的工具时,都会返回一个 isError: true 的工具结果,说明如何修正,以便模型调整调用;未知参数会列出该工具自身的参数。MCP 不会运行开发服务器;请在 shell 中使用 portless run <cmd> 启动。

{ "mcpServers": { "portless": { "command": "portless", "args": ["mcp"] } } }

portless.json

可选配置文件。Portless 会在当前目录中查找此文件。

字段类型默认值描述
name字符串从 package.json 推断基础应用名称(仍会应用 worktree 前缀)
script字符串"dev"要运行的 package.json 脚本名称
appPort数字自动分配子进程使用的固定端口
proxy布尔值自动检测是否通过代理路由(任务模式下为 false)
apps对象工作区软件包的覆盖项,以相对路径为键
turbo布尔值true设为 false 可直接启动,而不使用 turborepo

每个 apps 条目都采用相同的结构(name、script、appPort、proxy)。如果存在 apps,顶层字段仅在单应用模式下生效。

package.json "portless" 键

除了使用单独的 portless.json,你也可以在 package.json 中添加 "portless" 键。字符串值是设置名称的简写:

{ "portless": "myapp" }

对象支持所有单应用字段(name、script、appPort、proxy):

{ "portless": { "name": "myapp", "script": "dev:app" } }

优先级(越接近应用,优先级越高):CLI 标志 > package.json "portless" 键 > portless.json 应用条目 > 默认值。

故障排除

运行诊断

当本地路由或 HTTPS 行为异常时,请先使用 portless doctor。它只读,并会检查 Node.js、状态目录权限、代理运行状态、路由条目、主机名解析、本地 CA 信任以及 LAN 模式的前置条件。

代理未运行

使用 portless <name> <cmd> 运行应用时,代理会自动启动。如果代理未能启动(例如端口冲突),请手动启动:

portless proxy start
端口已被占用

另一个进程正在使用代理端口。你可以先停止该进程,或使用其他端口:

portless proxy start -p 8080
框架未遵循 PORT

对于忽略 PORT 环境变量的框架,Portless 会自动注入正确的 --port 标志;必要时还会注入匹配的 --host 标志:Vite、VitePlus(vp)、VitePress、Astro、React Router、Angular、Expo、React Native 和 Wrangler(它使用 --ip 而不是 --host)。SvelteKit 内部使用 Vite,因此会自动处理。只有当包脚本的命令以框架名称或已知运行器开头,且运行的是框架的服务命令(dev、serve、preview、start 或单独的 vite)时,才会注入标志。vite build、vite optimize、vp test 等其他非服务命令不接受这些标志,因此不会修改;Portless 无法识别的调用(vp --mode dev build)也不会修改。对于复合命令(&&、|、;)、尾随的 # 注释、自带的 -- 选项终止符、环境变量前缀(NODE_ENV=production vite)、委托给其他脚本的命令,以及脚本名称前带有运行器标志的命令(bun run --bun dev),同样会跳过注入——这些情况下应用会继续使用自己的端口,并返回 502,因此请在脚本中自行设置端口。

对于其他不会读取 PORT 的框架,请手动传入端口:

  • Webpack Dev Server:使用 --port $PORT
  • 自定义服务:读取 process.env.PORT 并在该端口上监听
权限错误

默认端口(HTTP 为 80,HTTPS 为 443)在 macOS 和 Linux 上需要 sudo。必要时,Portless 会自动通过 sudo 提升权限。如果无法使用 sudo,则会回退到端口 1355(无需 sudo)。Windows 上无需提升权限。

portless proxy start --https           # Auto-elevates with sudo for port 443
portless proxy start -p 1355 --https   # No sudo needed (URLs include :1355)
portless proxy stop                    # Stop (use sudo if started with sudo)
Safari 找不到 .localhost URL

Safari 依赖系统 DNS 解析器来解析 .localhost 子域名,而某些 macOS 配置可能无法解析这些域名。Chrome、Firefox 和 Edge 内置了相应处理。

修复方法:

portless hosts sync    # Reconcile current routes with /etc/hosts
portless hosts clean   # Remove entries later

默认情况下,会自动同步 /etc/hosts 中的路由主机名。设置 PORTLESS_SYNC_HOSTS=0 可禁用此功能。如果路由主机名无法解析,注册该主机名的命令会发出警告,并提示你运行 portless hosts sync。

手动同步会将 Portless 管理的条目与当前路由同步;没有路由时会删除过期条目。写入前必须成功读取 hosts 文件,并会验证每次写入。读取或验证失败时,会按照常规同步错误流程处理。

使用 --https 时浏览器显示证书警告

本地 CA 可能尚未受信任。运行:

portless trust

这会将 Portless 本地 CA 添加到系统信任存储中。完成后,重启浏览器。

从机器中移除 Portless
portless clean

必要时停止代理,从信任存储中移除 Portless CA(如果是由 Portless 添加的),删除状态目录下的已知文件,并移除 Portless 在 /etc/hosts 中创建的区块。在 macOS/Linux 上可能需要 sudo。如果移除信任存储中的证书失败,Portless 会保留其 CA 证书和密钥,以便稍后安全地重试 portless clean。

代理循环(508 Loop Detected)

如果开发服务将请求代理到另一个 Portless 应用(例如,Vite 将 /api 代理到 api.myapp.localhost),代理必须重写 Host 标头。否则,Portless 会将请求路由回原始应用,从而造成无限循环。

修复方法:在代理配置(Vite、webpack-dev-server 等)中设置 changeOrigin: true:

// vite.config.ts
proxy: {
  "/api": {
    target: "https://api.myapp.localhost",
    changeOrigin: true,
    ws: true,
  },
}

Portless 会在子进程中自动设置 NODE_EXTRA_CA_CERTS,使 Node.js 信任 Portless CA。如果你在 Portless 外部运行单独的 Node.js 进程,请手动指定 CA:NODE_EXTRA_CA_CERTS=~/.portless/ca.pem。或者,使用 --no-tls 来启用纯 HTTP。

Tailscale 无法正常工作

如果 --tailscale 或 --funnel 失败:

tailscale status     # Check if connected
tailscale up         # Connect to your tailnet

需要安装 Tailscale CLI(https://tailscale.com/download),并确保它位于 PATH 中。

ngrok 无法正常工作

如果 --ngrok 失败:

ngrok version                         # Check if installed
ngrok config add-authtoken <token>    # Configure authentication

需要安装 ngrok CLI(https://ngrok.com/download),并确保它位于 PATH 中。

要求
  • Node.js 24+
  • macOS、Linux 或 Windows
  • openssl(用于生成 --https 证书;macOS 和大多数 Linux 发行版均自带;在 Windows 上,可通过 winget install -e --id ShiningLight.OpenSSL.Dev 安装,或使用 Git for Windows 附带的副本)
  • tailscale CLI(可选,用于 --tailscale 和 --funnel)
  • ngrok CLI(可选,用于 --ngrok)
  • cloudflared CLI(可选,用于 --cloudflare)

评论

支持 Markdown