portless 0.5.0 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +280 -0
  2. package/dist/cli.js +1 -1
  3. package/package.json +3 -3
package/README.md ADDED
@@ -0,0 +1,280 @@
1
+ # portless
2
+
3
+ Replace port numbers with stable, named .localhost URLs. For humans and agents.
4
+
5
+ ```diff
6
+ - "dev": "next dev" # http://localhost:3000
7
+ + "dev": "portless run next dev" # http://myapp.localhost:1355
8
+ ```
9
+
10
+ ## Quick Start
11
+
12
+ ```bash
13
+ # Install
14
+ npm install -g portless
15
+
16
+ # Run your app (auto-starts the proxy if needed)
17
+ portless run next dev
18
+ # -> http://<project>.localhost:1355
19
+
20
+ # Or specify a name explicitly
21
+ portless myapp next dev
22
+ # -> http://myapp.localhost:1355
23
+ ```
24
+
25
+ > The proxy auto-starts when you run an app. You can also start it explicitly with `portless proxy start`.
26
+
27
+ ## Why
28
+
29
+ Local dev with port numbers is fragile:
30
+
31
+ - **Port conflicts** -- two projects default to the same port and you get `EADDRINUSE`
32
+ - **Memorizing ports** -- was the API on 3001 or 8080?
33
+ - **Refreshing shows the wrong app** -- stop one server, start another on the same port, and your open tab now shows something completely different
34
+ - **Monorepo multiplier** -- every problem above scales with each service in the repo
35
+ - **Agents test the wrong port** -- AI coding agents guess or hardcode the wrong port, especially in monorepos
36
+ - **Cookie and storage clashes** -- cookies set on `localhost` bleed across apps on different ports; localStorage is lost when ports shift
37
+ - **Hardcoded ports in config** -- CORS allowlists, OAuth redirect URIs, and `.env` files all break when ports change
38
+ - **Sharing URLs with teammates** -- "what port is that on?" becomes a Slack question
39
+ - **Browser history is useless** -- your history for `localhost:3000` is a jumble of unrelated projects
40
+
41
+ Portless fixes all of this by giving each dev server a stable, named `.localhost` URL that both humans and agents can rely on.
42
+
43
+ ## Usage
44
+
45
+ ```bash
46
+ # Auto-infer name from package.json / git / directory
47
+ portless run next dev
48
+ # -> http://<project>.localhost:1355
49
+
50
+ # Explicit name
51
+ portless myapp next dev
52
+ # -> http://myapp.localhost:1355
53
+
54
+ # Subdomains
55
+ portless api.myapp pnpm start
56
+ # -> http://api.myapp.localhost:1355
57
+
58
+ portless docs.myapp next dev
59
+ # -> http://docs.myapp.localhost:1355
60
+
61
+ # Wildcard subdomains (no extra registration needed)
62
+ # Any subdomain of a registered route routes automatically:
63
+ # tenant1.myapp.localhost:1355 -> myapp
64
+ # tenant2.myapp.localhost:1355 -> myapp
65
+ ```
66
+
67
+ ### Git Worktrees
68
+
69
+ `portless run` automatically detects git worktrees. When you're in a linked worktree, the branch name is prepended as a subdomain so each worktree gets its own URL without any config changes:
70
+
71
+ ```bash
72
+ # Main worktree (main/master branch) -- no prefix, works normally
73
+ portless run next dev
74
+ # -> http://myapp.localhost:1355
75
+
76
+ # Linked worktree on branch "fix-ui" -- branch name becomes a prefix
77
+ portless run next dev
78
+ # -> http://fix-ui.myapp.localhost:1355
79
+
80
+ # Linked worktree on branch "feature/auth" -- uses last segment
81
+ portless run next dev
82
+ # -> http://auth.myapp.localhost:1355
83
+ ```
84
+
85
+ This means you can put `portless run` in your `package.json` once and it just works everywhere -- the main checkout uses the plain name, and each worktree gets a unique subdomain. No `--force` needed, no name collisions.
86
+
87
+ ### In package.json
88
+
89
+ ```json
90
+ {
91
+ "scripts": {
92
+ "dev": "portless run next dev"
93
+ }
94
+ }
95
+ ```
96
+
97
+ The proxy auto-starts when you run an app. Or start it explicitly: `portless proxy start`.
98
+
99
+ ## How It Works
100
+
101
+ ```mermaid
102
+ flowchart TD
103
+ Browser["Browser\nmyapp.localhost:1355"]
104
+ Proxy["portless proxy<br>(port 1355)"]
105
+ App1[":4123\nmyapp"]
106
+ App2[":4567\napi"]
107
+
108
+ Browser -->|port 1355| Proxy
109
+ Proxy --> App1
110
+ Proxy --> App2
111
+ ```
112
+
113
+ 1. **Start the proxy** -- auto-starts when you run an app, or start explicitly with `portless proxy start`
114
+ 2. **Run apps** -- `portless <name> <command>` assigns a free port and registers with the proxy
115
+ 3. **Access via URL** -- `http://<name>.localhost:1355` routes through the proxy to your app
116
+
117
+ Apps are assigned a random port (4000-4999) via the `PORT` and `HOST` environment variables. Most frameworks (Next.js, Express, Nuxt, etc.) respect these automatically. For frameworks that ignore `PORT` (Vite, Astro, React Router, Angular, Expo, React Native), portless auto-injects the correct `--port` and `--host` flags.
118
+
119
+ ## HTTP/2 + HTTPS
120
+
121
+ Enable HTTP/2 for faster dev server page loads. Browsers limit HTTP/1.1 to 6 connections per host, which bottlenecks dev servers that serve many unbundled files (Vite, Nuxt, etc.). HTTP/2 multiplexes all requests over a single connection.
122
+
123
+ ```bash
124
+ # Start with HTTPS/2 -- generates certs and trusts them automatically
125
+ portless proxy start --https
126
+
127
+ # First run prompts for sudo once to add the CA to your system trust store.
128
+ # After that, no prompts. No browser warnings.
129
+
130
+ # Make it permanent (add to .bashrc / .zshrc)
131
+ export PORTLESS_HTTPS=1
132
+ portless proxy start # HTTPS by default now
133
+
134
+ # Use your own certs (e.g., from mkcert)
135
+ portless proxy start --cert ./cert.pem --key ./key.pem
136
+
137
+ # If you skipped sudo on first run, trust the CA later
138
+ sudo portless trust
139
+ ```
140
+
141
+ On Linux, `portless trust` supports Debian/Ubuntu, Arch, Fedora/RHEL/CentOS, and openSUSE (via `update-ca-certificates` or `update-ca-trust`).
142
+
143
+ ## Commands
144
+
145
+ ```bash
146
+ portless run <cmd> [args...] # Infer name from project, run through proxy
147
+ portless <name> <cmd> [args...] # Run app at http://<name>.localhost:1355
148
+ portless alias <name> <port> # Register a static route (e.g. for Docker)
149
+ portless alias <name> <port> --force # Overwrite an existing route
150
+ portless alias --remove <name> # Remove a static route
151
+ portless list # Show active routes
152
+ portless trust # Add local CA to system trust store
153
+ portless hosts sync # Add routes to /etc/hosts (fixes Safari)
154
+ portless hosts clean # Remove portless entries from /etc/hosts
155
+
156
+ # Disable portless (run command directly)
157
+ PORTLESS=0 pnpm dev # Bypasses proxy, uses default port
158
+ # Also accepts PORTLESS=skip
159
+
160
+ # Proxy control
161
+ portless proxy start # Start the proxy (port 1355, daemon)
162
+ portless proxy start --https # Start with HTTP/2 + TLS
163
+ portless proxy start -p 80 # Start on port 80 (requires sudo)
164
+ portless proxy start --foreground # Start in foreground (for debugging)
165
+ portless proxy stop # Stop the proxy
166
+
167
+ # Options
168
+ -p, --port <number> # Port for the proxy (default: 1355)
169
+ # Ports < 1024 require sudo
170
+ --https # Enable HTTP/2 + TLS with auto-generated certs
171
+ --cert <path> # Use a custom TLS certificate (implies --https)
172
+ --key <path> # Use a custom TLS private key (implies --https)
173
+ --no-tls # Disable HTTPS (overrides PORTLESS_HTTPS)
174
+ --foreground # Run proxy in foreground instead of daemon
175
+ --app-port <number> # Use a fixed port for the app (skip auto-assignment)
176
+ --force # Override a route registered by another process
177
+ --name <name> # Use <name> as the app name (bypasses subcommand dispatch)
178
+ -- # Stop flag parsing; everything after is passed to the child
179
+
180
+ # Injected into child processes
181
+ PORT # Ephemeral port the child should listen on
182
+ HOST # Always 127.0.0.1
183
+ PORTLESS_URL # Public URL (e.g. http://myapp.localhost:1355)
184
+
185
+ # Configuration
186
+ PORTLESS_PORT=<number> # Override the default proxy port
187
+ PORTLESS_APP_PORT=<number> # Use a fixed port for the app (same as --app-port)
188
+ PORTLESS_HTTPS=1|true # Always enable HTTPS
189
+ PORTLESS_SYNC_HOSTS=1 # Auto-sync /etc/hosts when routes change
190
+ PORTLESS_STATE_DIR=<path> # Override the state directory
191
+
192
+ # Info
193
+ portless --help # Show help
194
+ portless run --help # Show help for a specific subcommand
195
+ portless --version # Show version
196
+ ```
197
+
198
+ > **Reserved names:** `run`, `alias`, `hosts`, `list`, `trust`, and `proxy` are subcommands and cannot be used as app names directly. Use `portless run <cmd>` to infer the name from your project, or `portless --name <name> <cmd>` to force any name including reserved ones.
199
+
200
+ ## State Directory
201
+
202
+ Portless stores its state (routes, PID file, port file) in a directory that depends on the proxy port:
203
+
204
+ - **Port < 1024** (sudo required): `/tmp/portless` -- shared between root and user processes
205
+ - **Port >= 1024** (no sudo): `~/.portless` -- user-scoped, no root involvement
206
+
207
+ Override with the `PORTLESS_STATE_DIR` environment variable if needed.
208
+
209
+ ## Development
210
+
211
+ This repo is a pnpm workspace monorepo using [Turborepo](https://turbo.build). The publishable package lives in `packages/portless/`.
212
+
213
+ ```bash
214
+ pnpm install # Install all dependencies
215
+ pnpm build # Build all packages
216
+ pnpm test # Run tests
217
+ pnpm test:coverage # Run tests with coverage
218
+ pnpm test:watch # Run tests in watch mode
219
+ pnpm lint # Lint all packages
220
+ pnpm typecheck # Type-check all packages
221
+ pnpm format # Format all files with Prettier
222
+ ```
223
+
224
+ ## Safari / DNS
225
+
226
+ `.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.
227
+
228
+ If Safari can't find your `.localhost` URL:
229
+
230
+ ```bash
231
+ # Add current routes to /etc/hosts (requires sudo)
232
+ sudo portless hosts sync
233
+
234
+ # Clean up later
235
+ sudo portless hosts clean
236
+ ```
237
+
238
+ To auto-sync `/etc/hosts` whenever routes change, set `PORTLESS_SYNC_HOSTS=1` and start the proxy with sudo:
239
+
240
+ ```bash
241
+ export PORTLESS_SYNC_HOSTS=1
242
+ sudo portless proxy start
243
+ ```
244
+
245
+ ## Proxying Between Portless Apps
246
+
247
+ If 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, the proxy sends the **original** Host header, causing portless to route the request back to the frontend in an infinite loop.
248
+
249
+ **Vite** (`vite.config.ts`):
250
+
251
+ ```ts
252
+ server: {
253
+ proxy: {
254
+ "/api": {
255
+ target: "http://api.myapp.localhost:1355",
256
+ changeOrigin: true, // Required: rewrites Host header to match target
257
+ ws: true,
258
+ },
259
+ },
260
+ }
261
+ ```
262
+
263
+ **webpack-dev-server** (`webpack.config.js`):
264
+
265
+ ```js
266
+ devServer: {
267
+ proxy: [{
268
+ context: ["/api"],
269
+ target: "http://api.myapp.localhost:1355",
270
+ changeOrigin: true, // Required: rewrites Host header to match target
271
+ }],
272
+ }
273
+ ```
274
+
275
+ Portless detects this misconfiguration and responds with `508 Loop Detected` along with a message pointing to this fix.
276
+
277
+ ## Requirements
278
+
279
+ - Node.js 20+
280
+ - macOS or Linux
package/dist/cli.js CHANGED
@@ -1146,7 +1146,7 @@ ${chalk.bold("Reserved names:")}
1146
1146
  process.exit(0);
1147
1147
  }
1148
1148
  function printVersion() {
1149
- console.log("0.5.0");
1149
+ console.log("0.5.2");
1150
1150
  process.exit(0);
1151
1151
  }
1152
1152
  async function handleTrust() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portless",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Replace port numbers with stable, named .localhost URLs. For humans and agents.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,7 +29,7 @@
29
29
  "dev": "tsup --watch",
30
30
  "lint": "eslint src/",
31
31
  "lint:fix": "eslint src/ --fix",
32
- "prepublishOnly": "pnpm build",
32
+ "prepublishOnly": "cp ../../README.md . && pnpm build",
33
33
  "test": "vitest run",
34
34
  "test:coverage": "vitest run --coverage",
35
35
  "test:watch": "vitest",
@@ -47,7 +47,7 @@
47
47
  "type": "git",
48
48
  "url": "https://github.com/vercel-labs/portless.git"
49
49
  },
50
- "homepage": "https://github.com/vercel-labs/portless#readme",
50
+ "homepage": "https://port1355.dev",
51
51
  "bugs": {
52
52
  "url": "https://github.com/vercel-labs/portless/issues"
53
53
  },