#565

feat(run): allow repeating --name to serve one app on several hostnames

OpenGingerAdoniswants to merge intofrom

Closes #216

Summary

portless run --name can now be repeated so one dev server answers to several hostnames, for apps that pick a site from the Host header.

  • Each name goes through the same label truncation and worktree prefix or worktree.hostnameTemplate as a single --name, so {worktree}-{name} keeps every name one label deep.
  • Every name gets a route for each configured TLD, all pointing at the same app port.
  • The first name is the primary one: it sets PORTLESS_URL, is printed as the main -> URL, and receives any Tailscale, ngrok, or Cloudflare tunnel. The others are printed as also ->.
  • Identical names collapse into one. Distinct names that render to the same hostname, such as a template without {name}, fail before the proxy or the app starts.
  • portless <name>, portless --name, and the portless.json name field keep a single name.

Docs: run --help, portless --help, README, skills/portless/SKILL.md, and the commands, getting started, and configuration docs pages. run is already listed as omitted from the MCP server, so mcp.ts needs no change.

Example

With {"worktree":{"hostnameTemplate":"{worktree}-{name}"}} and PORTLESS_PREFIX=fix-ui:

$ portless run --name brand-a --name brand-b --name brand-c node -e 'console.log("PORTLESS_URL=" + process.env.PORTLESS_URL)'
-- fix-ui-brand-a.localhost, fix-ui-brand-b.localhost, fix-ui-brand-c.localhost (auto-resolves to 127.0.0.1)
-- Names "brand-a", "brand-b", "brand-c" (from --name flags)
-- Prefix "fix-ui" (from PORTLESS_PREFIX)
-- Using port 4653

  -> http://fix-ui-brand-a.localhost:19355

  also -> http://fix-ui-brand-b.localhost:19355
  also -> http://fix-ui-brand-c.localhost:19355

PORTLESS_URL=http://fix-ui-brand-a.localhost:19355

With a template that drops {name} ({worktree}-app):

$ portless run --name brand-a --name brand-b node -e 1
Error: Names "brand-a" and "brand-b" both resolve to "fix-ui-app.localhost". Use distinct names; if using worktree.hostnameTemplate, include {name}.

Testing

  • pnpm type-check and pnpm lint in packages/portless: pass.
  • New cli.test.ts cases: repeated names through a {worktree}-{name} template across two TLDs (routes, shared port, PORTLESS_URL, output, duplicate collapse), the collision error with no proxy, routes, or child started, help text, PORTLESS=0 passthrough, and a missing value on a repeated --name.
  • pnpm build && vitest run in packages/portless: 1217 passed, 1 failed. The failure is certs.test.ts > regenerates an existing CA whose Basic Constraints extension is duplicated (#429), which fails the same way on main locally because OpenSSL 4.1 refuses to parse the duplicated-extension fixture.
  • Manual: with a real proxy, curl to fix-ui-brand-a.localhost and fix-ui-brand-b.localhost both reached the same server with their own Host header.

Risks

runApp now takes a list of names. The default single-app and portless <name> paths pass a one-item list, so their behavior and output are unchanged.

Activity

2
vercel[bot]7d ago

@GingerAdonis is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.