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/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-ROBZDJST.js";
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
- "-CAcreateserial",
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
- "-CAcreateserial",
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(chalk.yellow("fs.watch unavailable; falling back to polling for route changes"));
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
- onError: (msg) => console.error(chalk.red(msg)),
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(chalk.red(`Port ${proxyPort} is already in use.`));
737
- console.error(chalk.blue("Stop the existing proxy first:"));
738
- console.error(chalk.cyan(" portless proxy stop"));
739
- console.error(chalk.blue("Or check what is using the port:"));
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
- chalk.cyan(
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(chalk.red(`Permission denied for port ${proxyPort}.`));
747
- console.error(chalk.blue("Either run with sudo:"));
748
- console.error(chalk.cyan(" sudo portless proxy start -p 80"));
749
- console.error(chalk.blue("Or use a non-privileged port (no sudo needed):"));
750
- console.error(chalk.cyan(" portless proxy start"));
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(chalk.red(`Proxy error: ${err.message}`));
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
- console.log(chalk.green(`${proto} proxy listening on port ${proxyPort}${tldLabel}`));
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(chalk.cyan("\nProxy is running. Press Ctrl+C to stop.\n"));
792
- console.log(chalk.gray(`Routes file: ${store.getRoutesPath()}`));
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(chalk.yellow(`PID file is missing but port ${proxyPort} is still in use.`));
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(chalk.green(`Killed process ${pid}. Proxy stopped.`));
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
- chalk.red("Permission denied. The proxy was started with elevated privileges.")
858
+ colors_default.red("Permission denied. The proxy was started with elevated privileges.")
814
859
  );
815
- console.error(chalk.blue("Stop it with:"));
860
+ console.error(colors_default.blue("Stop it with:"));
816
861
  console.error(
817
- chalk.cyan(
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(chalk.red(`Failed to stop proxy: ${message}`));
824
- console.error(chalk.blue("Check if the process is still running:"));
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
- chalk.cyan(
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(chalk.red("Cannot identify the process. It may be running as root."));
834
- console.error(chalk.blue("Try stopping with sudo:"));
835
- console.error(chalk.cyan(" sudo portless proxy stop"));
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(chalk.red(`Could not identify the process on port ${proxyPort}.`));
838
- console.error(chalk.blue("Try manually:"));
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
- chalk.cyan(
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(chalk.yellow("Proxy is not running."));
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(chalk.red("Corrupted PID file. Removing it."));
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(chalk.yellow("Proxy process is no longer running. Cleaning up stale files."));
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
- chalk.yellow(
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(chalk.yellow("Removing stale PID file."));
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(chalk.green("Proxy stopped."));
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
- chalk.red("Permission denied. The proxy was started with elevated privileges.")
933
+ colors_default.red("Permission denied. The proxy was started with elevated privileges.")
889
934
  );
890
- console.error(chalk.blue("Stop it with:"));
891
- console.error(chalk.cyan(` ${sudoHint}portless proxy stop`));
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(chalk.red(`Failed to stop proxy: ${message}`));
895
- console.error(chalk.blue("Check if the process is still running:"));
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
- chalk.cyan(
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(chalk.yellow("No active routes."));
908
- console.log(chalk.gray("Start an app with: portless <name> <command>"));
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(chalk.blue.bold("\nActive routes:\n"));
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
- ` ${chalk.cyan(url)} ${chalk.gray("->")} ${chalk.white(`localhost:${route.port}`)} ${chalk.gray(label)}`
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(chalk.red(`Error: ${err.message}`));
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
- chalk.yellow(
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(chalk.blue.bold(`
982
+ console.log(colors_default.blue.bold(`
938
983
  portless
939
984
  `));
940
- console.log(chalk.gray(`-- ${hostname} (auto-resolves to 127.0.0.1)`));
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(chalk.gray(`-- Name "${baseName}" (from ${autoInfo.nameSource})`));
988
+ console.log(colors_default.gray(`-- Name "${baseName}" (from ${autoInfo.nameSource})`));
944
989
  if (autoInfo.prefix) {
945
- console.log(chalk.gray(`-- Prefix "${autoInfo.prefix}" (from ${autoInfo.prefixSource})`));
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(chalk.red("Proxy is not running."));
955
- console.error(chalk.blue("Start the proxy first (requires sudo for this port):"));
956
- console.error(chalk.cyan(" sudo portless proxy start -p 80"));
957
- console.error(chalk.blue("Or use the default port (no sudo needed):"));
958
- console.error(chalk.cyan(" portless proxy start"));
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(chalk.yellow("Proxy not running. Start it? [Y/n/skip] "));
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(chalk.gray("Cancelled."));
1008
+ console.log(colors_default.gray("Cancelled."));
964
1009
  process.exit(0);
965
1010
  }
966
1011
  if (answer === "s" || answer === "skip") {
967
- console.log(chalk.gray("Skipping proxy, running command directly...\n"));
1012
+ console.log(colors_default.gray("Skipping proxy, running command directly...\n"));
968
1013
  spawnCommand(commandArgs);
969
1014
  return;
970
1015
  }
971
- console.log(chalk.yellow("Starting proxy (requires sudo)..."));
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(chalk.red("Failed to start proxy."));
982
- console.error(chalk.blue("Try starting it manually:"));
983
- console.error(chalk.cyan(" sudo portless proxy start"));
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(chalk.yellow("Starting proxy..."));
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(chalk.red("Failed to start proxy."));
999
- console.error(chalk.blue("Try starting it manually:"));
1000
- console.error(chalk.cyan(" portless proxy start"));
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(chalk.red("Proxy failed to start (timed out waiting for it to listen)."));
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(chalk.blue("Try starting the proxy manually to see the error:"));
1011
- console.error(chalk.cyan(` ${needsSudo ? "sudo " : ""}portless proxy start`));
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(chalk.gray(`Logs: ${logPath}`));
1058
+ console.error(colors_default.gray(`Logs: ${logPath}`));
1014
1059
  }
1015
1060
  process.exit(1);
1016
1061
  }
1017
1062
  tls2 = autoTls;
1018
- console.log(chalk.green("Proxy started in background"));
1063
+ console.log(colors_default.green("Proxy started in background"));
1019
1064
  } else {
1020
- console.log(chalk.gray("-- Proxy is running"));
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(chalk.green(`-- Using port ${port} (fixed)`));
1069
+ console.log(colors_default.green(`-- Using port ${port} (fixed)`));
1025
1070
  } else {
1026
- console.log(chalk.green(`-- Using port ${port}`));
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(chalk.red(`Error: ${err.message}`));
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(chalk.cyan.bold(`
1083
+ console.log(colors_default.cyan.bold(`
1039
1084
  -> ${finalUrl}
1040
1085
  `));
1041
1086
  injectFrameworkFlags(commandArgs, port);
1042
1087
  console.log(
1043
- chalk.gray(
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(chalk.red("Error: --app-port requires a port number."));
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(chalk.red(`Error: Invalid app port "${value}". Must be 1-65535.`));
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(chalk.red(`Error: Invalid PORTLESS_APP_PORT="${envVal}". Must be 1-65535.`));
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
- ${chalk.bold("portless run")} - Infer project name and run through the proxy.
1142
+ ${colors_default.bold("portless run")} - Infer project name and run through the proxy.
1098
1143
 
1099
- ${chalk.bold("Usage:")}
1100
- ${chalk.cyan("portless run [options] <command...>")}
1144
+ ${colors_default.bold("Usage:")}
1145
+ ${colors_default.cyan("portless run [options] <command...>")}
1101
1146
 
1102
- ${chalk.bold("Options:")}
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
- ${chalk.bold("Name inference (in order):")}
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
- ${chalk.bold("Examples:")}
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(chalk.red("Error: --name requires a name value."));
1133
- console.error(chalk.cyan(" portless run --name <name> <command...>"));
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(chalk.red(`Error: Unknown flag "${args[i]}".`));
1139
- console.error(chalk.blue("Known flags: --name, --force, --app-port, --help"));
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(chalk.red(`Error: Unknown flag "${args[i]}".`));
1162
- console.error(chalk.blue("Known flags: --force, --app-port"));
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(chalk.red(`Error: Unknown flag "${args[i]}".`));
1180
- console.error(chalk.blue("Known flags: --force, --app-port"));
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
- ${chalk.bold("portless")} - Replace port numbers with stable, named .localhost URLs. For humans and agents.
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
- ${chalk.bold("Install:")}
1196
- ${chalk.cyan("npm install -g portless")}
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
- ${chalk.bold("Usage:")}
1200
- ${chalk.cyan("portless proxy start")} Start the proxy (background daemon)
1201
- ${chalk.cyan("portless proxy start --https")} Start with HTTP/2 + TLS (auto-generates certs)
1202
- ${chalk.cyan("portless proxy start -p 80")} Start on port 80 (requires sudo)
1203
- ${chalk.cyan("portless proxy stop")} Stop the proxy
1204
- ${chalk.cyan("portless <name> <cmd>")} Run your app through the proxy
1205
- ${chalk.cyan("portless run <cmd>")} Infer name from project, run through proxy
1206
- ${chalk.cyan("portless get <name>")} Print URL for a service (for cross-service refs)
1207
- ${chalk.cyan("portless alias <name> <port>")} Register a static route (e.g. for Docker)
1208
- ${chalk.cyan("portless alias --remove <name>")} Remove a static route
1209
- ${chalk.cyan("portless list")} Show active routes
1210
- ${chalk.cyan("portless trust")} Add local CA to system trust store
1211
- ${chalk.cyan("portless hosts sync")} Add routes to ${HOSTS_DISPLAY} (fixes Safari)
1212
- ${chalk.cyan("portless hosts clean")} Remove portless entries from ${HOSTS_DISPLAY}
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
- ${chalk.bold("Examples:")}
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
- ${chalk.bold("In package.json:")}
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
- ${chalk.bold("How it works:")}
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
- ${chalk.bold("HTTP/2 + HTTPS:")}
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
- ${chalk.bold("Options:")}
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
- ${chalk.bold("Environment variables:")}
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
- ${chalk.bold("Child process environment:")}
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
- ${chalk.bold("Safari / DNS:")}
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
- ${chalk.cyan(`${SUDO_PREFIX}portless hosts sync`)}
1328
+ ${colors_default.cyan(`${SUDO_PREFIX}portless hosts sync`)}
1282
1329
  Clean up later with:
1283
- ${chalk.cyan(`${SUDO_PREFIX}portless hosts clean`)}
1330
+ ${colors_default.cyan(`${SUDO_PREFIX}portless hosts clean`)}
1284
1331
 
1285
- ${chalk.bold("Skip portless:")}
1332
+ ${colors_default.bold("Skip portless:")}
1286
1333
  PORTLESS=0 pnpm dev # Runs command directly without proxy
1287
1334
 
1288
- ${chalk.bold("Reserved names:")}
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.7.2");
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(chalk.green("Local CA added to system trust store."));
1304
- console.log(chalk.gray("Browsers will now trust portless HTTPS certificates."));
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(chalk.red(`Failed to trust CA: ${result.error}`));
1353
+ console.error(colors_default.red(`Failed to trust CA: ${result.error}`));
1307
1354
  if (result.error?.includes("sudo")) {
1308
- console.error(chalk.blue("Run with sudo:"));
1309
- console.error(chalk.cyan(" sudo portless trust"));
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(chalk.yellow(msg))
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
- ${chalk.bold("portless get")} - Print the URL for a service.
1371
+ ${colors_default.bold("portless get")} - Print the URL for a service.
1325
1372
 
1326
- ${chalk.bold("Usage:")}
1327
- ${chalk.cyan("portless get <name>")}
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
- ${chalk.bold("Options:")}
1382
+ ${colors_default.bold("Options:")}
1336
1383
  --no-worktree Skip worktree prefix detection
1337
1384
  --help, -h Show this help
1338
1385
 
1339
- ${chalk.bold("Examples:")}
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(chalk.red(`Error: Unknown flag "${args[i]}".`));
1353
- console.error(chalk.blue("Known flags: --no-worktree, --help"));
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(chalk.red("Error: Missing service name."));
1361
- console.error(chalk.blue("Usage:"));
1362
- console.error(chalk.cyan(" portless get <name>"));
1363
- console.error(chalk.blue("Example:"));
1364
- console.error(chalk.cyan(" portless get backend"));
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
- ${chalk.bold("portless alias")} - Register a static route for services not managed by portless.
1425
+ ${colors_default.bold("portless alias")} - Register a static route for services not managed by portless.
1379
1426
 
1380
- ${chalk.bold("Usage:")}
1381
- ${chalk.cyan("portless alias <name> <port>")} Register a route
1382
- ${chalk.cyan("portless alias --remove <name>")} Remove a route
1383
- ${chalk.cyan("portless alias <name> <port> --force")} Override existing route
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
- ${chalk.bold("Examples:")}
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(chalk.yellow(msg))
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(chalk.red("Error: No alias name provided."));
1400
- console.error(chalk.cyan(" portless alias --remove <name>"));
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(chalk.red(`Error: No alias found for "${hostname2}".`));
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(chalk.green(`Removed alias: ${hostname2}`));
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(chalk.red("Error: Missing arguments."));
1418
- console.error(chalk.blue("Usage:"));
1419
- console.error(chalk.cyan(" portless alias <name> <port>"));
1420
- console.error(chalk.cyan(" portless alias --remove <name>"));
1421
- console.error(chalk.blue("Example:"));
1422
- console.error(chalk.cyan(" portless alias my-postgres 5432"));
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(chalk.red(`Error: Invalid port "${aliasPort}". Must be 1-65535.`));
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(chalk.green(`Alias registered: ${hostname} -> 127.0.0.1:${port}`));
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
- ${chalk.bold("portless hosts")} - Manage ${HOSTS_DISPLAY} entries for .localhost subdomains.
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
- ${chalk.bold("Usage:")}
1444
- ${chalk.cyan(`${SUDO_PREFIX}portless hosts sync`)} Add current routes to ${HOSTS_DISPLAY}
1445
- ${chalk.cyan(`${SUDO_PREFIX}portless hosts clean`)} Remove portless entries from ${HOSTS_DISPLAY}
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
- ${chalk.bold("Auto-sync:")}
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(chalk.green(`Removed portless entries from ${HOSTS_DISPLAY}.`));
1502
+ console.log(colors_default.green(`Removed portless entries from ${HOSTS_DISPLAY}.`));
1456
1503
  } else {
1457
1504
  console.error(
1458
- chalk.red(
1505
+ colors_default.red(
1459
1506
  `Failed to update ${HOSTS_DISPLAY}${isWindows ? " (run as Administrator)." : " (requires sudo)."}`
1460
1507
  )
1461
1508
  );
1462
- console.error(chalk.cyan(` ${SUDO_PREFIX}portless hosts clean`));
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
- ${chalk.bold("Usage: portless hosts <command>")}
1516
+ ${colors_default.bold("Usage: portless hosts <command>")}
1470
1517
 
1471
- ${chalk.cyan(`${SUDO_PREFIX}portless hosts sync`)} Add current routes to ${HOSTS_DISPLAY}
1472
- ${chalk.cyan(`${SUDO_PREFIX}portless hosts clean`)} Remove portless entries from ${HOSTS_DISPLAY}
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(chalk.red(`Error: Unknown hosts subcommand "${args[1]}".`));
1478
- console.error(chalk.blue("Usage:"));
1524
+ console.error(colors_default.red(`Error: Unknown hosts subcommand "${args[1]}".`));
1525
+ console.error(colors_default.blue("Usage:"));
1479
1526
  console.error(
1480
- chalk.cyan(` ${SUDO_PREFIX}portless hosts sync # Add routes to ${HOSTS_DISPLAY}`)
1527
+ colors_default.cyan(` ${SUDO_PREFIX}portless hosts sync # Add routes to ${HOSTS_DISPLAY}`)
1481
1528
  );
1482
- console.error(chalk.cyan(` ${SUDO_PREFIX}portless hosts clean # Remove portless entries`));
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(chalk.yellow(msg))
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(chalk.yellow("No active routes to sync."));
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(chalk.green(`Synced ${hostnames.length} hostname(s) to ${HOSTS_DISPLAY}:`));
1543
+ console.log(colors_default.green(`Synced ${hostnames.length} hostname(s) to ${HOSTS_DISPLAY}:`));
1497
1544
  for (const h of hostnames) {
1498
- console.log(chalk.cyan(` 127.0.0.1 ${h}`));
1545
+ console.log(colors_default.cyan(` 127.0.0.1 ${h}`));
1499
1546
  }
1500
1547
  } else {
1501
1548
  console.error(
1502
- chalk.red(
1549
+ colors_default.red(
1503
1550
  `Failed to update ${HOSTS_DISPLAY}${isWindows ? " (run as Administrator)." : " (requires sudo)."}`
1504
1551
  )
1505
1552
  );
1506
- console.error(chalk.cyan(` ${SUDO_PREFIX}portless hosts sync`));
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(chalk.yellow(msg))
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
- ${chalk.bold("portless proxy")} - Manage the portless proxy server.
1569
+ ${colors_default.bold("portless proxy")} - Manage the portless proxy server.
1523
1570
 
1524
- ${chalk.bold("Usage:")}
1525
- ${chalk.cyan("portless proxy start")} Start the proxy (daemon)
1526
- ${chalk.cyan("portless proxy start --https")} Start with HTTP/2 + TLS
1527
- ${chalk.cyan("portless proxy start --foreground")} Start in foreground (for debugging)
1528
- ${chalk.cyan("portless proxy start -p 80")} Start on port 80 (requires sudo)
1529
- ${chalk.cyan("portless proxy start --tld test")} Use .test instead of .localhost
1530
- ${chalk.cyan("portless proxy stop")} Stop the proxy
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(chalk.red("Error: --port / -p requires a port number."));
1542
- console.error(chalk.blue("Usage:"));
1543
- console.error(chalk.cyan(" portless proxy start -p 8080"));
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(chalk.red(`Error: Invalid port number: ${portValue}`));
1549
- console.error(chalk.blue("Port must be between 1 and 65535."));
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(chalk.red("Error: --cert requires a file path."));
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(chalk.red("Error: --key requires a file path."));
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(chalk.red("Error: --cert and --key must be used together."));
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(chalk.red(`Error: ${err.message}`));
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(chalk.red("Error: --tld requires a TLD value (e.g. test, localhost)."));
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(chalk.red(`Error: ${tldErr}`));
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(chalk.yellow(`Warning: .${tld} -- ${riskyReason}`));
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
- chalk.yellow(
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(chalk.yellow("Hosts sync is disabled. To add entries manually, run:"));
1611
- console.warn(chalk.cyan(` ${SUDO_PREFIX}portless hosts sync`));
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(chalk.yellow(msg))
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(chalk.yellow(`Proxy is already running on port ${proxyPort}.`));
1674
+ console.log(colors_default.yellow(`Proxy is already running on port ${proxyPort}.`));
1626
1675
  console.log(
1627
- chalk.blue(
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(chalk.red(`Error: Port ${proxyPort} requires sudo.`));
1635
- console.error(chalk.blue("Either run with sudo:"));
1636
- console.error(chalk.cyan(" sudo portless proxy start -p 80"));
1637
- console.error(chalk.blue("Or use the default port (no sudo needed):"));
1638
- console.error(chalk.cyan(" portless proxy start"));
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(chalk.red(`Error: ${customCertPath} is not a valid PEM certificate.`));
1652
- console.error(chalk.gray("Expected a file starting with -----BEGIN CERTIFICATE-----"));
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(chalk.red(`Error: ${customKeyPath} is not a valid PEM private key.`));
1657
- console.error(chalk.gray("Expected a file starting with -----BEGIN ...PRIVATE KEY-----"));
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(chalk.red(`Error reading certificate files: ${message}`));
1712
+ console.error(colors_default.red(`Error reading certificate files: ${message}`));
1664
1713
  process.exit(1);
1665
1714
  }
1666
1715
  } else {
1667
- console.log(chalk.gray("Ensuring TLS certificates..."));
1716
+ console.log(colors_default.gray("Ensuring TLS certificates..."));
1668
1717
  const certs = ensureCerts(stateDir);
1669
1718
  if (certs.caGenerated) {
1670
- console.log(chalk.green("Generated local CA certificate."));
1719
+ console.log(colors_default.green("Generated local CA certificate."));
1671
1720
  }
1672
1721
  if (!isCATrusted(stateDir)) {
1673
- console.log(chalk.yellow("Adding CA to system trust store..."));
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
- chalk.green("CA added to system trust store. Browsers will trust portless certs.")
1726
+ colors_default.green("CA added to system trust store. Browsers will trust portless certs.")
1678
1727
  );
1679
1728
  } else {
1680
- console.warn(chalk.yellow("Could not add CA to system trust store."));
1729
+ console.warn(colors_default.yellow("Could not add CA to system trust store."));
1681
1730
  if (trustResult.error) {
1682
- console.warn(chalk.gray(trustResult.error));
1731
+ console.warn(colors_default.gray(trustResult.error));
1683
1732
  }
1684
1733
  console.warn(
1685
- chalk.yellow("Browsers will show certificate warnings. To fix this later, run:")
1734
+ colors_default.yellow("Browsers will show certificate warnings. To fix this later, run:")
1686
1735
  );
1687
- console.warn(chalk.cyan(" portless trust"));
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(chalk.blue.bold("\nportless proxy\n"));
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(chalk.red("Proxy failed to start (timed out waiting for it to listen)."));
1739
- console.error(chalk.blue("Try starting the proxy in the foreground to see the 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(chalk.cyan(` ${needsSudo ? "sudo " : ""}portless proxy start --foreground`));
1793
+ console.error(colors_default.cyan(` ${needsSudo ? "sudo " : ""}portless proxy start --foreground`));
1742
1794
  if (fs3.existsSync(logPath)) {
1743
- console.error(chalk.gray(`Logs: ${logPath}`));
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(chalk.green(`${proto} proxy started on port ${proxyPort}`));
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(chalk.red("Error: No command provided."));
1754
- console.error(chalk.blue("Usage:"));
1755
- console.error(chalk.cyan(" portless run <command...>"));
1756
- console.error(chalk.blue("Example:"));
1757
- console.error(chalk.cyan(" portless run next dev"));
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(chalk.yellow(msg))
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(chalk.red("Error: No command provided."));
1793
- console.error(chalk.blue("Usage:"));
1794
- console.error(chalk.cyan(" portless <name> <command...>"));
1795
- console.error(chalk.blue("Example:"));
1796
- console.error(chalk.cyan(" portless myapp next dev"));
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(chalk.yellow(msg))
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
- parsed.name,
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(chalk.red("Error: portless should not be run via npx or pnpm dlx."));
1830
- console.error(chalk.blue("Install globally instead:"));
1831
- console.error(chalk.cyan(" npm install -g portless"));
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(chalk.red("Error: --name requires an app name."));
1838
- console.error(chalk.cyan(" portless --name <name> <command...>"));
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(chalk.red("Error: No command provided."));
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(chalk.red("Error: No command provided."));
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(chalk.red("Error:"), message);
1963
+ console.error(colors_default.red("Error:"), message);
1911
1964
  process.exit(1);
1912
1965
  });