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