[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-74759":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":15,"contributorsCount":16,"subscribersCount":16,"size":16,"stars1d":17,"stars7d":18,"stars30d":19,"stars90d":16,"forks30d":16,"starsTrendScore":20,"compositeScore":21,"rankGlobal":10,"rankLanguage":10,"license":22,"archived":23,"fork":23,"defaultBranch":24,"hasWiki":25,"hasPages":23,"topics":26,"createdAt":10,"pushedAt":10,"updatedAt":27,"readmeContent":28,"aiSummary":29,"trendingCount":16,"starSnapshotCount":16,"syncStatus":30,"lastSyncTime":31,"discoverSource":32},74759,"portless","vercel-labs\u002Fportless","vercel-labs","Replace port numbers with stable, named local URLs. For humans and agents.","https:\u002F\u002Fportless.sh",null,"TypeScript",9716,307,22,59,0,63,130,462,189,38.47,"Apache License 2.0",false,"main",true,[],"2026-06-12 02:03:27","# 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:\u002F\u002Flocalhost:3000\n+ \"dev\": \"portless run next dev\"     # https:\u002F\u002Fmyapp.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:\u002F\u002Fmyapp.localhost\n```\n\nHTTPS with HTTP\u002F2 is enabled by default. On first run, portless generates a local CA, trusts it, and binds port 443 (auto-elevates with sudo on macOS\u002FLinux). 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.\n\nWhen auto-starting, portless reuses the configuration (port, TLS, TLD) from the most recent proxy run, so a restart or reboot does not silently revert to defaults. Explicit env vars (`PORTLESS_PORT`, `PORTLESS_HTTPS`, etc.) always take priority.\n\nIn non-interactive environments (no TTY, or `CI=1`), portless exits with a descriptive error instead of prompting, so task runners like turborepo and CI scripts fail early with a clear message.\n\n## Configuration\n\nBare `portless` works out of the box. It runs the `\"dev\"` script from `package.json` through the proxy, inferring the app name from the package name, git root, or directory:\n\n```bash\nportless        # -> runs \"dev\" script, https:\u002F\u002F\u003Cproject>.localhost\n```\n\nUse an optional `portless.json` to override defaults:\n\n```json\n{ \"name\": \"myapp\" }\n```\n\n```bash\nportless        # -> runs \"dev\" script, https:\u002F\u002Fmyapp.localhost\n```\n\nThe script defaults to `\"dev\"`. The name is inferred from `package.json` if not set in config.\n\n### Monorepo\n\nOne `portless.json` at the repo root covers all workspace packages. Portless discovers packages from `pnpm-workspace.yaml`, or the `\"workspaces\"` field in `package.json` (npm, yarn, bun):\n\n```json\n{\n  \"apps\": {\n    \"apps\u002Fweb\": { \"name\": \"myapp\" },\n    \"apps\u002Fapi\": { \"name\": \"api.myapp\" }\n  }\n}\n```\n\n```bash\nportless        # from repo root: starts all workspace packages with a \"dev\" script\ncd apps\u002Fweb && portless   # start just one package\n```\n\nThe `apps` map is optional and only needed for name overrides. Packages not listed still auto-discover with names inferred from their `package.json`.\n\nWithout an `apps` map, hostnames follow the `\u003Cpackage>.\u003Cproject>.localhost` convention. The project name comes from the most common npm scope across workspace packages (e.g. `@myorg\u002Fweb` and `@myorg\u002Fapi` produce `myorg`), falling back to the workspace root directory name. If a package's short name matches the project name, it gets the bare `\u003Cproject>.localhost` without duplication.\n\n### Config fields\n\n| Field     | Type    | Default  | Description                                               |\n| --------- | ------- | -------- | --------------------------------------------------------- |\n| `name`    | string  | inferred | Base app name. Worktree prefix still applies.             |\n| `script`  | string  | `\"dev\"`  | Name of a `package.json` script to run.                   |\n| `appPort` | number  | auto     | Fixed port for the child process.                         |\n| `proxy`   | boolean | auto     | Whether to route through the proxy. Auto-detected.        |\n| `apps`    | object  |          | Overrides for workspace packages, keyed by relative path. |\n| `turbo`   | boolean | `true`   | Set `false` to use direct spawning instead of turborepo.  |\n\n### package.json \"portless\" key\n\nInstead of a separate `portless.json`, you can add a `\"portless\"` key to your `package.json`. A string value is shorthand for setting the name:\n\n```json\n{\n  \"name\": \"@myorg\u002Fweb\",\n  \"portless\": \"myapp\"\n}\n```\n\nAn object supports all per-app fields (`name`, `script`, `appPort`, `proxy`):\n\n```json\n{\n  \"name\": \"@myorg\u002Fweb\",\n  \"portless\": { \"name\": \"myapp\", \"script\": \"dev:app\" }\n}\n```\n\nThe `package.json` `\"portless\"` key takes precedence over `portless.json` app entries but is overridden by CLI flags.\n\n### --script flag\n\nOverride the default script for a single invocation:\n\n```bash\nportless --script start       # run \"start\" instead of \"dev\"\nportless --script test        # run \"test\" instead of \"dev\"\n```\n\n### Turborepo\n\nTo use portless with turborepo, put `portless` as the `dev` script and the real command in a separate script:\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"portless\",\n    \"dev:app\": \"next dev\"\n  },\n  \"portless\": { \"name\": \"myapp\", \"script\": \"dev:app\" }\n}\n```\n\nTurbo runs each package's `dev` script, which invokes portless. Portless reads the config, detects the package manager, and runs `pnpm run dev:app` (or yarn\u002Fbun\u002Fnpm) through the proxy. No changes to `turbo.json` are needed.\n\n`pnpm dev` at the root works through turbo as usual. People without portless can run `pnpm run dev:app` directly.\n\n## Use in package.json\n\nYou can still use portless in `package.json` scripts:\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"portless run next dev\"\n  }\n}\n```\n\nWith a `portless.json`, you can simplify to:\n\n```json\n{\n  \"scripts\": {\n    \"dev\": \"next dev\"\n  }\n}\n```\n\nThen run `portless` or `portless run` to go through the proxy.\n\n## Subdomains\n\nOrganize services with subdomains:\n\n```bash\nportless api.myapp pnpm start\n# -> https:\u002F\u002Fapi.myapp.localhost\n\nportless docs.myapp next dev\n# -> https:\u002F\u002Fdocs.myapp.localhost\n```\n\nBy default, only explicitly registered subdomains are routed (strict mode). Use `--wildcard` when starting the proxy to allow any subdomain of a registered route to fall back to that app (e.g. `tenant1.myapp.localhost` routes to the `myapp` app without extra registration).\n\n## Git Worktrees\n\n`portless run` automatically detects git worktrees. In a linked worktree, the branch name is prepended as a subdomain so each worktree gets its own URL without any config changes:\n\n```bash\n# Main worktree (no prefix)\nportless run next dev   # -> https:\u002F\u002Fmyapp.localhost\n\n# Linked worktree on branch \"fix-ui\"\nportless run next dev   # -> https:\u002F\u002Ffix-ui.myapp.localhost\n```\n\nUse `--name` to override the inferred base name while keeping the worktree prefix:\n\n```bash\nportless run --name myapp next dev   # -> https:\u002F\u002Ffix-ui.myapp.localhost\n```\n\nPut `portless run` in your `package.json` once and it works everywhere. The main checkout uses the plain name, each worktree gets a unique subdomain. No collisions, no `--force`.\n\n## Custom TLD\n\nBy default, portless uses `.localhost` which auto-resolves to `127.0.0.1` in most browsers. If you prefer a different TLD (e.g. `.test`), use `--tld`:\n\n```bash\nportless proxy start --tld test\nportless myapp next dev\n# -> https:\u002F\u002Fmyapp.test\n```\n\nThe proxy auto-syncs `\u002Fetc\u002Fhosts` for route hostnames (including `.test`), so those domains resolve on your machine.\n\nRecommended: `.test` (IANA-reserved, no collision risk). Avoid `.local` (conflicts with mDNS\u002FBonjour) and `.dev` (Google-owned, forces HTTPS via HSTS).\n\n## How it works\n\n```mermaid\nflowchart TD\n    Browser[\"Browser\u003Cbr>myapp.localhost\"]\n    Proxy[\"portless proxy\u003Cbr>(port 80 or 443)\"]\n    App1[\":4123\u003Cbr>myapp\"]\n    App2[\":4567\u003Cbr>api\"]\n\n    Browser --> Proxy\n    Proxy --> App1\n    Proxy --> App2\n```\n\n1. **Start the proxy**: auto-starts when you run an app, or start explicitly with `portless proxy start`\n2. **Run apps**: `portless \u003Cname> \u003Ccommand>` assigns a free port and registers with the proxy\n3. **Access via URL**: `https:\u002F\u002F\u003Cname>.localhost` routes through the proxy to your app\n\n## HTTP\u002F2 + HTTPS\n\nHTTPS with HTTP\u002F2 is enabled by default. Browsers limit HTTP\u002F1.1 to 6 connections per host, which bottlenecks dev servers that serve many unbundled files (Vite, Nuxt, etc.). HTTP\u002F2 multiplexes all requests over a single connection.\n\nOn first run, portless generates a local CA and adds it to your system trust store. No browser warnings. No manual setup.\n\n```bash\n# Use your own certs (e.g., from mkcert)\nportless proxy start --cert .\u002Fcert.pem --key .\u002Fkey.pem\n\n# Disable HTTPS (plain HTTP on port 80)\nportless proxy start --no-tls\n\n# If you skipped the trust prompt on first run, trust the CA later\nportless trust\n```\n\nOn Linux, `portless trust` supports Debian\u002FUbuntu, Arch, Fedora\u002FRHEL\u002FCentOS, and openSUSE (via `update-ca-certificates` or `update-ca-trust`). On Windows, it uses `certutil` to add the CA to the system trust store.\n\n## Start at OS startup\n\nInstall the proxy as an OS startup service so clean HTTPS URLs are available after reboot without starting the proxy from a terminal:\n\n```bash\nportless service install\nportless service status\nportless service uninstall\n```\n\nThe service uses portless defaults: HTTPS on port 443 with `.localhost` names. macOS and Linux install a root-owned service so port 443 can bind at boot. Windows installs a Task Scheduler startup task that runs as SYSTEM. Installation and removal may require administrator privileges. `portless clean` automatically removes the service.\n\n## LAN mode\n\n```bash\nportless proxy start --lan\nportless proxy start --lan --https\nportless proxy start --lan --ip 192.168.1.42\n```\n\n`--lan` switches the proxy to mDNS discovery: services are advertised as `\u003Cname>.local` and reachable from any device on the same network. Portless auto-detects your LAN IP and follows Wi-Fi\u002FIP changes automatically, but you can pin another address with `--ip \u003Caddress>` or by exporting `PORTLESS_LAN_IP`. Set `PORTLESS_LAN=1` in your shell (0\u002F1 boolean) to make LAN mode the default whenever the proxy starts.\n\nPortless remembers LAN mode via `proxy.lan`, so if you stop a LAN proxy and start it again, it stays in LAN mode. All proxy settings (port, TLS, TLD, LAN) are persisted and reused on auto-start unless overridden by explicit flags or env vars. Use `PORTLESS_LAN=0` for one start to switch back to `.localhost` mode. If a proxy is already running with different explicit LAN\u002FTLS\u002FTLD settings, portless warns and asks you to stop it first.\n\nLAN mode depends on the system mDNS tools that portless already spawns: macOS ships with `dns-sd`, while Linux uses `avahi-publish-address` from `avahi-utils` (install via `sudo apt install avahi-utils` or your distro’s equivalent). If the command is missing or your network isn’t reachable, `portless proxy start --lan` prints the relevant error and exits.\n\n### Framework notes\n\n- **Next.js**: add your `.local` hostnames to `allowedDevOrigins`:\n\n  ```js\n  \u002F\u002F next.config.js\n  module.exports = {\n    allowedDevOrigins: [\"myapp.local\", \"*.myapp.local\"],\n  };\n  ```\n\n- **Expo \u002F React Native**: portless always injects `--port`. React Native also gets `--host 127.0.0.1`. Expo gets `--host localhost` outside LAN mode, but in LAN mode portless leaves Metro on its default LAN host behavior instead of forcing `--host` or `HOST`.\n\n## Tailscale sharing\n\nShare your dev server with teammates on your [Tailscale](https:\u002F\u002Ftailscale.com) network:\n\n```bash\nportless myapp --tailscale next dev\n# -> https:\u002F\u002Fmyapp.localhost           (local)\n# -> https:\u002F\u002Fdevbox.yourteam.ts.net    (tailnet)\n```\n\nEach `--tailscale` app is root-mounted on its own Tailscale HTTPS port, so no framework `basePath` configuration is needed. The first app gets port 443, subsequent apps get 8443, 8444, etc.\n\n```bash\nportless myapp --tailscale next dev     # -> https:\u002F\u002Fdevbox.ts.net\nportless api --tailscale pnpm start     # -> https:\u002F\u002Fdevbox.ts.net:8443\n```\n\nUse `--funnel` to expose your dev server to the public internet via [Tailscale Funnel](https:\u002F\u002Ftailscale.com\u002Fkb\u002F1223\u002Ffunnel\u002F):\n\n```bash\nportless myapp --funnel next dev\n# -> https:\u002F\u002Fdevbox.yourteam.ts.net    (public)\n```\n\nTailscale HTTPS certificates must be enabled before `--tailscale` or `--funnel` can register HTTPS URLs. Funnel must also be enabled for the tailnet and node before `--funnel` can register the public URL. If either setting is missing, portless exits before starting the child process.\n\nSet `PORTLESS_TAILSCALE=1` in your shell profile or `.env` to share every app by default. `portless list` shows both local and tailnet URLs. Tailscale serve registrations are cleaned up automatically when the app exits.\n\nRequires the Tailscale CLI to be installed and connected (`tailscale up`), with Tailscale HTTPS certificates enabled.\n\n## Commands\n\n```bash\nportless                        # Run dev script through proxy\nportless                        # From monorepo root: run all workspace packages\nportless run [--name \u003Cname>] [cmd] [args...]  # Infer name, run through proxy\nportless \u003Cname> \u003Ccmd> [args...]  # Run app at https:\u002F\u002F\u003Cname>.localhost\nportless alias \u003Cname> \u003Cport>     # Register a static route (e.g. for Docker)\nportless alias \u003Cname> \u003Cport> --force  # Overwrite an existing route\nportless alias --remove \u003Cname>   # Remove a static route\nportless list                    # Show active routes\nportless trust                   # Add local CA to system trust store\nportless clean                   # Remove state, CA trust entry, and hosts block\nportless prune                   # Kill orphaned dev servers from crashed sessions\nportless hosts sync              # Add routes to \u002Fetc\u002Fhosts (fixes Safari)\nportless hosts clean             # Remove portless entries from \u002Fetc\u002Fhosts\n\n# Disable portless (run command directly)\nPORTLESS=0 pnpm dev              # Bypasses proxy, uses default port\n\n# Proxy control\nportless proxy start             # Start the HTTPS proxy (port 443, daemon)\nportless proxy start --no-tls    # Start without HTTPS (port 80)\nportless proxy start --lan       # Start in LAN mode (mDNS .local for devices)\nportless proxy start -p 1355     # Start on a custom port (no sudo)\nportless proxy start --foreground  # Start in foreground (for debugging)\nportless proxy start --wildcard  # Allow unregistered subdomains to fall back to parent\nportless proxy stop              # Stop the proxy\n\n# OS startup service\nportless service install         # Start HTTPS proxy when the OS starts\nportless service status          # Show service and proxy status\nportless service uninstall       # Remove the startup service\n```\n\n### Options\n\n```\n-p, --port \u003Cnumber>              Port for the proxy (default: 443, or 80 with --no-tls)\n--no-tls                         Disable HTTPS (use plain HTTP on port 80)\n--https                          Enable HTTPS (default, accepted for compatibility)\n--lan                            Enable LAN mode (mDNS .local for real devices)\n--ip \u003Caddress>                   Pin a specific LAN IP (disables auto-follow; use with --lan)\n--cert \u003Cpath>                    Use a custom TLS certificate\n--key \u003Cpath>                     Use a custom TLS private key\n--foreground                     Run proxy in foreground instead of daemon\n--tld \u003Ctld>                      Use a custom TLD instead of .localhost (e.g. test)\n--wildcard                       Allow unregistered subdomains to fall back to parent route\n--script \u003Cname>                  Run a specific package.json script (default: dev)\n--app-port \u003Cnumber>              Use a fixed port for the app (skip auto-assignment)\n--tailscale                      Share the app on your Tailscale network (tailnet)\n--funnel                         Share the app publicly via Tailscale Funnel\n--force                          Kill the existing process and take over its route\n--name \u003Cname>                    Use \u003Cname> as the app name\n```\n\n### Environment variables\n\n```\n# Configuration\nPORTLESS_PORT=\u003Cnumber>           Override the default proxy port\nPORTLESS_APP_PORT=\u003Cnumber>       Use a fixed port for the app (same as --app-port)\nPORTLESS_HTTPS=0                 Disable HTTPS (same as --no-tls)\nPORTLESS_LAN=1                   Enable LAN mode when set to 1 (auto-detects LAN IP)\nPORTLESS_TLD=\u003Ctld>               Use a custom TLD (e.g. test; default: localhost)\nPORTLESS_WILDCARD=1              Allow unregistered subdomains to fall back to parent route\nPORTLESS_SYNC_HOSTS=0            Disable auto-sync of \u002Fetc\u002Fhosts (on by default)\nPORTLESS_TAILSCALE=1             Share apps on your Tailscale network (same as --tailscale)\nPORTLESS_FUNNEL=1                Share apps publicly via Tailscale Funnel (same as --funnel)\nPORTLESS_STATE_DIR=\u003Cpath>        Override the state directory\n\n# Injected into child processes\nPORT                             Ephemeral port the child should listen on\nHOST                             Usually 127.0.0.1 (omitted for Expo in LAN mode)\nPORTLESS_URL                     Public URL (e.g. https:\u002F\u002Fmyapp.localhost)\nPORTLESS_TAILSCALE_URL           Tailscale URL of the app (when --tailscale is active)\nNODE_EXTRA_CA_CERTS              Path to the portless CA (when HTTPS is active)\n```\n\n> **Reserved names:** `run`, `get`, `alias`, `hosts`, `list`, `trust`, `clean`, `prune`, `proxy`, and `service` are subcommands and cannot be used as app names directly. Use `portless run \u003Ccmd>` to infer the name from your project, or `portless --name \u003Cname> \u003Ccmd>` to force any name including reserved ones.\n\n## Uninstall \u002F reset\n\nTo remove portless data from your machine (proxy state under `~\u002F.portless` and the system state directory, the local CA from the OS trust store when portless installed it, and the portless block in `\u002Fetc\u002Fhosts`):\n\n```bash\nportless clean\n```\n\nmacOS\u002FLinux may prompt for `sudo`. Custom certificate paths passed with `--cert` and `--key` are not deleted.\n\n## Safari \u002F DNS\n\n`.localhost` subdomains auto-resolve to `127.0.0.1` in Chrome, Firefox, and Edge. Safari relies on the system DNS resolver, which may not handle `.localhost` subdomains on all configurations.\n\nIf Safari can't find your `.localhost` URL:\n\n```bash\nportless hosts sync    # Add current routes to \u002Fetc\u002Fhosts\nportless hosts clean   # Clean up later\n```\n\nAuto-syncs `\u002Fetc\u002Fhosts` for route hostnames by default (`.localhost`, custom TLDs, LAN `.local`). Set `PORTLESS_SYNC_HOSTS=0` to disable.\n\n## Proxying Between Portless Apps\n\nIf your frontend dev server (e.g. Vite, webpack) proxies API requests to another portless app, make sure the proxy rewrites the `Host` header. Without this, portless routes the request back to the frontend in an infinite loop.\n\n**Vite** (`vite.config.ts`):\n\n```ts\nserver: {\n  proxy: {\n    \"\u002Fapi\": {\n      target: \"https:\u002F\u002Fapi.myapp.localhost\",\n      changeOrigin: true,\n      ws: true,\n    },\n  },\n}\n```\n\n**webpack-dev-server** (`webpack.config.js`):\n\n```js\ndevServer: {\n  proxy: [{\n    context: [\"\u002Fapi\"],\n    target: \"https:\u002F\u002Fapi.myapp.localhost\",\n    changeOrigin: true,\n  }],\n}\n```\n\nPortless automatically sets `NODE_EXTRA_CA_CERTS` in child processes so Node.js trusts the portless CA. If you run a separate Node.js process outside portless, point it at the CA manually: `NODE_EXTRA_CA_CERTS=~\u002F.portless\u002Fca.pem`. Alternatively, use `--no-tls` for plain HTTP.\n\nPortless detects this misconfiguration and responds with `508 Loop Detected` along with a message pointing to this fix.\n\n## Development\n\nThis repo is a pnpm workspace monorepo using [Turborepo](https:\u002F\u002Fturbo.build). The publishable package lives in `packages\u002Fportless\u002F`.\n\n```bash\npnpm install          # Install all dependencies\npnpm build            # Build all packages\npnpm test             # Run tests\npnpm test:coverage    # Run tests with coverage\npnpm lint             # Lint all packages\npnpm type-check       # Type-check all packages\npnpm format           # Format all files with Prettier\n```\n\n## Requirements\n\n- Node.js 20+\n- macOS, Linux, or Windows\n- Tailscale CLI (optional, for `--tailscale` and `--funnel`)\n","portless 是一个用于本地开发的工具，它通过将端口号替换为稳定的、命名的 .localhost URL 来简化开发流程。项目使用 TypeScript 编写，支持 HTTPS 和 HTTP\u002F2，默认启用，并且能够自动处理端口分配和环境变量注入，从而确保大多数 Web 框架（如 Next.js, Express, Nuxt 等）可以直接使用。此外，portless 还支持单仓库多包管理，适用于需要频繁切换不同服务或应用开发场景下的开发者，尤其适合团队协作时希望保持一致性和可预测性的开发环境配置。",2,"2026-06-11 03:50:42","high_star"]