portless 0.7.1 → 0.8.0

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