portless 0.7.1 → 0.8.0
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/README.md +9 -6
- package/dist/{chunk-AXEPQFLY.js → chunk-KKXL2CMI.js} +61 -14
- package/dist/cli.js +325 -266
- package/dist/index.d.ts +6 -0
- package/dist/index.js +1 -1
- package/package.json +13 -15
- package/LICENSE +0 -201
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ portless myapp next dev
|
|
|
29
29
|
# -> http://myapp.localhost:1355
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
The 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, Astro, React Router, Angular), portless auto-injects `--port` and `--host` flags.
|
|
32
|
+
The 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, Astro, React Router, Angular, Expo, React Native), portless auto-injects `--port` and `--host` flags.
|
|
33
33
|
|
|
34
34
|
## Use in package.json
|
|
35
35
|
|
|
@@ -53,7 +53,7 @@ portless docs.myapp next dev
|
|
|
53
53
|
# -> http://docs.myapp.localhost:1355
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
By 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:1355` routes to the `myapp` app without extra registration).
|
|
57
57
|
|
|
58
58
|
## Git Worktrees
|
|
59
59
|
|
|
@@ -93,10 +93,10 @@ Recommended: `.test` (IANA-reserved, no collision risk). Avoid `.local` (conflic
|
|
|
93
93
|
|
|
94
94
|
```mermaid
|
|
95
95
|
flowchart TD
|
|
96
|
-
Browser["Browser
|
|
97
|
-
Proxy["portless proxy
|
|
98
|
-
App1[":4123
|
|
99
|
-
App2[":4567
|
|
96
|
+
Browser["Browser<br>myapp.localhost:1355"]
|
|
97
|
+
Proxy["portless proxy<br>(port 1355)"]
|
|
98
|
+
App1[":4123<br>myapp"]
|
|
99
|
+
App2[":4567<br>api"]
|
|
100
100
|
|
|
101
101
|
Browser -->|port 1355| Proxy
|
|
102
102
|
Proxy --> App1
|
|
@@ -152,6 +152,7 @@ portless proxy start # Start the proxy (port 1355, daemon)
|
|
|
152
152
|
portless proxy start --https # Start with HTTP/2 + TLS
|
|
153
153
|
portless proxy start -p 80 # Start on port 80 (requires sudo)
|
|
154
154
|
portless proxy start --foreground # Start in foreground (for debugging)
|
|
155
|
+
portless proxy start --wildcard # Allow unregistered subdomains to fall back to parent
|
|
155
156
|
portless proxy stop # Stop the proxy
|
|
156
157
|
```
|
|
157
158
|
|
|
@@ -165,6 +166,7 @@ portless proxy stop # Stop the proxy
|
|
|
165
166
|
--no-tls Disable HTTPS (overrides PORTLESS_HTTPS)
|
|
166
167
|
--foreground Run proxy in foreground instead of daemon
|
|
167
168
|
--tld <tld> Use a custom TLD instead of .localhost (e.g. test)
|
|
169
|
+
--wildcard Allow unregistered subdomains to fall back to parent route
|
|
168
170
|
--app-port <number> Use a fixed port for the app (skip auto-assignment)
|
|
169
171
|
--force Override a route registered by another process
|
|
170
172
|
--name <name> Use <name> as the app name
|
|
@@ -178,6 +180,7 @@ PORTLESS_PORT=<number> Override the default proxy port
|
|
|
178
180
|
PORTLESS_APP_PORT=<number> Use a fixed port for the app (same as --app-port)
|
|
179
181
|
PORTLESS_HTTPS=1 Always enable HTTPS
|
|
180
182
|
PORTLESS_TLD=<tld> Use a custom TLD (e.g. test; default: localhost)
|
|
183
|
+
PORTLESS_WILDCARD=1 Allow unregistered subdomains to fall back to parent route
|
|
181
184
|
PORTLESS_SYNC_HOSTS=1 Auto-sync /etc/hosts (auto-enabled for custom TLDs)
|
|
182
185
|
PORTLESS_STATE_DIR=<path> Override the state directory
|
|
183
186
|
|
|
@@ -300,14 +300,15 @@ function buildForwardedHeaders(req, tls) {
|
|
|
300
300
|
}
|
|
301
301
|
var PORTLESS_HOPS_HEADER = "x-portless-hops";
|
|
302
302
|
var MAX_PROXY_HOPS = 5;
|
|
303
|
-
function findRoute(routes, host) {
|
|
304
|
-
return routes.find((r) => r.hostname === host) || routes.find((r) => host.endsWith("." + r.hostname));
|
|
303
|
+
function findRoute(routes, host, strict) {
|
|
304
|
+
return routes.find((r) => r.hostname === host) || (strict ? void 0 : routes.find((r) => host.endsWith("." + r.hostname)));
|
|
305
305
|
}
|
|
306
306
|
function createProxyServer(options) {
|
|
307
307
|
const {
|
|
308
308
|
getRoutes,
|
|
309
309
|
proxyPort,
|
|
310
310
|
tld = "localhost",
|
|
311
|
+
strict = true,
|
|
311
312
|
onError = (msg) => console.error(msg),
|
|
312
313
|
tls
|
|
313
314
|
} = options;
|
|
@@ -342,7 +343,7 @@ function createProxyServer(options) {
|
|
|
342
343
|
);
|
|
343
344
|
return;
|
|
344
345
|
}
|
|
345
|
-
const route = findRoute(routes, host);
|
|
346
|
+
const route = findRoute(routes, host, strict);
|
|
346
347
|
if (!route) {
|
|
347
348
|
const safeHost = escapeHtml(host);
|
|
348
349
|
const strippedHost = host.endsWith(tldSuffix) ? host.slice(0, -tldSuffix.length) : host;
|
|
@@ -436,7 +437,7 @@ function createProxyServer(options) {
|
|
|
436
437
|
}
|
|
437
438
|
const routes = getRoutes();
|
|
438
439
|
const host = getRequestHost(req).split(":")[0];
|
|
439
|
-
const route = findRoute(routes, host);
|
|
440
|
+
const route = findRoute(routes, host, strict);
|
|
440
441
|
if (!route) {
|
|
441
442
|
socket.destroy();
|
|
442
443
|
return;
|
|
@@ -515,6 +516,9 @@ function createProxyServer(options) {
|
|
|
515
516
|
const plainServer = http.createServer(handleRequest);
|
|
516
517
|
plainServer.on("upgrade", handleUpgrade);
|
|
517
518
|
const wrapper = net.createServer((socket) => {
|
|
519
|
+
socket.on("error", () => {
|
|
520
|
+
socket.destroy();
|
|
521
|
+
});
|
|
518
522
|
socket.once("readable", () => {
|
|
519
523
|
const buf = socket.read(1);
|
|
520
524
|
if (!buf) {
|
|
@@ -745,6 +749,10 @@ function isHttpsEnvEnabled() {
|
|
|
745
749
|
const val = process.env.PORTLESS_HTTPS;
|
|
746
750
|
return val === "1" || val === "true";
|
|
747
751
|
}
|
|
752
|
+
function isWildcardEnvEnabled() {
|
|
753
|
+
const val = process.env.PORTLESS_WILDCARD;
|
|
754
|
+
return val === "1" || val === "true";
|
|
755
|
+
}
|
|
748
756
|
async function discoverState() {
|
|
749
757
|
if (process.env.PORTLESS_STATE_DIR) {
|
|
750
758
|
const dir = process.env.PORTLESS_STATE_DIR;
|
|
@@ -894,16 +902,28 @@ function collectBinPaths(cwd) {
|
|
|
894
902
|
return dirs;
|
|
895
903
|
}
|
|
896
904
|
function augmentedPath(env) {
|
|
897
|
-
const
|
|
905
|
+
const source = env ?? process.env;
|
|
906
|
+
const base = source.PATH ?? source.Path ?? "";
|
|
898
907
|
const bins = collectBinPaths(process.cwd());
|
|
899
|
-
|
|
908
|
+
const nodeBin = path2.dirname(process.execPath);
|
|
909
|
+
const allBins = [...bins, nodeBin];
|
|
910
|
+
return allBins.join(path2.delimiter) + path2.delimiter + base;
|
|
900
911
|
}
|
|
901
912
|
function spawnCommand(commandArgs, options) {
|
|
902
|
-
const env = {
|
|
903
|
-
|
|
913
|
+
const env = {
|
|
914
|
+
...options?.env ?? process.env,
|
|
915
|
+
PATH: augmentedPath(options?.env)
|
|
916
|
+
};
|
|
917
|
+
if (isWindows2) {
|
|
918
|
+
for (const key of Object.keys(env)) {
|
|
919
|
+
if (key !== "PATH" && key.toUpperCase() === "PATH") {
|
|
920
|
+
delete env[key];
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
const child = isWindows2 ? spawn("cmd.exe", ["/d", "/s", "/c", commandArgs.join(" ")], {
|
|
904
925
|
stdio: "inherit",
|
|
905
|
-
env
|
|
906
|
-
shell: true
|
|
926
|
+
env
|
|
907
927
|
}) : spawn("/bin/sh", ["-c", commandArgs.map(shellEscape).join(" ")], {
|
|
908
928
|
stdio: "inherit",
|
|
909
929
|
env
|
|
@@ -953,12 +973,38 @@ var FRAMEWORKS_NEEDING_PORT = {
|
|
|
953
973
|
"react-native": { strictPort: false },
|
|
954
974
|
expo: { strictPort: false }
|
|
955
975
|
};
|
|
976
|
+
var PACKAGE_RUNNERS = {
|
|
977
|
+
npx: [],
|
|
978
|
+
bunx: [],
|
|
979
|
+
pnpx: [],
|
|
980
|
+
yarn: ["dlx", "exec"],
|
|
981
|
+
pnpm: ["dlx", "exec"]
|
|
982
|
+
};
|
|
983
|
+
function findFrameworkBasename(commandArgs) {
|
|
984
|
+
if (commandArgs.length === 0) return null;
|
|
985
|
+
const first = path2.basename(commandArgs[0]);
|
|
986
|
+
if (FRAMEWORKS_NEEDING_PORT[first]) return first;
|
|
987
|
+
const subcommands = PACKAGE_RUNNERS[first];
|
|
988
|
+
if (!subcommands) return null;
|
|
989
|
+
let i = 1;
|
|
990
|
+
if (subcommands.length > 0) {
|
|
991
|
+
while (i < commandArgs.length && commandArgs[i].startsWith("-")) i++;
|
|
992
|
+
if (i >= commandArgs.length) return null;
|
|
993
|
+
if (!subcommands.includes(commandArgs[i])) {
|
|
994
|
+
const name2 = path2.basename(commandArgs[i]);
|
|
995
|
+
return FRAMEWORKS_NEEDING_PORT[name2] ? name2 : null;
|
|
996
|
+
}
|
|
997
|
+
i++;
|
|
998
|
+
}
|
|
999
|
+
while (i < commandArgs.length && commandArgs[i].startsWith("-")) i++;
|
|
1000
|
+
if (i >= commandArgs.length) return null;
|
|
1001
|
+
const name = path2.basename(commandArgs[i]);
|
|
1002
|
+
return FRAMEWORKS_NEEDING_PORT[name] ? name : null;
|
|
1003
|
+
}
|
|
956
1004
|
function injectFrameworkFlags(commandArgs, port) {
|
|
957
|
-
const
|
|
958
|
-
if (!
|
|
959
|
-
const basename2 = path2.basename(cmd);
|
|
1005
|
+
const basename2 = findFrameworkBasename(commandArgs);
|
|
1006
|
+
if (!basename2) return;
|
|
960
1007
|
const framework = FRAMEWORKS_NEEDING_PORT[basename2];
|
|
961
|
-
if (!framework) return;
|
|
962
1008
|
if (!commandArgs.includes("--port")) {
|
|
963
1009
|
commandArgs.push("--port", port.toString());
|
|
964
1010
|
if (framework.strictPort) {
|
|
@@ -1193,6 +1239,7 @@ export {
|
|
|
1193
1239
|
writeTldFile,
|
|
1194
1240
|
getDefaultTld,
|
|
1195
1241
|
isHttpsEnvEnabled,
|
|
1242
|
+
isWildcardEnvEnabled,
|
|
1196
1243
|
discoverState,
|
|
1197
1244
|
findFreePort,
|
|
1198
1245
|
isProxyRunning,
|