portless 0.9.4 → 0.9.6
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 +1 -1
- package/dist/{chunk-63BOQNMU.js → chunk-OPTRASOS.js} +28 -5
- package/dist/cli.js +24 -11
- package/dist/index.d.ts +11 -3
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -166,7 +166,7 @@ portless proxy stop # Stop the proxy
|
|
|
166
166
|
--tld <tld> Use a custom TLD instead of .localhost (e.g. test)
|
|
167
167
|
--wildcard Allow unregistered subdomains to fall back to parent route
|
|
168
168
|
--app-port <number> Use a fixed port for the app (skip auto-assignment)
|
|
169
|
-
--force
|
|
169
|
+
--force Kill the existing process and take over its route
|
|
170
170
|
--name <name> Use <name> as the app name
|
|
171
171
|
```
|
|
172
172
|
|
|
@@ -477,8 +477,16 @@ function createProxyServer(options) {
|
|
|
477
477
|
}
|
|
478
478
|
proxySocket.pipe(socket);
|
|
479
479
|
socket.pipe(proxySocket);
|
|
480
|
-
|
|
481
|
-
|
|
480
|
+
const cleanup = () => {
|
|
481
|
+
proxySocket.destroy();
|
|
482
|
+
socket.destroy();
|
|
483
|
+
};
|
|
484
|
+
proxySocket.on("error", cleanup);
|
|
485
|
+
socket.on("error", cleanup);
|
|
486
|
+
proxySocket.on("close", cleanup);
|
|
487
|
+
socket.on("close", cleanup);
|
|
488
|
+
proxySocket.on("end", cleanup);
|
|
489
|
+
socket.on("end", cleanup);
|
|
482
490
|
});
|
|
483
491
|
proxyReq.on("error", (err) => {
|
|
484
492
|
onError(`WebSocket proxy error for ${getRequestHost(req)}: ${err.message}`);
|
|
@@ -505,7 +513,7 @@ function createProxyServer(options) {
|
|
|
505
513
|
};
|
|
506
514
|
if (tls) {
|
|
507
515
|
const h2Server = http2.createSecureServer({
|
|
508
|
-
cert: tls.cert,
|
|
516
|
+
cert: tls.ca ? Buffer.concat([tls.cert, tls.ca]) : tls.cert,
|
|
509
517
|
key: tls.key,
|
|
510
518
|
allowHTTP1: true,
|
|
511
519
|
...tls.SNICallback ? { SNICallback: tls.SNICallback } : {}
|
|
@@ -1297,16 +1305,30 @@ var RouteStore = class _RouteStore {
|
|
|
1297
1305
|
fs4.writeFileSync(this.routesPath, JSON.stringify(routes, null, 2), { mode: this.fileMode });
|
|
1298
1306
|
fixOwnership(this.routesPath);
|
|
1299
1307
|
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Register a route. When `force` is true and the hostname is already claimed
|
|
1310
|
+
* by another live process, that process is sent SIGTERM before the route is
|
|
1311
|
+
* replaced. Returns the PID of the killed process (if any) so the caller can
|
|
1312
|
+
* log it.
|
|
1313
|
+
*/
|
|
1300
1314
|
addRoute(hostname, port, pid, force = false) {
|
|
1301
1315
|
this.ensureDir();
|
|
1302
1316
|
if (!this.acquireLock()) {
|
|
1303
1317
|
throw new Error("Failed to acquire route lock");
|
|
1304
1318
|
}
|
|
1319
|
+
let killedPid;
|
|
1305
1320
|
try {
|
|
1306
1321
|
const routes = this.loadRoutes(true);
|
|
1307
1322
|
const existing = routes.find((r) => r.hostname === hostname);
|
|
1308
|
-
if (existing && existing.pid !== pid && this.isProcessAlive(existing.pid)
|
|
1309
|
-
|
|
1323
|
+
if (existing && existing.pid !== pid && this.isProcessAlive(existing.pid)) {
|
|
1324
|
+
if (!force) {
|
|
1325
|
+
throw new RouteConflictError(hostname, existing.pid);
|
|
1326
|
+
}
|
|
1327
|
+
try {
|
|
1328
|
+
process.kill(existing.pid, "SIGTERM");
|
|
1329
|
+
killedPid = existing.pid;
|
|
1330
|
+
} catch {
|
|
1331
|
+
}
|
|
1310
1332
|
}
|
|
1311
1333
|
const filtered = routes.filter((r) => r.hostname !== hostname);
|
|
1312
1334
|
filtered.push({ hostname, port, pid });
|
|
@@ -1314,6 +1336,7 @@ var RouteStore = class _RouteStore {
|
|
|
1314
1336
|
} finally {
|
|
1315
1337
|
this.releaseLock();
|
|
1316
1338
|
}
|
|
1339
|
+
return killedPid;
|
|
1317
1340
|
}
|
|
1318
1341
|
removeRoute(hostname) {
|
|
1319
1342
|
this.ensureDir();
|
package/dist/cli.js
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
waitForProxy,
|
|
36
36
|
writeTldFile,
|
|
37
37
|
writeTlsMarker
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-OPTRASOS.js";
|
|
39
39
|
|
|
40
40
|
// src/colors.ts
|
|
41
41
|
function supportsColor() {
|
|
@@ -471,10 +471,13 @@ async function generateHostCertAsync(stateDir, hostname) {
|
|
|
471
471
|
fixOwnership(keyPath, certPath);
|
|
472
472
|
return { certPath, keyPath };
|
|
473
473
|
}
|
|
474
|
-
function createSNICallback(stateDir, defaultCert, defaultKey, tld = "localhost") {
|
|
474
|
+
function createSNICallback(stateDir, defaultCert, defaultKey, tld = "localhost", caCert) {
|
|
475
475
|
const cache = /* @__PURE__ */ new Map();
|
|
476
476
|
const pending = /* @__PURE__ */ new Map();
|
|
477
|
-
const defaultCtx = tls.createSecureContext({
|
|
477
|
+
const defaultCtx = tls.createSecureContext({
|
|
478
|
+
cert: caCert ? Buffer.concat([defaultCert, caCert]) : defaultCert,
|
|
479
|
+
key: defaultKey
|
|
480
|
+
});
|
|
478
481
|
return (servername, cb) => {
|
|
479
482
|
if (servername === tld) {
|
|
480
483
|
cb(null, defaultCtx);
|
|
@@ -490,8 +493,9 @@ function createSNICallback(stateDir, defaultCert, defaultKey, tld = "localhost")
|
|
|
490
493
|
const keyPath = path.join(hostDir, `${safeName}-key.pem`);
|
|
491
494
|
if (fileExists(certPath) && fileExists(keyPath) && isCertValid(certPath) && isCertSignatureStrong(certPath)) {
|
|
492
495
|
try {
|
|
496
|
+
const hostCert = fs.readFileSync(certPath);
|
|
493
497
|
const ctx = tls.createSecureContext({
|
|
494
|
-
cert:
|
|
498
|
+
cert: caCert ? Buffer.concat([hostCert, caCert]) : hostCert,
|
|
495
499
|
key: fs.readFileSync(keyPath)
|
|
496
500
|
});
|
|
497
501
|
cache.set(servername, ctx);
|
|
@@ -505,11 +509,14 @@ function createSNICallback(stateDir, defaultCert, defaultKey, tld = "localhost")
|
|
|
505
509
|
return;
|
|
506
510
|
}
|
|
507
511
|
const promise = generateHostCertAsync(stateDir, servername).then(async (generated) => {
|
|
508
|
-
const [
|
|
512
|
+
const [hostCert, key] = await Promise.all([
|
|
509
513
|
fs.promises.readFile(generated.certPath),
|
|
510
514
|
fs.promises.readFile(generated.keyPath)
|
|
511
515
|
]);
|
|
512
|
-
return tls.createSecureContext({
|
|
516
|
+
return tls.createSecureContext({
|
|
517
|
+
cert: caCert ? Buffer.concat([hostCert, caCert]) : hostCert,
|
|
518
|
+
key
|
|
519
|
+
});
|
|
513
520
|
});
|
|
514
521
|
pending.set(servername, promise);
|
|
515
522
|
promise.then((ctx) => {
|
|
@@ -1157,8 +1164,9 @@ portless
|
|
|
1157
1164
|
} else {
|
|
1158
1165
|
console.log(colors_default.green(`-- Using port ${port}`));
|
|
1159
1166
|
}
|
|
1167
|
+
let killedPid;
|
|
1160
1168
|
try {
|
|
1161
|
-
store.addRoute(hostname, port, process.pid, force);
|
|
1169
|
+
killedPid = store.addRoute(hostname, port, process.pid, force);
|
|
1162
1170
|
} catch (err) {
|
|
1163
1171
|
if (err instanceof RouteConflictError) {
|
|
1164
1172
|
console.error(colors_default.red(`Error: ${err.message}`));
|
|
@@ -1166,6 +1174,9 @@ portless
|
|
|
1166
1174
|
}
|
|
1167
1175
|
throw err;
|
|
1168
1176
|
}
|
|
1177
|
+
if (killedPid !== void 0) {
|
|
1178
|
+
console.log(colors_default.yellow(`Killed existing process (PID ${killedPid})`));
|
|
1179
|
+
}
|
|
1169
1180
|
const finalUrl = formatUrl(hostname, proxyPort, tls2);
|
|
1170
1181
|
console.log(colors_default.cyan.bold(`
|
|
1171
1182
|
-> ${finalUrl}
|
|
@@ -1233,7 +1244,7 @@ ${colors_default.bold("Usage:")}
|
|
|
1233
1244
|
|
|
1234
1245
|
${colors_default.bold("Options:")}
|
|
1235
1246
|
--name <name> Override the inferred base name (worktree prefix still applies)
|
|
1236
|
-
--force
|
|
1247
|
+
--force Kill the existing process and take over its route
|
|
1237
1248
|
--app-port <number> Use a fixed port for the app (skip auto-assignment)
|
|
1238
1249
|
--help, -h Show this help
|
|
1239
1250
|
|
|
@@ -1388,7 +1399,7 @@ ${colors_default.bold("Options:")}
|
|
|
1388
1399
|
--tld <tld> Use a custom TLD instead of .localhost (e.g. test, dev)
|
|
1389
1400
|
--wildcard Allow unregistered subdomains to fall back to parent route
|
|
1390
1401
|
--app-port <number> Use a fixed port for the app (skip auto-assignment)
|
|
1391
|
-
--force
|
|
1402
|
+
--force Kill the existing process and take over its route
|
|
1392
1403
|
--name <name> Use <name> as the app name (bypasses subcommand dispatch)
|
|
1393
1404
|
-- Stop flag parsing; everything after is passed to the child
|
|
1394
1405
|
|
|
@@ -1427,7 +1438,7 @@ ${colors_default.bold("Reserved names:")}
|
|
|
1427
1438
|
process.exit(0);
|
|
1428
1439
|
}
|
|
1429
1440
|
function printVersion() {
|
|
1430
|
-
console.log("0.9.
|
|
1441
|
+
console.log("0.9.6");
|
|
1431
1442
|
process.exit(0);
|
|
1432
1443
|
}
|
|
1433
1444
|
async function handleTrust() {
|
|
@@ -1954,10 +1965,12 @@ ${colors_default.bold("Usage:")}
|
|
|
1954
1965
|
}
|
|
1955
1966
|
const cert = fs3.readFileSync(certs.certPath);
|
|
1956
1967
|
const key = fs3.readFileSync(certs.keyPath);
|
|
1968
|
+
const ca = fs3.readFileSync(certs.caPath);
|
|
1957
1969
|
tlsOptions = {
|
|
1958
1970
|
cert,
|
|
1959
1971
|
key,
|
|
1960
|
-
|
|
1972
|
+
ca,
|
|
1973
|
+
SNICallback: createSNICallback(stateDir, cert, key, tld, ca)
|
|
1961
1974
|
};
|
|
1962
1975
|
}
|
|
1963
1976
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ interface ProxyServerOptions {
|
|
|
26
26
|
tls?: {
|
|
27
27
|
cert: Buffer;
|
|
28
28
|
key: Buffer;
|
|
29
|
+
/** CA certificate to include in the chain so clients can verify the leaf. */
|
|
30
|
+
ca?: Buffer;
|
|
29
31
|
/** SNI callback for per-hostname certificate selection. */
|
|
30
32
|
SNICallback?: (servername: string, cb: (err: Error | null, ctx?: node_tls.SecureContext) => void) => void;
|
|
31
33
|
};
|
|
@@ -65,8 +67,8 @@ interface RouteMapping extends RouteInfo {
|
|
|
65
67
|
pid: number;
|
|
66
68
|
}
|
|
67
69
|
/**
|
|
68
|
-
* Thrown when a route is already registered by a live process and --force
|
|
69
|
-
*
|
|
70
|
+
* Thrown when a route is already registered by a live process and --force was
|
|
71
|
+
* not specified. With --force, the existing process is killed instead.
|
|
70
72
|
*/
|
|
71
73
|
declare class RouteConflictError extends Error {
|
|
72
74
|
readonly hostname: string;
|
|
@@ -106,7 +108,13 @@ declare class RouteStore {
|
|
|
106
108
|
*/
|
|
107
109
|
loadRoutes(persistCleanup?: boolean): RouteMapping[];
|
|
108
110
|
private saveRoutes;
|
|
109
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Register a route. When `force` is true and the hostname is already claimed
|
|
113
|
+
* by another live process, that process is sent SIGTERM before the route is
|
|
114
|
+
* replaced. Returns the PID of the killed process (if any) so the caller can
|
|
115
|
+
* log it.
|
|
116
|
+
*/
|
|
117
|
+
addRoute(hostname: string, port: number, pid: number, force?: boolean): number | undefined;
|
|
110
118
|
removeRoute(hostname: string): void;
|
|
111
119
|
}
|
|
112
120
|
|
package/dist/index.js
CHANGED