portless

portless

热门

设置并使用 portless,以命名的本地开发服务器 URL(例如 https://myapp.localhost)替代 http://localhost:3000。在将 portless 集成到项目中、配置开发服务器名称、设置本地代理、使用 .localhost 域名或排查端口/代理问题时使用。

1万Star
329Fork
更新于 2026/7/18
SKILL.md
readonly只读
name
portless
description

设置并使用 portless,以命名的本地开发服务器 URL(例如 https://myapp.localhost)替代 http://localhost:3000。在将 portless 集成到项目中、配置开发服务器名称、设置本地代理、使用 .localhost 域名或排查端口/代理问题时使用。

Portless

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

为什么选择 portless

  • 端口冲突:两个项目默认使用相同端口时出现 EADDRINUSE
  • 记忆端口号:哪个应用在 3001 还是 8080?
  • 刷新显示错误的应用:停止一个服务器,启动另一个同端口服务器,旧标签页显示错误内容
  • Monorepo 放大效应:每个问题随仓库中每个服务而放大
  • AI 代理测试错误端口:AI 代理猜测或硬编码错误端口
  • Cookie/存储冲突localhost 上的 Cookie 在应用间泄露;端口变化时 localStorage 丢失
  • 配置中硬编码端口:CORS 白名单、OAuth 重定向、.env 文件在端口变化时失效
  • 与队友共享 URL:“那个端口是多少?”成为 Slack 问题
  • 浏览器历史无用localhost:3000 的历史是无关项目的混合

安装

全局安装(推荐)或作为项目开发依赖安装。不要使用 npxpnpm dlx 一次性执行。

# 全局安装(随处可用)
npm install -g portless

# 或作为项目开发依赖
npm install -D portless

当作为项目依赖安装时,通过 package.json 脚本或 npx portless 调用(由于包在本地,npx 不会下载任何内容)。

快速开始

# 全局安装(或添加到项目 -D)
npm install -g portless

# 运行你的应用(自动启动 HTTPS 代理在 443 端口)
portless run next dev
# -> https://<project>.localhost

# 或使用显式名称
portless myapp next dev
# -> https://myapp.localhost

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

在非交互式环境(无 TTY,或 CI=1)中,portless 会退出并显示描述性错误,而不是提示。像 turborepo 这样的任务运行器应预先启动代理。

集成模式

零配置(推荐)

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

portless        # -> 运行 "dev" 脚本,https://<project>.localhost
pnpm dev        # -> 不使用 portless 也能工作,纯 "next dev"

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

{ "name": "myapp" }
portless        # -> 运行 "dev" 脚本,https://myapp.localhost

Monorepo

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

{
  "apps": {
    "apps/web": { "name": "myapp" },
    "apps/api": { "name": "api.myapp" }
  }
}
portless                  # 从仓库根目录:启动所有包含 "dev" 脚本的包
cd apps/web && portless   # 只启动一个包
portless --script start   # 运行 "start" 而不是 "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 通过代理。

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 应用)。精确匹配始终优先于通配符。

Git worktrees

portless run 自动检测 git worktrees。在链接的 worktree 中,分支名称作为子域名前缀添加,因此每个 worktree 获得唯一的 URL:

# 主 worktree(无前缀)
portless run next dev   # -> https://myapp.localhost

# 分支 "fix-ui" 上的链接 worktree
portless run next dev   # -> https://fix-ui.myapp.localhost

无需更改配置。将 portless run 放入 package.json 一次,即可在所有 worktrees 中工作。

绕过 portless

设置 PORTLESS=0 直接运行命令而不经过代理:

PORTLESS=0 pnpm dev   # 绕过代理,使用默认端口

工作原理

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

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

.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。PORTLESS_TLD 接受相同的逗号分隔列表格式,例如 PORTLESS_TLD=localhost,test

大多数框架(Next.js、Express、Nuxt 等)会自动尊重 PORT 环境变量。对于忽略 PORT 的框架(Vite、VitePlus、Astro、React Router、Angular、Expo、React Native),portless 会自动注入正确的 --port 标志,并在需要时注入匹配的 --host CLI 标志。

状态目录

Portless 将其状态(路由、PID 文件、端口文件)存储在 ~/.portless 中。当代理在 sudo 下运行时,这仍然是调用用户的主目录,因此非特权应用和代理共享路由注册。使用 PORTLESS_STATE_DIR 环境变量覆盖。

环境变量

变量 描述
PORTLESS_PORT 覆盖默认代理端口(默认:HTTPS 为 443,无 HTTPS 为 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,test)
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_STATE_DIR 覆盖状态目录
PORTLESS=0 绕过代理,直接运行命令

HTTP/2 + HTTPS

默认启用 HTTPS 和 HTTP/2(对于包含许多文件的开发服务器,页面加载更快)。WebSocket 通过 HTTP/1.1(Upgrade)和 HTTP/2(RFC 8441 扩展 CONNECT)均能工作,因此开发服务器 HMR 可以通过代理工作。首次运行会生成本地 CA 并将其添加到系统信任存储。之后,没有提示,也没有浏览器警告。

portless proxy start --cert ./c.pem --key ./k.pem  # 使用自定义证书
portless proxy start --no-tls                       # 禁用 HTTPS(纯 HTTP)
portless trust                                      # 稍后添加 CA 到信任存储

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

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-publish-address(来自 avahi-utils,通过 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。Expo 在非 LAN 模式下获得 --host localhost,但在 LAN 模式下,portless 让 Metro 保持其默认的 LAN 主机行为,而不是强制 --hostHOST

Tailscale 共享

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

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

portless myapp --funnel next dev
# -> https://myapp.localhost           (本地)
# -> https://devbox.yourteam.ts.net    (公共互联网)

--tailscale--funnel 注册 HTTPS URL 之前,必须启用 Tailscale HTTPS 证书。Funnel 还必须为 tailnet 和节点启用,然后 --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 通过 ngrok 将开发服务器暴露到公共互联网:

portless myapp --ngrok next dev
# -> https://myapp.localhost           (本地)
# -> https://abc123.ngrok.app          (公共互联网)

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

操作系统启动服务

当用户希望代理在重启后自动启动时,使用 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 通过代理运行 dev 脚本
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 终止崩溃会话中的孤立开发服务器
portless prune --force 使用 SIGKILL 而不是 SIGTERM 终止孤立进程
portless proxy start 启动 HTTPS 代理作为守护进程(端口 443,自动提权)
portless proxy start --no-tls 无 HTTPS 启动(纯 HTTP 在端口 80)
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 --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> --force <cmd> 终止现有进程并接管其路由
portless --name <name> <cmd> 强制 <name> 作为应用名称(绕过子命令分发)
portless <name> -- <cmd> [args...] 停止标志解析;-- 之后的所有内容传递给子进程
portless --help / -h 显示帮助
portless run --help 显示子命令的帮助(也适用于:alias、hosts、clean)
portless --version / -v 显示版本

保留名称: rungetaliashostslistdoctortrustcleanpruneproxyservice 是子命令,不能直接用作应用名称。使用 portless run <cmd> 推断名称,或使用 portless --name <name> <cmd> 强制任何名称,包括保留名称。

portless.json

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

字段 类型 默认值 描述
name string 从 package.json 推断 基础应用名称(worktree 前缀仍然适用)
script string "dev" 要运行的 package.json 脚本名称
appPort number 自动分配 子进程的固定端口
proxy boolean 自动检测 是否通过代理路由(false 用于任务)
apps object 工作区包的覆盖,键为相对路径
turbo boolean true 设置为 false 使用直接生成而不是 turborepo

每个 apps 条目具有相同结构(namescriptappPortproxy)。当存在 apps 时,顶级字段仅适用于单应用模式。

package.json 中的 "portless" 键

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

{ "portless": "myapp" }

对象支持所有每个应用的字段(namescriptappPortproxy):

{ "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 标志:ViteVitePlus (vp)、AstroReact RouterAngularExpoReact Native。SvelteKit 内部使用 Vite,会自动处理。

对于其他不读取 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           # 自动使用 sudo 提权以使用端口 443
portless proxy start -p 1355 --https   # 无需 sudo(URL 包含 :1355)
portless proxy stop                    # 停止(如果使用 sudo 启动,则使用 sudo)

Safari 无法找到 .localhost URL

Safari 依赖系统 DNS 解析器处理 .localhost 子域名,在某些 macOS 配置上可能无法解析。Chrome、Firefox 和 Edge 有内置处理。

修复:

portless hosts sync    # 将当前路由添加到 /etc/hosts
portless hosts clean   # 稍后移除条目

默认情况下,自动同步 /etc/hosts 以包含路由主机名。设置 PORTLESS_SYNC_HOSTS=0 禁用。

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

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

portless trust

这会将 portless 本地 CA 添加到系统信任存储。之后,重启浏览器。

从机器中移除 portless

portless clean

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

代理循环(508 循环检测)

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

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

// 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     # 检查是否已连接
tailscale up         # 连接到你的 tailnet

需要安装 Tailscale CLI(https://tailscale.com/download)并在 PATH 中。

ngrok 不工作

如果 --ngrok 失败:

ngrok version                         # 检查是否已安装
ngrok config add-authtoken <token>    # 配置身份验证

需要安装 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