portless 0.3.0 → 0.4.1

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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
+ import * as node_tls from 'node:tls';
1
2
  import * as http from 'node:http';
3
+ import * as net from 'node:net';
2
4
 
3
5
  /** Route info used by the proxy server to map hostnames to ports. */
4
6
  interface RouteInfo {
@@ -12,18 +14,31 @@ interface ProxyServerOptions {
12
14
  proxyPort: number;
13
15
  /** Optional error logger; defaults to console.error. */
14
16
  onError?: (message: string) => void;
17
+ /** When provided, enables HTTP/2 over TLS (HTTPS). */
18
+ tls?: {
19
+ cert: Buffer;
20
+ key: Buffer;
21
+ /** SNI callback for per-hostname certificate selection. */
22
+ SNICallback?: (servername: string, cb: (err: Error | null, ctx?: node_tls.SecureContext) => void) => void;
23
+ };
15
24
  }
16
25
 
17
26
  /** Response header used to identify a portless proxy (for health checks). */
18
27
  declare const PORTLESS_HEADER = "X-Portless";
28
+ /** Server type returned by createProxyServer (plain HTTP/1.1 or net.Server TLS wrapper). */
29
+ type ProxyServer = http.Server | net.Server;
19
30
  /**
20
31
  * Create an HTTP proxy server that routes requests based on the Host header.
21
32
  *
22
33
  * Uses Node's built-in http module for proxying (no external dependencies).
23
34
  * The `getRoutes` callback is invoked on every request so callers can provide
24
35
  * either a static list or a live-updating one.
36
+ *
37
+ * When `tls` is provided, creates an HTTP/2 secure server with HTTP/1.1
38
+ * fallback (`allowHTTP1: true`). This enables HTTP/2 multiplexing for
39
+ * browsers while keeping WebSocket upgrades working over HTTP/1.1.
25
40
  */
26
- declare function createProxyServer(options: ProxyServerOptions): http.Server;
41
+ declare function createProxyServer(options: ProxyServerOptions): ProxyServer;
27
42
 
28
43
  /** File permission mode for route and state files. */
29
44
  declare const FILE_MODE = 420;
@@ -37,7 +52,8 @@ interface RouteMapping extends RouteInfo {
37
52
  * Supports file locking and stale-route cleanup.
38
53
  */
39
54
  declare class RouteStore {
40
- private readonly dir;
55
+ /** The state directory path. */
56
+ readonly dir: string;
41
57
  private readonly routesPath;
42
58
  private readonly lockPath;
43
59
  readonly pidPath: string;
@@ -72,13 +88,14 @@ declare function isErrnoException(err: unknown): err is NodeJS.ErrnoException;
72
88
  */
73
89
  declare function escapeHtml(str: string): string;
74
90
  /**
75
- * Format a .localhost URL, including the port only when it is not 80 (standard HTTP).
91
+ * Format a .localhost URL. Omits the port when it matches the protocol default
92
+ * (80 for HTTP, 443 for HTTPS).
76
93
  */
77
- declare function formatUrl(hostname: string, proxyPort: number): string;
94
+ declare function formatUrl(hostname: string, proxyPort: number, tls?: boolean): string;
78
95
  /**
79
96
  * Parse and normalize a hostname input for use as a .localhost subdomain.
80
97
  * Strips protocol prefixes, validates characters, and appends .localhost if needed.
81
98
  */
82
99
  declare function parseHostname(input: string): string;
83
100
 
84
- export { DIR_MODE, FILE_MODE, PORTLESS_HEADER, type ProxyServerOptions, type RouteInfo, type RouteMapping, RouteStore, createProxyServer, escapeHtml, formatUrl, isErrnoException, parseHostname };
101
+ export { DIR_MODE, FILE_MODE, PORTLESS_HEADER, type ProxyServer, type ProxyServerOptions, type RouteInfo, type RouteMapping, RouteStore, createProxyServer, escapeHtml, formatUrl, isErrnoException, parseHostname };
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  formatUrl,
9
9
  isErrnoException,
10
10
  parseHostname
11
- } from "./chunk-Y5OVKUR4.js";
11
+ } from "./chunk-VRBD6YAY.js";
12
12
  export {
13
13
  DIR_MODE,
14
14
  FILE_MODE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portless",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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",
@@ -17,7 +17,6 @@
17
17
  "files": [
18
18
  "dist"
19
19
  ],
20
- "packageManager": "pnpm@9.15.4",
21
20
  "engines": {
22
21
  "node": ">=20"
23
22
  },
@@ -25,21 +24,6 @@
25
24
  "darwin",
26
25
  "linux"
27
26
  ],
28
- "scripts": {
29
- "build": "tsup",
30
- "dev": "tsup --watch",
31
- "format": "prettier --write .",
32
- "format:check": "prettier --check .",
33
- "lint": "eslint src/",
34
- "lint:fix": "eslint src/ --fix",
35
- "prepublishOnly": "pnpm build",
36
- "pretest": "pnpm build",
37
- "test": "vitest run",
38
- "test:coverage": "vitest run --coverage",
39
- "test:watch": "vitest",
40
- "typecheck": "tsc --noEmit",
41
- "prepare": "husky"
42
- },
43
27
  "keywords": [
44
28
  "local",
45
29
  "development",
@@ -60,24 +44,21 @@
60
44
  "chalk": "^5.3.0"
61
45
  },
62
46
  "devDependencies": {
63
- "@eslint/js": "^9.39.2",
64
47
  "@types/node": "^20.11.0",
65
48
  "@vitest/coverage-v8": "^4.0.18",
66
49
  "eslint": "^9.39.2",
67
- "eslint-config-prettier": "^10.1.8",
68
- "husky": "^9.1.7",
69
- "lint-staged": "^16.2.7",
70
- "prettier": "^3.8.1",
71
50
  "tsup": "^8.0.1",
72
51
  "typescript": "^5.3.3",
73
- "typescript-eslint": "^8.55.0",
74
52
  "vitest": "^4.0.18"
75
53
  },
76
- "lint-staged": {
77
- "*.{ts,js}": [
78
- "eslint --fix",
79
- "prettier --write"
80
- ],
81
- "*.{json,md,yml,yaml}": "prettier --write"
54
+ "scripts": {
55
+ "build": "tsup",
56
+ "dev": "tsup --watch",
57
+ "lint": "eslint src/",
58
+ "lint:fix": "eslint src/ --fix",
59
+ "test": "vitest run",
60
+ "test:coverage": "vitest run --coverage",
61
+ "test:watch": "vitest",
62
+ "typecheck": "tsc --noEmit"
82
63
  }
83
- }
64
+ }
package/README.md DELETED
@@ -1,131 +0,0 @@
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 myapp next dev" # http://myapp.localhost:1355
8
- ```
9
-
10
- ## Quick Start
11
-
12
- ```bash
13
- # Install
14
- npm install -g portless
15
-
16
- # Start the proxy (once, no sudo needed)
17
- portless proxy start
18
-
19
- # Run your app (auto-starts the proxy if needed)
20
- portless myapp next dev
21
- # -> http://myapp.localhost:1355
22
- ```
23
-
24
- > The proxy auto-starts when you run an app. You can also start it explicitly with `portless proxy start`.
25
-
26
- ## Why
27
-
28
- Local dev with port numbers is fragile:
29
-
30
- - **Port conflicts** -- two projects default to the same port and you get `EADDRINUSE`
31
- - **Memorizing ports** -- was the API on 3001 or 8080?
32
- - **Refreshing shows the wrong app** -- stop one server, start another on the same port, and your open tab now shows something completely different
33
- - **Monorepo multiplier** -- every problem above scales with each service in the repo
34
- - **Agents test the wrong port** -- AI coding agents guess or hardcode the wrong port, especially in monorepos
35
- - **Cookie and storage clashes** -- cookies set on `localhost` bleed across apps on different ports; localStorage is lost when ports shift
36
- - **Hardcoded ports in config** -- CORS allowlists, OAuth redirect URIs, and `.env` files all break when ports change
37
- - **Sharing URLs with teammates** -- "what port is that on?" becomes a Slack question
38
- - **Browser history is useless** -- your history for `localhost:3000` is a jumble of unrelated projects
39
-
40
- Portless fixes all of this by giving each dev server a stable, named `.localhost` URL that both humans and agents can rely on.
41
-
42
- ## Usage
43
-
44
- ```bash
45
- # Basic
46
- portless myapp next dev
47
- # -> http://myapp.localhost:1355
48
-
49
- # Subdomains
50
- portless api.myapp pnpm start
51
- # -> http://api.myapp.localhost:1355
52
-
53
- portless docs.myapp next dev
54
- # -> http://docs.myapp.localhost:1355
55
- ```
56
-
57
- ### In package.json
58
-
59
- ```json
60
- {
61
- "scripts": {
62
- "dev": "portless myapp next dev"
63
- }
64
- }
65
- ```
66
-
67
- The proxy auto-starts when you run an app. Or start it explicitly: `portless proxy start`.
68
-
69
- ## How It Works
70
-
71
- ```mermaid
72
- flowchart TD
73
- Browser["Browser\nmyapp.localhost:1355"]
74
- Proxy["portless proxy\n(port 1355)"]
75
- App1[":4123\nmyapp"]
76
- App2[":4567\napi"]
77
-
78
- Browser -->|port 1355| Proxy
79
- Proxy --> App1
80
- Proxy --> App2
81
- ```
82
-
83
- 1. **Start the proxy** -- auto-starts when you run an app, or start explicitly with `portless proxy start`
84
- 2. **Run apps** -- `portless <name> <command>` assigns a free port and registers with the proxy
85
- 3. **Access via URL** -- `http://<name>.localhost:1355` routes through the proxy to your app
86
-
87
- Apps are assigned a random port (4000-4999) via the `PORT` environment variable. Most frameworks (Next.js, Vite, etc.) respect this automatically.
88
-
89
- ## Commands
90
-
91
- ```bash
92
- portless <name> <cmd> [args...] # Run app at http://<name>.localhost:1355
93
- portless list # Show active routes
94
-
95
- # Disable portless (run command directly)
96
- PORTLESS=0 pnpm dev # Bypasses proxy, uses default port
97
- # Also accepts PORTLESS=skip
98
-
99
- # Proxy control
100
- portless proxy start # Start the proxy (port 1355, daemon)
101
- portless proxy start -p 80 # Start on port 80 (requires sudo)
102
- portless proxy start --foreground # Start in foreground (for debugging)
103
- portless proxy stop # Stop the proxy
104
-
105
- # Options
106
- -p, --port <number> # Port for the proxy (default: 1355)
107
- # Ports < 1024 require sudo
108
- --foreground # Run proxy in foreground instead of daemon
109
-
110
- # Environment variables
111
- PORTLESS_PORT=<number> # Override the default proxy port
112
- PORTLESS_STATE_DIR=<path> # Override the state directory
113
-
114
- # Info
115
- portless --help # Show help
116
- portless --version # Show version
117
- ```
118
-
119
- ## State Directory
120
-
121
- Portless stores its state (routes, PID file, port file) in a directory that depends on the proxy port:
122
-
123
- - **Port < 1024** (sudo required): `/tmp/portless` -- shared between root and user processes
124
- - **Port >= 1024** (no sudo): `~/.portless` -- user-scoped, no root involvement
125
-
126
- Override with the `PORTLESS_STATE_DIR` environment variable if needed.
127
-
128
- ## Requirements
129
-
130
- - Node.js 20+
131
- - macOS or Linux