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/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
isErrnoException,
|
|
20
20
|
isHttpsEnvEnabled,
|
|
21
21
|
isProxyRunning,
|
|
22
|
+
isWildcardEnvEnabled,
|
|
22
23
|
isWindows,
|
|
23
24
|
parseHostname,
|
|
24
25
|
prompt,
|
|
@@ -31,10 +32,34 @@ import {
|
|
|
31
32
|
waitForProxy,
|
|
32
33
|
writeTldFile,
|
|
33
34
|
writeTlsMarker
|
|
34
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-KKXL2CMI.js";
|
|
36
|
+
|
|
37
|
+
// src/colors.ts
|
|
38
|
+
function supportsColor() {
|
|
39
|
+
if ("NO_COLOR" in process.env) return false;
|
|
40
|
+
if ("FORCE_COLOR" in process.env) return true;
|
|
41
|
+
return !!(process.stdout.isTTY || process.stderr.isTTY);
|
|
42
|
+
}
|
|
43
|
+
var enabled = supportsColor();
|
|
44
|
+
var wrap = (open, close) => {
|
|
45
|
+
if (!enabled) return (s) => s;
|
|
46
|
+
return (s) => `\x1B[${open}m${s}\x1B[${close}m`;
|
|
47
|
+
};
|
|
48
|
+
var bold = wrap("1", "22");
|
|
49
|
+
var red = wrap("31", "39");
|
|
50
|
+
var green = wrap("32", "39");
|
|
51
|
+
var yellow = wrap("33", "39");
|
|
52
|
+
var blue = Object.assign(wrap("34", "39"), {
|
|
53
|
+
bold: enabled ? (s) => `\x1B[34;1m${s}\x1B[22;39m` : (s) => s
|
|
54
|
+
});
|
|
55
|
+
var cyan = Object.assign(wrap("36", "39"), {
|
|
56
|
+
bold: enabled ? (s) => `\x1B[36;1m${s}\x1B[22;39m` : (s) => s
|
|
57
|
+
});
|
|
58
|
+
var white = wrap("37", "39");
|
|
59
|
+
var gray = wrap("90", "39");
|
|
60
|
+
var colors_default = { bold, red, green, yellow, blue, cyan, white, gray };
|
|
35
61
|
|
|
36
62
|
// src/cli.ts
|
|
37
|
-
import chalk from "chalk";
|
|
38
63
|
import * as fs3 from "fs";
|
|
39
64
|
import * as path3 from "path";
|
|
40
65
|
import { spawn, spawnSync } from "child_process";
|
|
@@ -164,6 +189,13 @@ function generateServerCert(stateDir) {
|
|
|
164
189
|
"subjectAltName=DNS:localhost,DNS:*.localhost"
|
|
165
190
|
].join("\n") + "\n"
|
|
166
191
|
);
|
|
192
|
+
const srlPath = path.join(stateDir, "ca.srl");
|
|
193
|
+
if (!fileExists(srlPath)) {
|
|
194
|
+
fs.writeFileSync(
|
|
195
|
+
srlPath,
|
|
196
|
+
crypto.randomUUID().replace(/-/g, "").slice(0, 16).toUpperCase() + "\n"
|
|
197
|
+
);
|
|
198
|
+
}
|
|
167
199
|
openssl([
|
|
168
200
|
"x509",
|
|
169
201
|
"-req",
|
|
@@ -174,7 +206,8 @@ function generateServerCert(stateDir) {
|
|
|
174
206
|
caCertPath,
|
|
175
207
|
"-CAkey",
|
|
176
208
|
caKeyPath,
|
|
177
|
-
"-
|
|
209
|
+
"-CAserial",
|
|
210
|
+
srlPath,
|
|
178
211
|
"-out",
|
|
179
212
|
serverCertPath,
|
|
180
213
|
"-days",
|
|
@@ -332,6 +365,7 @@ var HOST_CERTS_DIR = "host-certs";
|
|
|
332
365
|
function sanitizeHostForFilename(hostname) {
|
|
333
366
|
return hostname.replace(/\./g, "_").replace(/[^a-z0-9_-]/gi, "");
|
|
334
367
|
}
|
|
368
|
+
var MAX_CN_LENGTH = 64;
|
|
335
369
|
async function generateHostCertAsync(stateDir, hostname) {
|
|
336
370
|
const caKeyPath = path.join(stateDir, CA_KEY_FILE);
|
|
337
371
|
const caCertPath = path.join(stateDir, CA_CERT_FILE);
|
|
@@ -346,7 +380,8 @@ async function generateHostCertAsync(stateDir, hostname) {
|
|
|
346
380
|
const csrPath = path.join(hostDir, `${safeName}.csr`);
|
|
347
381
|
const extPath = path.join(hostDir, `${safeName}-ext.cnf`);
|
|
348
382
|
await opensslAsync(["ecparam", "-genkey", "-name", "prime256v1", "-noout", "-out", keyPath]);
|
|
349
|
-
|
|
383
|
+
const cn = hostname.length > MAX_CN_LENGTH ? hostname.slice(0, MAX_CN_LENGTH) : hostname;
|
|
384
|
+
await opensslAsync(["req", "-new", "-key", keyPath, "-out", csrPath, "-subj", `/CN=${cn}`]);
|
|
350
385
|
const sans = [`DNS:${hostname}`];
|
|
351
386
|
const parts = hostname.split(".");
|
|
352
387
|
if (parts.length >= 2) {
|
|
@@ -362,6 +397,13 @@ async function generateHostCertAsync(stateDir, hostname) {
|
|
|
362
397
|
`subjectAltName=${sans.join(",")}`
|
|
363
398
|
].join("\n") + "\n"
|
|
364
399
|
);
|
|
400
|
+
const srlPath = path.join(stateDir, "ca.srl");
|
|
401
|
+
if (!fs.existsSync(srlPath)) {
|
|
402
|
+
await fs.promises.writeFile(
|
|
403
|
+
srlPath,
|
|
404
|
+
crypto.randomUUID().replace(/-/g, "").slice(0, 16).toUpperCase() + "\n"
|
|
405
|
+
);
|
|
406
|
+
}
|
|
365
407
|
await opensslAsync([
|
|
366
408
|
"x509",
|
|
367
409
|
"-req",
|
|
@@ -372,7 +414,8 @@ async function generateHostCertAsync(stateDir, hostname) {
|
|
|
372
414
|
caCertPath,
|
|
373
415
|
"-CAkey",
|
|
374
416
|
caKeyPath,
|
|
375
|
-
"-
|
|
417
|
+
"-CAserial",
|
|
418
|
+
srlPath,
|
|
376
419
|
"-out",
|
|
377
420
|
certPath,
|
|
378
421
|
"-days",
|
|
@@ -683,7 +726,7 @@ var DEBOUNCE_MS = 100;
|
|
|
683
726
|
var POLL_INTERVAL_MS = 3e3;
|
|
684
727
|
var EXIT_TIMEOUT_MS = 2e3;
|
|
685
728
|
var SUDO_SPAWN_TIMEOUT_MS = 3e4;
|
|
686
|
-
function startProxyServer(store, proxyPort, tld, tlsOptions) {
|
|
729
|
+
function startProxyServer(store, proxyPort, tld, tlsOptions, strict) {
|
|
687
730
|
store.ensureDir();
|
|
688
731
|
const isTls = !!tlsOptions;
|
|
689
732
|
const routesPath = store.getRoutesPath();
|
|
@@ -716,7 +759,7 @@ function startProxyServer(store, proxyPort, tld, tlsOptions) {
|
|
|
716
759
|
debounceTimer = setTimeout(reloadRoutes, DEBOUNCE_MS);
|
|
717
760
|
});
|
|
718
761
|
} catch {
|
|
719
|
-
console.warn(
|
|
762
|
+
console.warn(colors_default.yellow("fs.watch unavailable; falling back to polling for route changes"));
|
|
720
763
|
pollingInterval = setInterval(reloadRoutes, POLL_INTERVAL_MS);
|
|
721
764
|
}
|
|
722
765
|
if (autoSyncHosts) {
|
|
@@ -726,28 +769,29 @@ function startProxyServer(store, proxyPort, tld, tlsOptions) {
|
|
|
726
769
|
getRoutes: () => cachedRoutes,
|
|
727
770
|
proxyPort,
|
|
728
771
|
tld,
|
|
729
|
-
|
|
772
|
+
strict,
|
|
773
|
+
onError: (msg) => console.error(colors_default.red(msg)),
|
|
730
774
|
tls: tlsOptions
|
|
731
775
|
});
|
|
732
776
|
server.on("error", (err) => {
|
|
733
777
|
if (err.code === "EADDRINUSE") {
|
|
734
|
-
console.error(
|
|
735
|
-
console.error(
|
|
736
|
-
console.error(
|
|
737
|
-
console.error(
|
|
778
|
+
console.error(colors_default.red(`Port ${proxyPort} is already in use.`));
|
|
779
|
+
console.error(colors_default.blue("Stop the existing proxy first:"));
|
|
780
|
+
console.error(colors_default.cyan(" portless proxy stop"));
|
|
781
|
+
console.error(colors_default.blue("Or check what is using the port:"));
|
|
738
782
|
console.error(
|
|
739
|
-
|
|
783
|
+
colors_default.cyan(
|
|
740
784
|
isWindows ? ` netstat -ano | findstr :${proxyPort}` : ` lsof -ti tcp:${proxyPort}`
|
|
741
785
|
)
|
|
742
786
|
);
|
|
743
787
|
} else if (err.code === "EACCES") {
|
|
744
|
-
console.error(
|
|
745
|
-
console.error(
|
|
746
|
-
console.error(
|
|
747
|
-
console.error(
|
|
748
|
-
console.error(
|
|
788
|
+
console.error(colors_default.red(`Permission denied for port ${proxyPort}.`));
|
|
789
|
+
console.error(colors_default.blue("Either run with sudo:"));
|
|
790
|
+
console.error(colors_default.cyan(" sudo portless proxy start -p 80"));
|
|
791
|
+
console.error(colors_default.blue("Or use a non-privileged port (no sudo needed):"));
|
|
792
|
+
console.error(colors_default.cyan(" portless proxy start"));
|
|
749
793
|
} else {
|
|
750
|
-
console.error(
|
|
794
|
+
console.error(colors_default.red(`Proxy error: ${err.message}`));
|
|
751
795
|
}
|
|
752
796
|
process.exit(1);
|
|
753
797
|
});
|
|
@@ -759,7 +803,10 @@ function startProxyServer(store, proxyPort, tld, tlsOptions) {
|
|
|
759
803
|
fixOwnership(store.dir, store.pidPath, store.portFilePath);
|
|
760
804
|
const proto = isTls ? "HTTPS/2" : "HTTP";
|
|
761
805
|
const tldLabel = tld !== DEFAULT_TLD ? ` (TLD: .${tld})` : "";
|
|
762
|
-
|
|
806
|
+
const modeLabel = strict === false ? " (wildcard)" : "";
|
|
807
|
+
console.log(
|
|
808
|
+
colors_default.green(`${proto} proxy listening on port ${proxyPort}${tldLabel}${modeLabel}`)
|
|
809
|
+
);
|
|
763
810
|
});
|
|
764
811
|
let exiting = false;
|
|
765
812
|
const cleanup = () => {
|
|
@@ -786,8 +833,8 @@ function startProxyServer(store, proxyPort, tld, tlsOptions) {
|
|
|
786
833
|
};
|
|
787
834
|
process.on("SIGINT", cleanup);
|
|
788
835
|
process.on("SIGTERM", cleanup);
|
|
789
|
-
console.log(
|
|
790
|
-
console.log(
|
|
836
|
+
console.log(colors_default.cyan("\nProxy is running. Press Ctrl+C to stop.\n"));
|
|
837
|
+
console.log(colors_default.gray(`Routes file: ${store.getRoutesPath()}`));
|
|
791
838
|
}
|
|
792
839
|
async function stopProxy(store, proxyPort, _tls) {
|
|
793
840
|
const pidPath = store.pidPath;
|
|
@@ -795,7 +842,7 @@ async function stopProxy(store, proxyPort, _tls) {
|
|
|
795
842
|
const sudoHint = needsSudo ? "sudo " : "";
|
|
796
843
|
if (!fs3.existsSync(pidPath)) {
|
|
797
844
|
if (await isProxyRunning(proxyPort)) {
|
|
798
|
-
console.log(
|
|
845
|
+
console.log(colors_default.yellow(`PID file is missing but port ${proxyPort} is still in use.`));
|
|
799
846
|
const pid = findPidOnPort(proxyPort);
|
|
800
847
|
if (pid !== null) {
|
|
801
848
|
try {
|
|
@@ -804,58 +851,58 @@ async function stopProxy(store, proxyPort, _tls) {
|
|
|
804
851
|
fs3.unlinkSync(store.portFilePath);
|
|
805
852
|
} catch {
|
|
806
853
|
}
|
|
807
|
-
console.log(
|
|
854
|
+
console.log(colors_default.green(`Killed process ${pid}. Proxy stopped.`));
|
|
808
855
|
} catch (err) {
|
|
809
856
|
if (isErrnoException(err) && err.code === "EPERM") {
|
|
810
857
|
console.error(
|
|
811
|
-
|
|
858
|
+
colors_default.red("Permission denied. The proxy was started with elevated privileges.")
|
|
812
859
|
);
|
|
813
|
-
console.error(
|
|
860
|
+
console.error(colors_default.blue("Stop it with:"));
|
|
814
861
|
console.error(
|
|
815
|
-
|
|
862
|
+
colors_default.cyan(
|
|
816
863
|
isWindows ? " Run portless proxy stop as Administrator" : " sudo portless proxy stop"
|
|
817
864
|
)
|
|
818
865
|
);
|
|
819
866
|
} else {
|
|
820
867
|
const message = err instanceof Error ? err.message : String(err);
|
|
821
|
-
console.error(
|
|
822
|
-
console.error(
|
|
868
|
+
console.error(colors_default.red(`Failed to stop proxy: ${message}`));
|
|
869
|
+
console.error(colors_default.blue("Check if the process is still running:"));
|
|
823
870
|
console.error(
|
|
824
|
-
|
|
871
|
+
colors_default.cyan(
|
|
825
872
|
isWindows ? ` netstat -ano | findstr :${proxyPort}` : ` lsof -ti tcp:${proxyPort}`
|
|
826
873
|
)
|
|
827
874
|
);
|
|
828
875
|
}
|
|
829
876
|
}
|
|
830
877
|
} else if (!isWindows && process.getuid?.() !== 0) {
|
|
831
|
-
console.error(
|
|
832
|
-
console.error(
|
|
833
|
-
console.error(
|
|
878
|
+
console.error(colors_default.red("Cannot identify the process. It may be running as root."));
|
|
879
|
+
console.error(colors_default.blue("Try stopping with sudo:"));
|
|
880
|
+
console.error(colors_default.cyan(" sudo portless proxy stop"));
|
|
834
881
|
} else {
|
|
835
|
-
console.error(
|
|
836
|
-
console.error(
|
|
882
|
+
console.error(colors_default.red(`Could not identify the process on port ${proxyPort}.`));
|
|
883
|
+
console.error(colors_default.blue("Try manually:"));
|
|
837
884
|
console.error(
|
|
838
|
-
|
|
885
|
+
colors_default.cyan(
|
|
839
886
|
isWindows ? " taskkill /F /PID <pid>" : ` sudo kill "$(lsof -ti tcp:${proxyPort})"`
|
|
840
887
|
)
|
|
841
888
|
);
|
|
842
889
|
}
|
|
843
890
|
} else {
|
|
844
|
-
console.log(
|
|
891
|
+
console.log(colors_default.yellow("Proxy is not running."));
|
|
845
892
|
}
|
|
846
893
|
return;
|
|
847
894
|
}
|
|
848
895
|
try {
|
|
849
896
|
const pid = parseInt(fs3.readFileSync(pidPath, "utf-8"), 10);
|
|
850
897
|
if (isNaN(pid)) {
|
|
851
|
-
console.error(
|
|
898
|
+
console.error(colors_default.red("Corrupted PID file. Removing it."));
|
|
852
899
|
fs3.unlinkSync(pidPath);
|
|
853
900
|
return;
|
|
854
901
|
}
|
|
855
902
|
try {
|
|
856
903
|
process.kill(pid, 0);
|
|
857
904
|
} catch {
|
|
858
|
-
console.log(
|
|
905
|
+
console.log(colors_default.yellow("Proxy process is no longer running. Cleaning up stale files."));
|
|
859
906
|
fs3.unlinkSync(pidPath);
|
|
860
907
|
try {
|
|
861
908
|
fs3.unlinkSync(store.portFilePath);
|
|
@@ -865,11 +912,11 @@ async function stopProxy(store, proxyPort, _tls) {
|
|
|
865
912
|
}
|
|
866
913
|
if (!await isProxyRunning(proxyPort)) {
|
|
867
914
|
console.log(
|
|
868
|
-
|
|
915
|
+
colors_default.yellow(
|
|
869
916
|
`PID file exists but port ${proxyPort} is not listening. The PID may have been recycled.`
|
|
870
917
|
)
|
|
871
918
|
);
|
|
872
|
-
console.log(
|
|
919
|
+
console.log(colors_default.yellow("Removing stale PID file."));
|
|
873
920
|
fs3.unlinkSync(pidPath);
|
|
874
921
|
return;
|
|
875
922
|
}
|
|
@@ -879,20 +926,20 @@ async function stopProxy(store, proxyPort, _tls) {
|
|
|
879
926
|
fs3.unlinkSync(store.portFilePath);
|
|
880
927
|
} catch {
|
|
881
928
|
}
|
|
882
|
-
console.log(
|
|
929
|
+
console.log(colors_default.green("Proxy stopped."));
|
|
883
930
|
} catch (err) {
|
|
884
931
|
if (isErrnoException(err) && err.code === "EPERM") {
|
|
885
932
|
console.error(
|
|
886
|
-
|
|
933
|
+
colors_default.red("Permission denied. The proxy was started with elevated privileges.")
|
|
887
934
|
);
|
|
888
|
-
console.error(
|
|
889
|
-
console.error(
|
|
935
|
+
console.error(colors_default.blue("Stop it with:"));
|
|
936
|
+
console.error(colors_default.cyan(` ${sudoHint}portless proxy stop`));
|
|
890
937
|
} else {
|
|
891
938
|
const message = err instanceof Error ? err.message : String(err);
|
|
892
|
-
console.error(
|
|
893
|
-
console.error(
|
|
939
|
+
console.error(colors_default.red(`Failed to stop proxy: ${message}`));
|
|
940
|
+
console.error(colors_default.blue("Check if the process is still running:"));
|
|
894
941
|
console.error(
|
|
895
|
-
|
|
942
|
+
colors_default.cyan(
|
|
896
943
|
isWindows ? ` netstat -ano | findstr :${proxyPort}` : ` lsof -ti tcp:${proxyPort}`
|
|
897
944
|
)
|
|
898
945
|
);
|
|
@@ -902,16 +949,16 @@ async function stopProxy(store, proxyPort, _tls) {
|
|
|
902
949
|
function listRoutes(store, proxyPort, tls2) {
|
|
903
950
|
const routes = store.loadRoutes();
|
|
904
951
|
if (routes.length === 0) {
|
|
905
|
-
console.log(
|
|
906
|
-
console.log(
|
|
952
|
+
console.log(colors_default.yellow("No active routes."));
|
|
953
|
+
console.log(colors_default.gray("Start an app with: portless <name> <command>"));
|
|
907
954
|
return;
|
|
908
955
|
}
|
|
909
|
-
console.log(
|
|
956
|
+
console.log(colors_default.blue.bold("\nActive routes:\n"));
|
|
910
957
|
for (const route of routes) {
|
|
911
958
|
const url = formatUrl(route.hostname, proxyPort, tls2);
|
|
912
959
|
const label = route.pid === 0 ? "(alias)" : `(pid ${route.pid})`;
|
|
913
960
|
console.log(
|
|
914
|
-
` ${
|
|
961
|
+
` ${colors_default.cyan(url)} ${colors_default.gray("->")} ${colors_default.white(`localhost:${route.port}`)} ${colors_default.gray(label)}`
|
|
915
962
|
);
|
|
916
963
|
}
|
|
917
964
|
console.log();
|
|
@@ -922,25 +969,25 @@ async function runApp(store, proxyPort, stateDir, name, commandArgs, tls2, tld,
|
|
|
922
969
|
try {
|
|
923
970
|
envTld = getDefaultTld();
|
|
924
971
|
} catch (err) {
|
|
925
|
-
console.error(
|
|
972
|
+
console.error(colors_default.red(`Error: ${err.message}`));
|
|
926
973
|
process.exit(1);
|
|
927
974
|
}
|
|
928
975
|
if (envTld !== DEFAULT_TLD && envTld !== tld) {
|
|
929
976
|
console.warn(
|
|
930
|
-
|
|
977
|
+
colors_default.yellow(
|
|
931
978
|
`Warning: PORTLESS_TLD=${envTld} but the running proxy uses .${tld}. Using .${tld}.`
|
|
932
979
|
)
|
|
933
980
|
);
|
|
934
981
|
}
|
|
935
|
-
console.log(
|
|
982
|
+
console.log(colors_default.blue.bold(`
|
|
936
983
|
portless
|
|
937
984
|
`));
|
|
938
|
-
console.log(
|
|
985
|
+
console.log(colors_default.gray(`-- ${hostname} (auto-resolves to 127.0.0.1)`));
|
|
939
986
|
if (autoInfo) {
|
|
940
987
|
const baseName = autoInfo.prefix ? name.slice(autoInfo.prefix.length + 1) : name;
|
|
941
|
-
console.log(
|
|
988
|
+
console.log(colors_default.gray(`-- Name "${baseName}" (from ${autoInfo.nameSource})`));
|
|
942
989
|
if (autoInfo.prefix) {
|
|
943
|
-
console.log(
|
|
990
|
+
console.log(colors_default.gray(`-- Prefix "${autoInfo.prefix}" (from ${autoInfo.prefixSource})`));
|
|
944
991
|
}
|
|
945
992
|
}
|
|
946
993
|
if (!await isProxyRunning(proxyPort, tls2)) {
|
|
@@ -949,24 +996,24 @@ portless
|
|
|
949
996
|
const wantHttps = isHttpsEnvEnabled();
|
|
950
997
|
if (needsSudo) {
|
|
951
998
|
if (!process.stdin.isTTY) {
|
|
952
|
-
console.error(
|
|
953
|
-
console.error(
|
|
954
|
-
console.error(
|
|
955
|
-
console.error(
|
|
956
|
-
console.error(
|
|
999
|
+
console.error(colors_default.red("Proxy is not running."));
|
|
1000
|
+
console.error(colors_default.blue("Start the proxy first (requires sudo for this port):"));
|
|
1001
|
+
console.error(colors_default.cyan(" sudo portless proxy start -p 80"));
|
|
1002
|
+
console.error(colors_default.blue("Or use the default port (no sudo needed):"));
|
|
1003
|
+
console.error(colors_default.cyan(" portless proxy start"));
|
|
957
1004
|
process.exit(1);
|
|
958
1005
|
}
|
|
959
|
-
const answer = await prompt(
|
|
1006
|
+
const answer = await prompt(colors_default.yellow("Proxy not running. Start it? [Y/n/skip] "));
|
|
960
1007
|
if (answer === "n" || answer === "no") {
|
|
961
|
-
console.log(
|
|
1008
|
+
console.log(colors_default.gray("Cancelled."));
|
|
962
1009
|
process.exit(0);
|
|
963
1010
|
}
|
|
964
1011
|
if (answer === "s" || answer === "skip") {
|
|
965
|
-
console.log(
|
|
1012
|
+
console.log(colors_default.gray("Skipping proxy, running command directly...\n"));
|
|
966
1013
|
spawnCommand(commandArgs);
|
|
967
1014
|
return;
|
|
968
1015
|
}
|
|
969
|
-
console.log(
|
|
1016
|
+
console.log(colors_default.yellow("Starting proxy (requires sudo)..."));
|
|
970
1017
|
const startArgs = [process.execPath, process.argv[1], "proxy", "start"];
|
|
971
1018
|
if (wantHttps) startArgs.push("--https");
|
|
972
1019
|
if (tld !== DEFAULT_TLD) startArgs.push("--tld", tld);
|
|
@@ -975,13 +1022,15 @@ portless
|
|
|
975
1022
|
timeout: SUDO_SPAWN_TIMEOUT_MS
|
|
976
1023
|
});
|
|
977
1024
|
if (result.status !== 0) {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1025
|
+
if (!await isProxyRunning(proxyPort)) {
|
|
1026
|
+
console.error(colors_default.red("Failed to start proxy."));
|
|
1027
|
+
console.error(colors_default.blue("Try starting it manually:"));
|
|
1028
|
+
console.error(colors_default.cyan(" sudo portless proxy start"));
|
|
1029
|
+
process.exit(1);
|
|
1030
|
+
}
|
|
982
1031
|
}
|
|
983
1032
|
} else {
|
|
984
|
-
console.log(
|
|
1033
|
+
console.log(colors_default.yellow("Starting proxy..."));
|
|
985
1034
|
const startArgs = [process.argv[1], "proxy", "start"];
|
|
986
1035
|
if (wantHttps) startArgs.push("--https");
|
|
987
1036
|
if (tld !== DEFAULT_TLD) startArgs.push("--tld", tld);
|
|
@@ -990,51 +1039,53 @@ portless
|
|
|
990
1039
|
timeout: SUDO_SPAWN_TIMEOUT_MS
|
|
991
1040
|
});
|
|
992
1041
|
if (result.status !== 0) {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1042
|
+
if (!await isProxyRunning(proxyPort)) {
|
|
1043
|
+
console.error(colors_default.red("Failed to start proxy."));
|
|
1044
|
+
console.error(colors_default.blue("Try starting it manually:"));
|
|
1045
|
+
console.error(colors_default.cyan(" portless proxy start"));
|
|
1046
|
+
process.exit(1);
|
|
1047
|
+
}
|
|
997
1048
|
}
|
|
998
1049
|
}
|
|
999
1050
|
const autoTls = readTlsMarker(stateDir);
|
|
1000
1051
|
tld = readTldFromDir(stateDir);
|
|
1001
1052
|
if (!await waitForProxy(defaultPort, void 0, void 0, autoTls)) {
|
|
1002
|
-
console.error(
|
|
1053
|
+
console.error(colors_default.red("Proxy failed to start (timed out waiting for it to listen)."));
|
|
1003
1054
|
const logPath = path3.join(stateDir, "proxy.log");
|
|
1004
|
-
console.error(
|
|
1005
|
-
console.error(
|
|
1055
|
+
console.error(colors_default.blue("Try starting the proxy manually to see the error:"));
|
|
1056
|
+
console.error(colors_default.cyan(` ${needsSudo ? "sudo " : ""}portless proxy start`));
|
|
1006
1057
|
if (fs3.existsSync(logPath)) {
|
|
1007
|
-
console.error(
|
|
1058
|
+
console.error(colors_default.gray(`Logs: ${logPath}`));
|
|
1008
1059
|
}
|
|
1009
1060
|
process.exit(1);
|
|
1010
1061
|
}
|
|
1011
1062
|
tls2 = autoTls;
|
|
1012
|
-
console.log(
|
|
1063
|
+
console.log(colors_default.green("Proxy started in background"));
|
|
1013
1064
|
} else {
|
|
1014
|
-
console.log(
|
|
1065
|
+
console.log(colors_default.gray("-- Proxy is running"));
|
|
1015
1066
|
}
|
|
1016
1067
|
const port = desiredPort ?? await findFreePort();
|
|
1017
1068
|
if (desiredPort) {
|
|
1018
|
-
console.log(
|
|
1069
|
+
console.log(colors_default.green(`-- Using port ${port} (fixed)`));
|
|
1019
1070
|
} else {
|
|
1020
|
-
console.log(
|
|
1071
|
+
console.log(colors_default.green(`-- Using port ${port}`));
|
|
1021
1072
|
}
|
|
1022
1073
|
try {
|
|
1023
1074
|
store.addRoute(hostname, port, process.pid, force);
|
|
1024
1075
|
} catch (err) {
|
|
1025
1076
|
if (err instanceof RouteConflictError) {
|
|
1026
|
-
console.error(
|
|
1077
|
+
console.error(colors_default.red(`Error: ${err.message}`));
|
|
1027
1078
|
process.exit(1);
|
|
1028
1079
|
}
|
|
1029
1080
|
throw err;
|
|
1030
1081
|
}
|
|
1031
1082
|
const finalUrl = formatUrl(hostname, proxyPort, tls2);
|
|
1032
|
-
console.log(
|
|
1083
|
+
console.log(colors_default.cyan.bold(`
|
|
1033
1084
|
-> ${finalUrl}
|
|
1034
1085
|
`));
|
|
1035
1086
|
injectFrameworkFlags(commandArgs, port);
|
|
1036
1087
|
console.log(
|
|
1037
|
-
|
|
1088
|
+
colors_default.gray(
|
|
1038
1089
|
`Running: PORT=${port} HOST=127.0.0.1 PORTLESS_URL=${finalUrl} ${commandArgs.join(" ")}
|
|
1039
1090
|
`
|
|
1040
1091
|
)
|
|
@@ -1057,12 +1108,12 @@ portless
|
|
|
1057
1108
|
}
|
|
1058
1109
|
function parseAppPort(value) {
|
|
1059
1110
|
if (!value || value.startsWith("--")) {
|
|
1060
|
-
console.error(
|
|
1111
|
+
console.error(colors_default.red("Error: --app-port requires a port number."));
|
|
1061
1112
|
process.exit(1);
|
|
1062
1113
|
}
|
|
1063
1114
|
const port = parseInt(value, 10);
|
|
1064
1115
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
1065
|
-
console.error(
|
|
1116
|
+
console.error(colors_default.red(`Error: Invalid app port "${value}". Must be 1-65535.`));
|
|
1066
1117
|
process.exit(1);
|
|
1067
1118
|
}
|
|
1068
1119
|
return port;
|
|
@@ -1072,7 +1123,7 @@ function appPortFromEnv() {
|
|
|
1072
1123
|
if (!envVal) return void 0;
|
|
1073
1124
|
const port = parseInt(envVal, 10);
|
|
1074
1125
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
1075
|
-
console.error(
|
|
1126
|
+
console.error(colors_default.red(`Error: Invalid PORTLESS_APP_PORT="${envVal}". Must be 1-65535.`));
|
|
1076
1127
|
process.exit(1);
|
|
1077
1128
|
}
|
|
1078
1129
|
return port;
|
|
@@ -1088,18 +1139,18 @@ function parseRunArgs(args) {
|
|
|
1088
1139
|
break;
|
|
1089
1140
|
} else if (args[i] === "--help" || args[i] === "-h") {
|
|
1090
1141
|
console.log(`
|
|
1091
|
-
${
|
|
1142
|
+
${colors_default.bold("portless run")} - Infer project name and run through the proxy.
|
|
1092
1143
|
|
|
1093
|
-
${
|
|
1094
|
-
${
|
|
1144
|
+
${colors_default.bold("Usage:")}
|
|
1145
|
+
${colors_default.cyan("portless run [options] <command...>")}
|
|
1095
1146
|
|
|
1096
|
-
${
|
|
1147
|
+
${colors_default.bold("Options:")}
|
|
1097
1148
|
--name <name> Override the inferred base name (worktree prefix still applies)
|
|
1098
1149
|
--force Override an existing route registered by another process
|
|
1099
1150
|
--app-port <number> Use a fixed port for the app (skip auto-assignment)
|
|
1100
1151
|
--help, -h Show this help
|
|
1101
1152
|
|
|
1102
|
-
${
|
|
1153
|
+
${colors_default.bold("Name inference (in order):")}
|
|
1103
1154
|
1. package.json "name" field (walks up directories)
|
|
1104
1155
|
2. Git repo root directory name
|
|
1105
1156
|
3. Current directory basename
|
|
@@ -1108,7 +1159,7 @@ ${chalk.bold("Name inference (in order):")}
|
|
|
1108
1159
|
In git worktrees, the branch name is prepended as a subdomain prefix
|
|
1109
1160
|
(e.g. feature-auth.myapp.localhost).
|
|
1110
1161
|
|
|
1111
|
-
${
|
|
1162
|
+
${colors_default.bold("Examples:")}
|
|
1112
1163
|
portless run next dev # -> http://<project>.localhost:1355
|
|
1113
1164
|
portless run --name myapp next dev # -> http://myapp.localhost:1355
|
|
1114
1165
|
portless run vite dev # -> http://<project>.localhost:1355
|
|
@@ -1123,14 +1174,14 @@ ${chalk.bold("Examples:")}
|
|
|
1123
1174
|
} else if (args[i] === "--name") {
|
|
1124
1175
|
i++;
|
|
1125
1176
|
if (!args[i] || args[i].startsWith("-")) {
|
|
1126
|
-
console.error(
|
|
1127
|
-
console.error(
|
|
1177
|
+
console.error(colors_default.red("Error: --name requires a name value."));
|
|
1178
|
+
console.error(colors_default.cyan(" portless run --name <name> <command...>"));
|
|
1128
1179
|
process.exit(1);
|
|
1129
1180
|
}
|
|
1130
1181
|
name = args[i];
|
|
1131
1182
|
} else {
|
|
1132
|
-
console.error(
|
|
1133
|
-
console.error(
|
|
1183
|
+
console.error(colors_default.red(`Error: Unknown flag "${args[i]}".`));
|
|
1184
|
+
console.error(colors_default.blue("Known flags: --name, --force, --app-port, --help"));
|
|
1134
1185
|
process.exit(1);
|
|
1135
1186
|
}
|
|
1136
1187
|
i++;
|
|
@@ -1152,8 +1203,8 @@ function parseAppArgs(args) {
|
|
|
1152
1203
|
i++;
|
|
1153
1204
|
appPort = parseAppPort(args[i]);
|
|
1154
1205
|
} else {
|
|
1155
|
-
console.error(
|
|
1156
|
-
console.error(
|
|
1206
|
+
console.error(colors_default.red(`Error: Unknown flag "${args[i]}".`));
|
|
1207
|
+
console.error(colors_default.blue("Known flags: --force, --app-port"));
|
|
1157
1208
|
process.exit(1);
|
|
1158
1209
|
}
|
|
1159
1210
|
i++;
|
|
@@ -1170,8 +1221,8 @@ function parseAppArgs(args) {
|
|
|
1170
1221
|
i++;
|
|
1171
1222
|
appPort = parseAppPort(args[i]);
|
|
1172
1223
|
} else {
|
|
1173
|
-
console.error(
|
|
1174
|
-
console.error(
|
|
1224
|
+
console.error(colors_default.red(`Error: Unknown flag "${args[i]}".`));
|
|
1225
|
+
console.error(colors_default.blue("Known flags: --force, --app-port"));
|
|
1175
1226
|
process.exit(1);
|
|
1176
1227
|
}
|
|
1177
1228
|
i++;
|
|
@@ -1181,31 +1232,31 @@ function parseAppArgs(args) {
|
|
|
1181
1232
|
}
|
|
1182
1233
|
function printHelp() {
|
|
1183
1234
|
console.log(`
|
|
1184
|
-
${
|
|
1235
|
+
${colors_default.bold("portless")} - Replace port numbers with stable, named .localhost URLs. For humans and agents.
|
|
1185
1236
|
|
|
1186
1237
|
Eliminates port conflicts, memorizing port numbers, and cookie/storage
|
|
1187
1238
|
clashes by giving each dev server a stable .localhost URL.
|
|
1188
1239
|
|
|
1189
|
-
${
|
|
1190
|
-
${
|
|
1240
|
+
${colors_default.bold("Install:")}
|
|
1241
|
+
${colors_default.cyan("npm install -g portless")}
|
|
1191
1242
|
Do NOT add portless as a project dependency.
|
|
1192
1243
|
|
|
1193
|
-
${
|
|
1194
|
-
${
|
|
1195
|
-
${
|
|
1196
|
-
${
|
|
1197
|
-
${
|
|
1198
|
-
${
|
|
1199
|
-
${
|
|
1200
|
-
${
|
|
1201
|
-
${
|
|
1202
|
-
${
|
|
1203
|
-
${
|
|
1204
|
-
${
|
|
1205
|
-
${
|
|
1206
|
-
${
|
|
1244
|
+
${colors_default.bold("Usage:")}
|
|
1245
|
+
${colors_default.cyan("portless proxy start")} Start the proxy (background daemon)
|
|
1246
|
+
${colors_default.cyan("portless proxy start --https")} Start with HTTP/2 + TLS (auto-generates certs)
|
|
1247
|
+
${colors_default.cyan("portless proxy start -p 80")} Start on port 80 (requires sudo)
|
|
1248
|
+
${colors_default.cyan("portless proxy stop")} Stop the proxy
|
|
1249
|
+
${colors_default.cyan("portless <name> <cmd>")} Run your app through the proxy
|
|
1250
|
+
${colors_default.cyan("portless run <cmd>")} Infer name from project, run through proxy
|
|
1251
|
+
${colors_default.cyan("portless get <name>")} Print URL for a service (for cross-service refs)
|
|
1252
|
+
${colors_default.cyan("portless alias <name> <port>")} Register a static route (e.g. for Docker)
|
|
1253
|
+
${colors_default.cyan("portless alias --remove <name>")} Remove a static route
|
|
1254
|
+
${colors_default.cyan("portless list")} Show active routes
|
|
1255
|
+
${colors_default.cyan("portless trust")} Add local CA to system trust store
|
|
1256
|
+
${colors_default.cyan("portless hosts sync")} Add routes to ${HOSTS_DISPLAY} (fixes Safari)
|
|
1257
|
+
${colors_default.cyan("portless hosts clean")} Remove portless entries from ${HOSTS_DISPLAY}
|
|
1207
1258
|
|
|
1208
|
-
${
|
|
1259
|
+
${colors_default.bold("Examples:")}
|
|
1209
1260
|
portless proxy start # Start proxy on port 1355
|
|
1210
1261
|
portless proxy start --https # Start with HTTPS/2 (faster page loads)
|
|
1211
1262
|
portless myapp next dev # -> http://myapp.localhost:1355
|
|
@@ -1216,14 +1267,14 @@ ${chalk.bold("Examples:")}
|
|
|
1216
1267
|
portless get backend # -> http://backend.localhost:1355 (for cross-service refs)
|
|
1217
1268
|
# Wildcard subdomains: tenant.myapp.localhost also routes to myapp
|
|
1218
1269
|
|
|
1219
|
-
${
|
|
1270
|
+
${colors_default.bold("In package.json:")}
|
|
1220
1271
|
{
|
|
1221
1272
|
"scripts": {
|
|
1222
1273
|
"dev": "portless run next dev"
|
|
1223
1274
|
}
|
|
1224
1275
|
}
|
|
1225
1276
|
|
|
1226
|
-
${
|
|
1277
|
+
${colors_default.bold("How it works:")}
|
|
1227
1278
|
1. Start the proxy once (listens on port 1355 by default, no sudo needed)
|
|
1228
1279
|
2. Run your apps - they auto-start the proxy and register automatically
|
|
1229
1280
|
(apps get a random port in the 4000-4999 range via PORT)
|
|
@@ -1232,12 +1283,12 @@ ${chalk.bold("How it works:")}
|
|
|
1232
1283
|
5. Frameworks that ignore PORT (Vite, Astro, React Router, Angular,
|
|
1233
1284
|
Expo, React Native) get --port and --host flags injected automatically
|
|
1234
1285
|
|
|
1235
|
-
${
|
|
1286
|
+
${colors_default.bold("HTTP/2 + HTTPS:")}
|
|
1236
1287
|
Use --https for HTTP/2 multiplexing (faster dev server page loads).
|
|
1237
1288
|
On first use, portless generates a local CA and adds it to your
|
|
1238
1289
|
system trust store. No browser warnings. No sudo required on macOS.
|
|
1239
1290
|
|
|
1240
|
-
${
|
|
1291
|
+
${colors_default.bold("Options:")}
|
|
1241
1292
|
run [--name <name>] <cmd> Infer project name (or override with --name)
|
|
1242
1293
|
Adds worktree prefix in git worktrees
|
|
1243
1294
|
-p, --port <number> Port for the proxy to listen on (default: 1355)
|
|
@@ -1248,38 +1299,40 @@ ${chalk.bold("Options:")}
|
|
|
1248
1299
|
--no-tls Disable HTTPS (overrides PORTLESS_HTTPS)
|
|
1249
1300
|
--foreground Run proxy in foreground (for debugging)
|
|
1250
1301
|
--tld <tld> Use a custom TLD instead of .localhost (e.g. test, dev)
|
|
1302
|
+
--wildcard Allow unregistered subdomains to fall back to parent route
|
|
1251
1303
|
--app-port <number> Use a fixed port for the app (skip auto-assignment)
|
|
1252
1304
|
--force Override an existing route registered by another process
|
|
1253
1305
|
--name <name> Use <name> as the app name (bypasses subcommand dispatch)
|
|
1254
1306
|
-- Stop flag parsing; everything after is passed to the child
|
|
1255
1307
|
|
|
1256
|
-
${
|
|
1308
|
+
${colors_default.bold("Environment variables:")}
|
|
1257
1309
|
PORTLESS_PORT=<number> Override the default proxy port (e.g. in .bashrc)
|
|
1258
1310
|
PORTLESS_APP_PORT=<number> Use a fixed port for the app (same as --app-port)
|
|
1259
1311
|
PORTLESS_HTTPS=1 Always enable HTTPS (set in .bashrc / .zshrc)
|
|
1260
1312
|
PORTLESS_TLD=<tld> Use a custom TLD (e.g. test, dev; default: localhost)
|
|
1313
|
+
PORTLESS_WILDCARD=1 Allow unregistered subdomains to fall back to parent route
|
|
1261
1314
|
PORTLESS_SYNC_HOSTS=1 Auto-sync ${HOSTS_DISPLAY} (auto-enabled for custom TLDs)
|
|
1262
1315
|
PORTLESS_STATE_DIR=<path> Override the state directory
|
|
1263
1316
|
PORTLESS=0 Run command directly without proxy
|
|
1264
1317
|
|
|
1265
|
-
${
|
|
1318
|
+
${colors_default.bold("Child process environment:")}
|
|
1266
1319
|
PORT Ephemeral port the child should listen on
|
|
1267
1320
|
HOST Always 127.0.0.1
|
|
1268
1321
|
PORTLESS_URL Public URL of the app (e.g. http://myapp.localhost:1355)
|
|
1269
1322
|
|
|
1270
|
-
${
|
|
1323
|
+
${colors_default.bold("Safari / DNS:")}
|
|
1271
1324
|
.localhost subdomains auto-resolve in Chrome, Firefox, and Edge.
|
|
1272
1325
|
Safari relies on the system DNS resolver, which may not handle them.
|
|
1273
1326
|
Auto-syncs ${HOSTS_DISPLAY} for custom TLDs (e.g. --tld test). For .localhost,
|
|
1274
1327
|
set PORTLESS_SYNC_HOSTS=1 to enable. To manually sync:
|
|
1275
|
-
${
|
|
1328
|
+
${colors_default.cyan(`${SUDO_PREFIX}portless hosts sync`)}
|
|
1276
1329
|
Clean up later with:
|
|
1277
|
-
${
|
|
1330
|
+
${colors_default.cyan(`${SUDO_PREFIX}portless hosts clean`)}
|
|
1278
1331
|
|
|
1279
|
-
${
|
|
1332
|
+
${colors_default.bold("Skip portless:")}
|
|
1280
1333
|
PORTLESS=0 pnpm dev # Runs command directly without proxy
|
|
1281
1334
|
|
|
1282
|
-
${
|
|
1335
|
+
${colors_default.bold("Reserved names:")}
|
|
1283
1336
|
run, get, alias, hosts, list, trust, proxy are subcommands and cannot
|
|
1284
1337
|
be used as app names directly. Use "portless run" to infer the name,
|
|
1285
1338
|
or "portless --name <name>" to force any name including reserved ones.
|
|
@@ -1287,20 +1340,20 @@ ${chalk.bold("Reserved names:")}
|
|
|
1287
1340
|
process.exit(0);
|
|
1288
1341
|
}
|
|
1289
1342
|
function printVersion() {
|
|
1290
|
-
console.log("0.
|
|
1343
|
+
console.log("0.8.0");
|
|
1291
1344
|
process.exit(0);
|
|
1292
1345
|
}
|
|
1293
1346
|
async function handleTrust() {
|
|
1294
1347
|
const { dir } = await discoverState();
|
|
1295
1348
|
const result = trustCA(dir);
|
|
1296
1349
|
if (result.trusted) {
|
|
1297
|
-
console.log(
|
|
1298
|
-
console.log(
|
|
1350
|
+
console.log(colors_default.green("Local CA added to system trust store."));
|
|
1351
|
+
console.log(colors_default.gray("Browsers will now trust portless HTTPS certificates."));
|
|
1299
1352
|
} else {
|
|
1300
|
-
console.error(
|
|
1353
|
+
console.error(colors_default.red(`Failed to trust CA: ${result.error}`));
|
|
1301
1354
|
if (result.error?.includes("sudo")) {
|
|
1302
|
-
console.error(
|
|
1303
|
-
console.error(
|
|
1355
|
+
console.error(colors_default.blue("Run with sudo:"));
|
|
1356
|
+
console.error(colors_default.cyan(" sudo portless trust"));
|
|
1304
1357
|
}
|
|
1305
1358
|
process.exit(1);
|
|
1306
1359
|
}
|
|
@@ -1308,17 +1361,17 @@ async function handleTrust() {
|
|
|
1308
1361
|
async function handleList() {
|
|
1309
1362
|
const { dir, port, tls: tls2 } = await discoverState();
|
|
1310
1363
|
const store = new RouteStore(dir, {
|
|
1311
|
-
onWarning: (msg) => console.warn(
|
|
1364
|
+
onWarning: (msg) => console.warn(colors_default.yellow(msg))
|
|
1312
1365
|
});
|
|
1313
1366
|
listRoutes(store, port, tls2);
|
|
1314
1367
|
}
|
|
1315
1368
|
async function handleGet(args) {
|
|
1316
1369
|
if (args[1] === "--help" || args[1] === "-h") {
|
|
1317
1370
|
console.log(`
|
|
1318
|
-
${
|
|
1371
|
+
${colors_default.bold("portless get")} - Print the URL for a service.
|
|
1319
1372
|
|
|
1320
|
-
${
|
|
1321
|
-
${
|
|
1373
|
+
${colors_default.bold("Usage:")}
|
|
1374
|
+
${colors_default.cyan("portless get <name>")}
|
|
1322
1375
|
|
|
1323
1376
|
Constructs the URL using the same hostname and worktree logic as
|
|
1324
1377
|
"portless run", then prints it to stdout. Useful for wiring services
|
|
@@ -1326,11 +1379,11 @@ together:
|
|
|
1326
1379
|
|
|
1327
1380
|
BACKEND_URL=$(portless get backend)
|
|
1328
1381
|
|
|
1329
|
-
${
|
|
1382
|
+
${colors_default.bold("Options:")}
|
|
1330
1383
|
--no-worktree Skip worktree prefix detection
|
|
1331
1384
|
--help, -h Show this help
|
|
1332
1385
|
|
|
1333
|
-
${
|
|
1386
|
+
${colors_default.bold("Examples:")}
|
|
1334
1387
|
portless get backend # -> http://backend.localhost:1355
|
|
1335
1388
|
portless get backend # in worktree -> http://auth.backend.localhost:1355
|
|
1336
1389
|
portless get backend --no-worktree # -> http://backend.localhost:1355 (skip worktree)
|
|
@@ -1343,19 +1396,19 @@ ${chalk.bold("Examples:")}
|
|
|
1343
1396
|
if (args[i] === "--no-worktree") {
|
|
1344
1397
|
skipWorktree = true;
|
|
1345
1398
|
} else if (args[i].startsWith("-")) {
|
|
1346
|
-
console.error(
|
|
1347
|
-
console.error(
|
|
1399
|
+
console.error(colors_default.red(`Error: Unknown flag "${args[i]}".`));
|
|
1400
|
+
console.error(colors_default.blue("Known flags: --no-worktree, --help"));
|
|
1348
1401
|
process.exit(1);
|
|
1349
1402
|
} else {
|
|
1350
1403
|
positional.push(args[i]);
|
|
1351
1404
|
}
|
|
1352
1405
|
}
|
|
1353
1406
|
if (positional.length === 0) {
|
|
1354
|
-
console.error(
|
|
1355
|
-
console.error(
|
|
1356
|
-
console.error(
|
|
1357
|
-
console.error(
|
|
1358
|
-
console.error(
|
|
1407
|
+
console.error(colors_default.red("Error: Missing service name."));
|
|
1408
|
+
console.error(colors_default.blue("Usage:"));
|
|
1409
|
+
console.error(colors_default.cyan(" portless get <name>"));
|
|
1410
|
+
console.error(colors_default.blue("Example:"));
|
|
1411
|
+
console.error(colors_default.cyan(" portless get backend"));
|
|
1359
1412
|
process.exit(1);
|
|
1360
1413
|
}
|
|
1361
1414
|
const name = positional[0];
|
|
@@ -1369,14 +1422,14 @@ ${chalk.bold("Examples:")}
|
|
|
1369
1422
|
async function handleAlias(args) {
|
|
1370
1423
|
if (args[1] === "--help" || args[1] === "-h") {
|
|
1371
1424
|
console.log(`
|
|
1372
|
-
${
|
|
1425
|
+
${colors_default.bold("portless alias")} - Register a static route for services not managed by portless.
|
|
1373
1426
|
|
|
1374
|
-
${
|
|
1375
|
-
${
|
|
1376
|
-
${
|
|
1377
|
-
${
|
|
1427
|
+
${colors_default.bold("Usage:")}
|
|
1428
|
+
${colors_default.cyan("portless alias <name> <port>")} Register a route
|
|
1429
|
+
${colors_default.cyan("portless alias --remove <name>")} Remove a route
|
|
1430
|
+
${colors_default.cyan("portless alias <name> <port> --force")} Override existing route
|
|
1378
1431
|
|
|
1379
|
-
${
|
|
1432
|
+
${colors_default.bold("Examples:")}
|
|
1380
1433
|
portless alias my-postgres 5432 # -> http://my-postgres.localhost:1355
|
|
1381
1434
|
portless alias redis 6379 # -> http://redis.localhost:1355
|
|
1382
1435
|
portless alias --remove my-postgres # Remove the alias
|
|
@@ -1385,60 +1438,60 @@ ${chalk.bold("Examples:")}
|
|
|
1385
1438
|
}
|
|
1386
1439
|
const { dir, tld } = await discoverState();
|
|
1387
1440
|
const store = new RouteStore(dir, {
|
|
1388
|
-
onWarning: (msg) => console.warn(
|
|
1441
|
+
onWarning: (msg) => console.warn(colors_default.yellow(msg))
|
|
1389
1442
|
});
|
|
1390
1443
|
if (args[1] === "--remove") {
|
|
1391
1444
|
const aliasName2 = args[2];
|
|
1392
1445
|
if (!aliasName2) {
|
|
1393
|
-
console.error(
|
|
1394
|
-
console.error(
|
|
1446
|
+
console.error(colors_default.red("Error: No alias name provided."));
|
|
1447
|
+
console.error(colors_default.cyan(" portless alias --remove <name>"));
|
|
1395
1448
|
process.exit(1);
|
|
1396
1449
|
}
|
|
1397
1450
|
const hostname2 = parseHostname(aliasName2, tld);
|
|
1398
1451
|
const routes = store.loadRoutes();
|
|
1399
1452
|
const existing = routes.find((r) => r.hostname === hostname2 && r.pid === 0);
|
|
1400
1453
|
if (!existing) {
|
|
1401
|
-
console.error(
|
|
1454
|
+
console.error(colors_default.red(`Error: No alias found for "${hostname2}".`));
|
|
1402
1455
|
process.exit(1);
|
|
1403
1456
|
}
|
|
1404
1457
|
store.removeRoute(hostname2);
|
|
1405
|
-
console.log(
|
|
1458
|
+
console.log(colors_default.green(`Removed alias: ${hostname2}`));
|
|
1406
1459
|
return;
|
|
1407
1460
|
}
|
|
1408
1461
|
const aliasName = args[1];
|
|
1409
1462
|
const aliasPort = args[2];
|
|
1410
1463
|
if (!aliasName || !aliasPort) {
|
|
1411
|
-
console.error(
|
|
1412
|
-
console.error(
|
|
1413
|
-
console.error(
|
|
1414
|
-
console.error(
|
|
1415
|
-
console.error(
|
|
1416
|
-
console.error(
|
|
1464
|
+
console.error(colors_default.red("Error: Missing arguments."));
|
|
1465
|
+
console.error(colors_default.blue("Usage:"));
|
|
1466
|
+
console.error(colors_default.cyan(" portless alias <name> <port>"));
|
|
1467
|
+
console.error(colors_default.cyan(" portless alias --remove <name>"));
|
|
1468
|
+
console.error(colors_default.blue("Example:"));
|
|
1469
|
+
console.error(colors_default.cyan(" portless alias my-postgres 5432"));
|
|
1417
1470
|
process.exit(1);
|
|
1418
1471
|
}
|
|
1419
1472
|
const hostname = parseHostname(aliasName, tld);
|
|
1420
1473
|
const port = parseInt(aliasPort, 10);
|
|
1421
1474
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
1422
|
-
console.error(
|
|
1475
|
+
console.error(colors_default.red(`Error: Invalid port "${aliasPort}". Must be 1-65535.`));
|
|
1423
1476
|
process.exit(1);
|
|
1424
1477
|
}
|
|
1425
1478
|
const force = args.includes("--force");
|
|
1426
1479
|
store.addRoute(hostname, port, 0, force);
|
|
1427
|
-
console.log(
|
|
1480
|
+
console.log(colors_default.green(`Alias registered: ${hostname} -> 127.0.0.1:${port}`));
|
|
1428
1481
|
}
|
|
1429
1482
|
async function handleHosts(args) {
|
|
1430
1483
|
if (args[1] === "--help" || args[1] === "-h") {
|
|
1431
1484
|
console.log(`
|
|
1432
|
-
${
|
|
1485
|
+
${colors_default.bold("portless hosts")} - Manage ${HOSTS_DISPLAY} entries for .localhost subdomains.
|
|
1433
1486
|
|
|
1434
1487
|
Safari relies on the system DNS resolver, which may not handle .localhost
|
|
1435
1488
|
subdomains. This command adds entries to ${HOSTS_DISPLAY} as a workaround.
|
|
1436
1489
|
|
|
1437
|
-
${
|
|
1438
|
-
${
|
|
1439
|
-
${
|
|
1490
|
+
${colors_default.bold("Usage:")}
|
|
1491
|
+
${colors_default.cyan(`${SUDO_PREFIX}portless hosts sync`)} Add current routes to ${HOSTS_DISPLAY}
|
|
1492
|
+
${colors_default.cyan(`${SUDO_PREFIX}portless hosts clean`)} Remove portless entries from ${HOSTS_DISPLAY}
|
|
1440
1493
|
|
|
1441
|
-
${
|
|
1494
|
+
${colors_default.bold("Auto-sync:")}
|
|
1442
1495
|
Auto-enabled for custom TLDs (e.g. --tld test). For .localhost, set
|
|
1443
1496
|
PORTLESS_SYNC_HOSTS=1 to enable. Disable with PORTLESS_SYNC_HOSTS=0.
|
|
1444
1497
|
`);
|
|
@@ -1446,58 +1499,58 @@ ${chalk.bold("Auto-sync:")}
|
|
|
1446
1499
|
}
|
|
1447
1500
|
if (args[1] === "clean") {
|
|
1448
1501
|
if (cleanHostsFile()) {
|
|
1449
|
-
console.log(
|
|
1502
|
+
console.log(colors_default.green(`Removed portless entries from ${HOSTS_DISPLAY}.`));
|
|
1450
1503
|
} else {
|
|
1451
1504
|
console.error(
|
|
1452
|
-
|
|
1505
|
+
colors_default.red(
|
|
1453
1506
|
`Failed to update ${HOSTS_DISPLAY}${isWindows ? " (run as Administrator)." : " (requires sudo)."}`
|
|
1454
1507
|
)
|
|
1455
1508
|
);
|
|
1456
|
-
console.error(
|
|
1509
|
+
console.error(colors_default.cyan(` ${SUDO_PREFIX}portless hosts clean`));
|
|
1457
1510
|
process.exit(1);
|
|
1458
1511
|
}
|
|
1459
1512
|
return;
|
|
1460
1513
|
}
|
|
1461
1514
|
if (!args[1]) {
|
|
1462
1515
|
console.log(`
|
|
1463
|
-
${
|
|
1516
|
+
${colors_default.bold("Usage: portless hosts <command>")}
|
|
1464
1517
|
|
|
1465
|
-
${
|
|
1466
|
-
${
|
|
1518
|
+
${colors_default.cyan(`${SUDO_PREFIX}portless hosts sync`)} Add current routes to ${HOSTS_DISPLAY}
|
|
1519
|
+
${colors_default.cyan(`${SUDO_PREFIX}portless hosts clean`)} Remove portless entries from ${HOSTS_DISPLAY}
|
|
1467
1520
|
`);
|
|
1468
1521
|
process.exit(0);
|
|
1469
1522
|
}
|
|
1470
1523
|
if (args[1] !== "sync") {
|
|
1471
|
-
console.error(
|
|
1472
|
-
console.error(
|
|
1524
|
+
console.error(colors_default.red(`Error: Unknown hosts subcommand "${args[1]}".`));
|
|
1525
|
+
console.error(colors_default.blue("Usage:"));
|
|
1473
1526
|
console.error(
|
|
1474
|
-
|
|
1527
|
+
colors_default.cyan(` ${SUDO_PREFIX}portless hosts sync # Add routes to ${HOSTS_DISPLAY}`)
|
|
1475
1528
|
);
|
|
1476
|
-
console.error(
|
|
1529
|
+
console.error(colors_default.cyan(` ${SUDO_PREFIX}portless hosts clean # Remove portless entries`));
|
|
1477
1530
|
process.exit(1);
|
|
1478
1531
|
}
|
|
1479
1532
|
const { dir } = await discoverState();
|
|
1480
1533
|
const store = new RouteStore(dir, {
|
|
1481
|
-
onWarning: (msg) => console.warn(
|
|
1534
|
+
onWarning: (msg) => console.warn(colors_default.yellow(msg))
|
|
1482
1535
|
});
|
|
1483
1536
|
const routes = store.loadRoutes();
|
|
1484
1537
|
if (routes.length === 0) {
|
|
1485
|
-
console.log(
|
|
1538
|
+
console.log(colors_default.yellow("No active routes to sync."));
|
|
1486
1539
|
return;
|
|
1487
1540
|
}
|
|
1488
1541
|
const hostnames = routes.map((r) => r.hostname);
|
|
1489
1542
|
if (syncHostsFile(hostnames)) {
|
|
1490
|
-
console.log(
|
|
1543
|
+
console.log(colors_default.green(`Synced ${hostnames.length} hostname(s) to ${HOSTS_DISPLAY}:`));
|
|
1491
1544
|
for (const h of hostnames) {
|
|
1492
|
-
console.log(
|
|
1545
|
+
console.log(colors_default.cyan(` 127.0.0.1 ${h}`));
|
|
1493
1546
|
}
|
|
1494
1547
|
} else {
|
|
1495
1548
|
console.error(
|
|
1496
|
-
|
|
1549
|
+
colors_default.red(
|
|
1497
1550
|
`Failed to update ${HOSTS_DISPLAY}${isWindows ? " (run as Administrator)." : " (requires sudo)."}`
|
|
1498
1551
|
)
|
|
1499
1552
|
);
|
|
1500
|
-
console.error(
|
|
1553
|
+
console.error(colors_default.cyan(` ${SUDO_PREFIX}portless hosts sync`));
|
|
1501
1554
|
process.exit(1);
|
|
1502
1555
|
}
|
|
1503
1556
|
}
|
|
@@ -1505,7 +1558,7 @@ async function handleProxy(args) {
|
|
|
1505
1558
|
if (args[1] === "stop") {
|
|
1506
1559
|
const { dir, port, tls: tls2 } = await discoverState();
|
|
1507
1560
|
const store2 = new RouteStore(dir, {
|
|
1508
|
-
onWarning: (msg) => console.warn(
|
|
1561
|
+
onWarning: (msg) => console.warn(colors_default.yellow(msg))
|
|
1509
1562
|
});
|
|
1510
1563
|
await stopProxy(store2, port, tls2);
|
|
1511
1564
|
return;
|
|
@@ -1513,15 +1566,16 @@ async function handleProxy(args) {
|
|
|
1513
1566
|
const isProxyHelp = args[1] === "--help" || args[1] === "-h";
|
|
1514
1567
|
if (isProxyHelp || args[1] !== "start") {
|
|
1515
1568
|
console.log(`
|
|
1516
|
-
${
|
|
1569
|
+
${colors_default.bold("portless proxy")} - Manage the portless proxy server.
|
|
1517
1570
|
|
|
1518
|
-
${
|
|
1519
|
-
${
|
|
1520
|
-
${
|
|
1521
|
-
${
|
|
1522
|
-
${
|
|
1523
|
-
${
|
|
1524
|
-
${
|
|
1571
|
+
${colors_default.bold("Usage:")}
|
|
1572
|
+
${colors_default.cyan("portless proxy start")} Start the proxy (daemon)
|
|
1573
|
+
${colors_default.cyan("portless proxy start --https")} Start with HTTP/2 + TLS
|
|
1574
|
+
${colors_default.cyan("portless proxy start --foreground")} Start in foreground (for debugging)
|
|
1575
|
+
${colors_default.cyan("portless proxy start -p 80")} Start on port 80 (requires sudo)
|
|
1576
|
+
${colors_default.cyan("portless proxy start --tld test")} Use .test instead of .localhost
|
|
1577
|
+
${colors_default.cyan("portless proxy start --wildcard")} Allow unregistered subdomains to fall back to parent
|
|
1578
|
+
${colors_default.cyan("portless proxy stop")} Stop the proxy
|
|
1525
1579
|
`);
|
|
1526
1580
|
process.exit(isProxyHelp || !args[1] ? 0 : 1);
|
|
1527
1581
|
}
|
|
@@ -1532,15 +1586,15 @@ ${chalk.bold("Usage:")}
|
|
|
1532
1586
|
if (portFlagIndex !== -1) {
|
|
1533
1587
|
const portValue = args[portFlagIndex + 1];
|
|
1534
1588
|
if (!portValue || portValue.startsWith("-")) {
|
|
1535
|
-
console.error(
|
|
1536
|
-
console.error(
|
|
1537
|
-
console.error(
|
|
1589
|
+
console.error(colors_default.red("Error: --port / -p requires a port number."));
|
|
1590
|
+
console.error(colors_default.blue("Usage:"));
|
|
1591
|
+
console.error(colors_default.cyan(" portless proxy start -p 8080"));
|
|
1538
1592
|
process.exit(1);
|
|
1539
1593
|
}
|
|
1540
1594
|
proxyPort = parseInt(portValue, 10);
|
|
1541
1595
|
if (isNaN(proxyPort) || proxyPort < 1 || proxyPort > 65535) {
|
|
1542
|
-
console.error(
|
|
1543
|
-
console.error(
|
|
1596
|
+
console.error(colors_default.red(`Error: Invalid port number: ${portValue}`));
|
|
1597
|
+
console.error(colors_default.blue("Port must be between 1 and 65535."));
|
|
1544
1598
|
process.exit(1);
|
|
1545
1599
|
}
|
|
1546
1600
|
}
|
|
@@ -1553,7 +1607,7 @@ ${chalk.bold("Usage:")}
|
|
|
1553
1607
|
if (certIdx !== -1) {
|
|
1554
1608
|
customCertPath = args[certIdx + 1] || null;
|
|
1555
1609
|
if (!customCertPath || customCertPath.startsWith("-")) {
|
|
1556
|
-
console.error(
|
|
1610
|
+
console.error(colors_default.red("Error: --cert requires a file path."));
|
|
1557
1611
|
process.exit(1);
|
|
1558
1612
|
}
|
|
1559
1613
|
}
|
|
@@ -1561,53 +1615,54 @@ ${chalk.bold("Usage:")}
|
|
|
1561
1615
|
if (keyIdx !== -1) {
|
|
1562
1616
|
customKeyPath = args[keyIdx + 1] || null;
|
|
1563
1617
|
if (!customKeyPath || customKeyPath.startsWith("-")) {
|
|
1564
|
-
console.error(
|
|
1618
|
+
console.error(colors_default.red("Error: --key requires a file path."));
|
|
1565
1619
|
process.exit(1);
|
|
1566
1620
|
}
|
|
1567
1621
|
}
|
|
1568
1622
|
if (customCertPath && !customKeyPath || !customCertPath && customKeyPath) {
|
|
1569
|
-
console.error(
|
|
1623
|
+
console.error(colors_default.red("Error: --cert and --key must be used together."));
|
|
1570
1624
|
process.exit(1);
|
|
1571
1625
|
}
|
|
1572
1626
|
let tld;
|
|
1573
1627
|
try {
|
|
1574
1628
|
tld = getDefaultTld();
|
|
1575
1629
|
} catch (err) {
|
|
1576
|
-
console.error(
|
|
1630
|
+
console.error(colors_default.red(`Error: ${err.message}`));
|
|
1577
1631
|
process.exit(1);
|
|
1578
1632
|
}
|
|
1579
1633
|
const tldIdx = args.indexOf("--tld");
|
|
1580
1634
|
if (tldIdx !== -1) {
|
|
1581
1635
|
const tldValue = args[tldIdx + 1];
|
|
1582
1636
|
if (!tldValue || tldValue.startsWith("-")) {
|
|
1583
|
-
console.error(
|
|
1637
|
+
console.error(colors_default.red("Error: --tld requires a TLD value (e.g. test, localhost)."));
|
|
1584
1638
|
process.exit(1);
|
|
1585
1639
|
}
|
|
1586
1640
|
tld = tldValue.trim().toLowerCase();
|
|
1587
1641
|
const tldErr = validateTld(tld);
|
|
1588
1642
|
if (tldErr) {
|
|
1589
|
-
console.error(
|
|
1643
|
+
console.error(colors_default.red(`Error: ${tldErr}`));
|
|
1590
1644
|
process.exit(1);
|
|
1591
1645
|
}
|
|
1592
1646
|
}
|
|
1593
1647
|
const riskyReason = RISKY_TLDS.get(tld);
|
|
1594
1648
|
if (riskyReason) {
|
|
1595
|
-
console.warn(
|
|
1649
|
+
console.warn(colors_default.yellow(`Warning: .${tld} -- ${riskyReason}`));
|
|
1596
1650
|
}
|
|
1597
1651
|
const syncDisabled = process.env.PORTLESS_SYNC_HOSTS === "0" || process.env.PORTLESS_SYNC_HOSTS === "false";
|
|
1598
1652
|
if (tld !== DEFAULT_TLD && syncDisabled) {
|
|
1599
1653
|
console.warn(
|
|
1600
|
-
|
|
1654
|
+
colors_default.yellow(
|
|
1601
1655
|
`Warning: .${tld} domains require ${HOSTS_DISPLAY} entries to resolve to 127.0.0.1.`
|
|
1602
1656
|
)
|
|
1603
1657
|
);
|
|
1604
|
-
console.warn(
|
|
1605
|
-
console.warn(
|
|
1658
|
+
console.warn(colors_default.yellow("Hosts sync is disabled. To add entries manually, run:"));
|
|
1659
|
+
console.warn(colors_default.cyan(` ${SUDO_PREFIX}portless hosts sync`));
|
|
1606
1660
|
}
|
|
1661
|
+
const useWildcard = args.includes("--wildcard") || isWildcardEnvEnabled();
|
|
1607
1662
|
const useHttps = wantHttps || !!(customCertPath && customKeyPath);
|
|
1608
1663
|
const stateDir = resolveStateDir(proxyPort);
|
|
1609
1664
|
const store = new RouteStore(stateDir, {
|
|
1610
|
-
onWarning: (msg) => console.warn(
|
|
1665
|
+
onWarning: (msg) => console.warn(colors_default.yellow(msg))
|
|
1611
1666
|
});
|
|
1612
1667
|
if (await isProxyRunning(proxyPort)) {
|
|
1613
1668
|
if (isForeground) {
|
|
@@ -1616,20 +1671,20 @@ ${chalk.bold("Usage:")}
|
|
|
1616
1671
|
const needsSudo = !isWindows && proxyPort < PRIVILEGED_PORT_THRESHOLD;
|
|
1617
1672
|
const sudoPrefix = needsSudo ? "sudo " : "";
|
|
1618
1673
|
const portFlag = proxyPort !== getDefaultPort() ? ` -p ${proxyPort}` : "";
|
|
1619
|
-
console.log(
|
|
1674
|
+
console.log(colors_default.yellow(`Proxy is already running on port ${proxyPort}.`));
|
|
1620
1675
|
console.log(
|
|
1621
|
-
|
|
1676
|
+
colors_default.blue(
|
|
1622
1677
|
`To restart: ${sudoPrefix}portless proxy stop${portFlag} && ${sudoPrefix}portless proxy start${portFlag}`
|
|
1623
1678
|
)
|
|
1624
1679
|
);
|
|
1625
1680
|
return;
|
|
1626
1681
|
}
|
|
1627
1682
|
if (!isWindows && proxyPort < PRIVILEGED_PORT_THRESHOLD && (process.getuid?.() ?? -1) !== 0) {
|
|
1628
|
-
console.error(
|
|
1629
|
-
console.error(
|
|
1630
|
-
console.error(
|
|
1631
|
-
console.error(
|
|
1632
|
-
console.error(
|
|
1683
|
+
console.error(colors_default.red(`Error: Port ${proxyPort} requires sudo.`));
|
|
1684
|
+
console.error(colors_default.blue("Either run with sudo:"));
|
|
1685
|
+
console.error(colors_default.cyan(" sudo portless proxy start -p 80"));
|
|
1686
|
+
console.error(colors_default.blue("Or use the default port (no sudo needed):"));
|
|
1687
|
+
console.error(colors_default.cyan(" portless proxy start"));
|
|
1633
1688
|
process.exit(1);
|
|
1634
1689
|
}
|
|
1635
1690
|
let tlsOptions;
|
|
@@ -1642,43 +1697,43 @@ ${chalk.bold("Usage:")}
|
|
|
1642
1697
|
const certStr = cert.toString("utf-8");
|
|
1643
1698
|
const keyStr = key.toString("utf-8");
|
|
1644
1699
|
if (!certStr.includes("-----BEGIN CERTIFICATE-----")) {
|
|
1645
|
-
console.error(
|
|
1646
|
-
console.error(
|
|
1700
|
+
console.error(colors_default.red(`Error: ${customCertPath} is not a valid PEM certificate.`));
|
|
1701
|
+
console.error(colors_default.gray("Expected a file starting with -----BEGIN CERTIFICATE-----"));
|
|
1647
1702
|
process.exit(1);
|
|
1648
1703
|
}
|
|
1649
1704
|
if (!keyStr.match(/-----BEGIN [\w\s]*PRIVATE KEY-----/)) {
|
|
1650
|
-
console.error(
|
|
1651
|
-
console.error(
|
|
1705
|
+
console.error(colors_default.red(`Error: ${customKeyPath} is not a valid PEM private key.`));
|
|
1706
|
+
console.error(colors_default.gray("Expected a file starting with -----BEGIN ...PRIVATE KEY-----"));
|
|
1652
1707
|
process.exit(1);
|
|
1653
1708
|
}
|
|
1654
1709
|
tlsOptions = { cert, key };
|
|
1655
1710
|
} catch (err) {
|
|
1656
1711
|
const message = err instanceof Error ? err.message : String(err);
|
|
1657
|
-
console.error(
|
|
1712
|
+
console.error(colors_default.red(`Error reading certificate files: ${message}`));
|
|
1658
1713
|
process.exit(1);
|
|
1659
1714
|
}
|
|
1660
1715
|
} else {
|
|
1661
|
-
console.log(
|
|
1716
|
+
console.log(colors_default.gray("Ensuring TLS certificates..."));
|
|
1662
1717
|
const certs = ensureCerts(stateDir);
|
|
1663
1718
|
if (certs.caGenerated) {
|
|
1664
|
-
console.log(
|
|
1719
|
+
console.log(colors_default.green("Generated local CA certificate."));
|
|
1665
1720
|
}
|
|
1666
1721
|
if (!isCATrusted(stateDir)) {
|
|
1667
|
-
console.log(
|
|
1722
|
+
console.log(colors_default.yellow("Adding CA to system trust store..."));
|
|
1668
1723
|
const trustResult = trustCA(stateDir);
|
|
1669
1724
|
if (trustResult.trusted) {
|
|
1670
1725
|
console.log(
|
|
1671
|
-
|
|
1726
|
+
colors_default.green("CA added to system trust store. Browsers will trust portless certs.")
|
|
1672
1727
|
);
|
|
1673
1728
|
} else {
|
|
1674
|
-
console.warn(
|
|
1729
|
+
console.warn(colors_default.yellow("Could not add CA to system trust store."));
|
|
1675
1730
|
if (trustResult.error) {
|
|
1676
|
-
console.warn(
|
|
1731
|
+
console.warn(colors_default.gray(trustResult.error));
|
|
1677
1732
|
}
|
|
1678
1733
|
console.warn(
|
|
1679
|
-
|
|
1734
|
+
colors_default.yellow("Browsers will show certificate warnings. To fix this later, run:")
|
|
1680
1735
|
);
|
|
1681
|
-
console.warn(
|
|
1736
|
+
console.warn(colors_default.cyan(" portless trust"));
|
|
1682
1737
|
}
|
|
1683
1738
|
}
|
|
1684
1739
|
const cert = fs3.readFileSync(certs.certPath);
|
|
@@ -1691,8 +1746,8 @@ ${chalk.bold("Usage:")}
|
|
|
1691
1746
|
}
|
|
1692
1747
|
}
|
|
1693
1748
|
if (isForeground) {
|
|
1694
|
-
console.log(
|
|
1695
|
-
startProxyServer(store, proxyPort, tld, tlsOptions);
|
|
1749
|
+
console.log(colors_default.blue.bold("\nportless proxy\n"));
|
|
1750
|
+
startProxyServer(store, proxyPort, tld, tlsOptions, useWildcard ? false : void 0);
|
|
1696
1751
|
return;
|
|
1697
1752
|
}
|
|
1698
1753
|
store.ensureDir();
|
|
@@ -1718,6 +1773,9 @@ ${chalk.bold("Usage:")}
|
|
|
1718
1773
|
if (tld !== DEFAULT_TLD) {
|
|
1719
1774
|
daemonArgs.push("--tld", tld);
|
|
1720
1775
|
}
|
|
1776
|
+
if (useWildcard) {
|
|
1777
|
+
daemonArgs.push("--wildcard");
|
|
1778
|
+
}
|
|
1721
1779
|
const child = spawn(process.execPath, daemonArgs, {
|
|
1722
1780
|
detached: true,
|
|
1723
1781
|
stdio: ["ignore", logFd, logFd],
|
|
@@ -1729,32 +1787,32 @@ ${chalk.bold("Usage:")}
|
|
|
1729
1787
|
fs3.closeSync(logFd);
|
|
1730
1788
|
}
|
|
1731
1789
|
if (!await waitForProxy(proxyPort, void 0, void 0, useHttps)) {
|
|
1732
|
-
console.error(
|
|
1733
|
-
console.error(
|
|
1790
|
+
console.error(colors_default.red("Proxy failed to start (timed out waiting for it to listen)."));
|
|
1791
|
+
console.error(colors_default.blue("Try starting the proxy in the foreground to see the error:"));
|
|
1734
1792
|
const needsSudo = !isWindows && proxyPort < PRIVILEGED_PORT_THRESHOLD;
|
|
1735
|
-
console.error(
|
|
1793
|
+
console.error(colors_default.cyan(` ${needsSudo ? "sudo " : ""}portless proxy start --foreground`));
|
|
1736
1794
|
if (fs3.existsSync(logPath)) {
|
|
1737
|
-
console.error(
|
|
1795
|
+
console.error(colors_default.gray(`Logs: ${logPath}`));
|
|
1738
1796
|
}
|
|
1739
1797
|
process.exit(1);
|
|
1740
1798
|
}
|
|
1741
1799
|
const proto = useHttps ? "HTTPS/2" : "HTTP";
|
|
1742
|
-
console.log(
|
|
1800
|
+
console.log(colors_default.green(`${proto} proxy started on port ${proxyPort}`));
|
|
1743
1801
|
}
|
|
1744
1802
|
async function handleRunMode(args) {
|
|
1745
1803
|
const parsed = parseRunArgs(args);
|
|
1746
1804
|
if (parsed.commandArgs.length === 0) {
|
|
1747
|
-
console.error(
|
|
1748
|
-
console.error(
|
|
1749
|
-
console.error(
|
|
1750
|
-
console.error(
|
|
1751
|
-
console.error(
|
|
1805
|
+
console.error(colors_default.red("Error: No command provided."));
|
|
1806
|
+
console.error(colors_default.blue("Usage:"));
|
|
1807
|
+
console.error(colors_default.cyan(" portless run <command...>"));
|
|
1808
|
+
console.error(colors_default.blue("Example:"));
|
|
1809
|
+
console.error(colors_default.cyan(" portless run next dev"));
|
|
1752
1810
|
process.exit(1);
|
|
1753
1811
|
}
|
|
1754
1812
|
let baseName;
|
|
1755
1813
|
let nameSource;
|
|
1756
1814
|
if (parsed.name) {
|
|
1757
|
-
baseName = parsed.name;
|
|
1815
|
+
baseName = parsed.name.split(".").map((label) => truncateLabel(label)).join(".");
|
|
1758
1816
|
nameSource = "--name flag";
|
|
1759
1817
|
} else {
|
|
1760
1818
|
const inferred = inferProjectName();
|
|
@@ -1765,7 +1823,7 @@ async function handleRunMode(args) {
|
|
|
1765
1823
|
const effectiveName = worktree ? `${worktree.prefix}.${baseName}` : baseName;
|
|
1766
1824
|
const { dir, port, tls: tls2, tld } = await discoverState();
|
|
1767
1825
|
const store = new RouteStore(dir, {
|
|
1768
|
-
onWarning: (msg) => console.warn(
|
|
1826
|
+
onWarning: (msg) => console.warn(colors_default.yellow(msg))
|
|
1769
1827
|
});
|
|
1770
1828
|
await runApp(
|
|
1771
1829
|
store,
|
|
@@ -1783,22 +1841,23 @@ async function handleRunMode(args) {
|
|
|
1783
1841
|
async function handleNamedMode(args) {
|
|
1784
1842
|
const parsed = parseAppArgs(args);
|
|
1785
1843
|
if (parsed.commandArgs.length === 0) {
|
|
1786
|
-
console.error(
|
|
1787
|
-
console.error(
|
|
1788
|
-
console.error(
|
|
1789
|
-
console.error(
|
|
1790
|
-
console.error(
|
|
1844
|
+
console.error(colors_default.red("Error: No command provided."));
|
|
1845
|
+
console.error(colors_default.blue("Usage:"));
|
|
1846
|
+
console.error(colors_default.cyan(" portless <name> <command...>"));
|
|
1847
|
+
console.error(colors_default.blue("Example:"));
|
|
1848
|
+
console.error(colors_default.cyan(" portless myapp next dev"));
|
|
1791
1849
|
process.exit(1);
|
|
1792
1850
|
}
|
|
1851
|
+
const safeName = parsed.name.split(".").map((label) => truncateLabel(label)).join(".");
|
|
1793
1852
|
const { dir, port, tls: tls2, tld } = await discoverState();
|
|
1794
1853
|
const store = new RouteStore(dir, {
|
|
1795
|
-
onWarning: (msg) => console.warn(
|
|
1854
|
+
onWarning: (msg) => console.warn(colors_default.yellow(msg))
|
|
1796
1855
|
});
|
|
1797
1856
|
await runApp(
|
|
1798
1857
|
store,
|
|
1799
1858
|
port,
|
|
1800
1859
|
dir,
|
|
1801
|
-
|
|
1860
|
+
safeName,
|
|
1802
1861
|
parsed.commandArgs,
|
|
1803
1862
|
tls2,
|
|
1804
1863
|
tld,
|
|
@@ -1820,23 +1879,23 @@ async function main() {
|
|
|
1820
1879
|
const isNpx = process.env.npm_command === "exec" && !process.env.npm_lifecycle_event;
|
|
1821
1880
|
const isPnpmDlx = !!process.env.PNPM_SCRIPT_SRC_DIR && !process.env.npm_lifecycle_event;
|
|
1822
1881
|
if (isNpx || isPnpmDlx) {
|
|
1823
|
-
console.error(
|
|
1824
|
-
console.error(
|
|
1825
|
-
console.error(
|
|
1882
|
+
console.error(colors_default.red("Error: portless should not be run via npx or pnpm dlx."));
|
|
1883
|
+
console.error(colors_default.blue("Install globally instead:"));
|
|
1884
|
+
console.error(colors_default.cyan(" npm install -g portless"));
|
|
1826
1885
|
process.exit(1);
|
|
1827
1886
|
}
|
|
1828
1887
|
if (args[0] === "--name") {
|
|
1829
1888
|
args.shift();
|
|
1830
1889
|
if (!args[0]) {
|
|
1831
|
-
console.error(
|
|
1832
|
-
console.error(
|
|
1890
|
+
console.error(colors_default.red("Error: --name requires an app name."));
|
|
1891
|
+
console.error(colors_default.cyan(" portless --name <name> <command...>"));
|
|
1833
1892
|
process.exit(1);
|
|
1834
1893
|
}
|
|
1835
1894
|
const skipPortless2 = process.env.PORTLESS === "0" || process.env.PORTLESS === "false" || process.env.PORTLESS === "skip";
|
|
1836
1895
|
if (skipPortless2) {
|
|
1837
1896
|
const { commandArgs } = parseAppArgs(args);
|
|
1838
1897
|
if (commandArgs.length === 0) {
|
|
1839
|
-
console.error(
|
|
1898
|
+
console.error(colors_default.red("Error: No command provided."));
|
|
1840
1899
|
process.exit(1);
|
|
1841
1900
|
}
|
|
1842
1901
|
spawnCommand(commandArgs);
|
|
@@ -1853,7 +1912,7 @@ async function main() {
|
|
|
1853
1912
|
if (skipPortless && (isRunCommand || args.length >= 2 && args[0] !== "proxy")) {
|
|
1854
1913
|
const { commandArgs } = isRunCommand ? parseRunArgs(args) : parseAppArgs(args);
|
|
1855
1914
|
if (commandArgs.length === 0) {
|
|
1856
|
-
console.error(
|
|
1915
|
+
console.error(colors_default.red("Error: No command provided."));
|
|
1857
1916
|
process.exit(1);
|
|
1858
1917
|
}
|
|
1859
1918
|
spawnCommand(commandArgs);
|
|
@@ -1901,6 +1960,6 @@ async function main() {
|
|
|
1901
1960
|
}
|
|
1902
1961
|
main().catch((err) => {
|
|
1903
1962
|
const message = err instanceof Error ? err.message : String(err);
|
|
1904
|
-
console.error(
|
|
1963
|
+
console.error(colors_default.red("Error:"), message);
|
|
1905
1964
|
process.exit(1);
|
|
1906
1965
|
});
|