@hamedb89/localghost 0.1.9 → 0.1.12

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.
@@ -55,6 +55,51 @@ type GhostTunnelPreviewOptions = {
55
55
  };
56
56
  type GhostTunnelMode = "manual" | "public";
57
57
  type GhostTunnelDomainOptions = string | readonly string[];
58
+ type GhostTunnelAdapterProvider = "vercel";
59
+ type GhostTunnelAdapterStrategy = "same-project" | "separate-relay";
60
+ type GhostTunnelTransportKind = "none" | "ip" | "tunnel";
61
+ type GhostTunnelAdapterTransport = GhostTunnelTransportKind;
62
+ type GhostTunnelTunnelStoreProvider = "vercel-redis" | "redis";
63
+ type GhostTunnelTunnelStoreEnv = "auto";
64
+ type GhostTunnelTunnelStoreOptions = {
65
+ provider?: GhostTunnelTunnelStoreProvider;
66
+ env?: GhostTunnelTunnelStoreEnv;
67
+ namespace?: string;
68
+ };
69
+ type GhostTunnelTransportOptions = GhostTunnelTransportKind | {
70
+ kind?: "none";
71
+ } | {
72
+ kind: "ip";
73
+ allowPrivateNetworkAddress?: boolean;
74
+ } | {
75
+ kind: "tunnel";
76
+ store?: GhostTunnelTunnelStoreOptions;
77
+ waitMs?: number;
78
+ pollIntervalMs?: number;
79
+ routeTtlSeconds?: number;
80
+ requestTtlSeconds?: number;
81
+ maxRequestBodyBytes?: number;
82
+ maxResponseBodyBytes?: number;
83
+ };
84
+ type GhostTunnelTransportConfig = {
85
+ kind: "none";
86
+ } | {
87
+ kind: "ip";
88
+ allowPrivateNetworkAddress: boolean;
89
+ } | {
90
+ kind: "tunnel";
91
+ store: Required<Pick<GhostTunnelTunnelStoreOptions, "provider" | "env" | "namespace">>;
92
+ waitMs: number;
93
+ pollIntervalMs: number;
94
+ routeTtlSeconds: number;
95
+ requestTtlSeconds: number;
96
+ maxRequestBodyBytes: number;
97
+ maxResponseBodyBytes: number;
98
+ };
99
+ type GhostTunnelAdapterOptions = GhostTunnelAdapterProvider | {
100
+ provider: GhostTunnelAdapterProvider;
101
+ strategy?: GhostTunnelAdapterStrategy;
102
+ };
58
103
  type GhostTunnelOptions = false | GhostTunnelMode | {
59
104
  enabled?: boolean;
60
105
  mode?: GhostTunnelMode;
@@ -64,6 +109,8 @@ type GhostTunnelOptions = false | GhostTunnelMode | {
64
109
  preview?: GhostTunnelPreviewOptions;
65
110
  requireHttps?: boolean;
66
111
  requireAuth?: boolean;
112
+ adapter?: GhostTunnelAdapterOptions;
113
+ transport?: GhostTunnelTransportOptions;
67
114
  };
68
115
  type GhostTunnelConfig = {
69
116
  enabled: boolean;
@@ -77,6 +124,11 @@ type GhostTunnelConfig = {
77
124
  displayUrls: string[];
78
125
  requireHttps: boolean;
79
126
  requireAuth: boolean;
127
+ transport: GhostTunnelTransportConfig;
128
+ adapter?: {
129
+ provider: GhostTunnelAdapterProvider;
130
+ strategy: GhostTunnelAdapterStrategy;
131
+ };
80
132
  };
81
133
  type GhostTunnelDisplayDefaults = {
82
134
  domain?: string;
@@ -123,4 +175,4 @@ declare function assertSecureGhostTunnelRequest(input: {
123
175
  authenticated?: boolean;
124
176
  }): GhostTunnelRoute;
125
177
 
126
- export { parseDevHosts as A, parseGhostTunnelHost as B, type ConfigPattern as C, type DevHostEntry as D, readDevHosts as E, resolveDevHostsPath as F, type GhostTunnelOptions as G, resolveGhostTunnelConfig as H, sanitizeProjectName as I, LOCALGHOST_CONFIG_FILE as L, type ReadDevHostsOptions as R, type GhostTunnelConfig as a, type ConstructGhostTunnelUrlInput as b, type GhostTunnelDisplayDefaults as c, type GhostTunnelDomainOptions as d, type GhostTunnelMode as e, type GhostTunnelNamespaceConfig as f, type GhostTunnelNamespaceOptions as g, type GhostTunnelNamespaceTag as h, type GhostTunnelNamespaceValues as i, type GhostTunnelPreviewOptions as j, type GhostTunnelRoute as k, type ResolvedDevHostsPath as l, assertSecureGhostTunnelRequest as m, constructGhostTunnelHost as n, constructGhostTunnelURL as o, constructGhostTunnelUrl as p, findLocalMdnsHosts as q, getConfigFileCandidates as r, getDevHostsPath as s, getGhostTunnelDefaultDisplayUrl as t, getGhostTunnelDisplayUrl as u, getGhostTunnelDisplayUrls as v, getGhostTunnelEntryHost as w, getGhostTunnelPreviewUrl as x, getGhostTunnelWildcardHost as y, getProjectName as z };
178
+ export { findLocalMdnsHosts as A, getConfigFileCandidates as B, type ConfigPattern as C, type DevHostEntry as D, getDevHostsPath as E, getGhostTunnelDefaultDisplayUrl as F, type GhostTunnelOptions as G, getGhostTunnelDisplayUrl as H, getGhostTunnelDisplayUrls as I, getGhostTunnelEntryHost as J, getGhostTunnelPreviewUrl as K, LOCALGHOST_CONFIG_FILE as L, getGhostTunnelWildcardHost as M, getProjectName as N, parseDevHosts as O, parseGhostTunnelHost as P, readDevHosts as Q, type ReadDevHostsOptions as R, resolveDevHostsPath as S, resolveGhostTunnelConfig as T, sanitizeProjectName as U, type ResolvedDevHostsPath as a, type ConstructGhostTunnelUrlInput as b, type GhostTunnelTransportOptions as c, type GhostTunnelTransportConfig as d, type GhostTunnelRoute as e, type GhostTunnelConfig as f, type GhostTunnelAdapterOptions as g, type GhostTunnelAdapterProvider as h, type GhostTunnelAdapterStrategy as i, type GhostTunnelAdapterTransport as j, type GhostTunnelDisplayDefaults as k, type GhostTunnelDomainOptions as l, type GhostTunnelMode as m, type GhostTunnelNamespaceConfig as n, type GhostTunnelNamespaceOptions as o, type GhostTunnelNamespaceTag as p, type GhostTunnelNamespaceValues as q, type GhostTunnelPreviewOptions as r, type GhostTunnelTransportKind as s, type GhostTunnelTunnelStoreEnv as t, type GhostTunnelTunnelStoreOptions as u, type GhostTunnelTunnelStoreProvider as v, assertSecureGhostTunnelRequest as w, constructGhostTunnelHost as x, constructGhostTunnelURL as y, constructGhostTunnelUrl as z };
package/dist/vite.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Plugin } from 'vite';
2
- import { C as ConfigPattern, G as GhostTunnelOptions } from './tunnel-DzfLXZ8O.js';
2
+ import { C as ConfigPattern, G as GhostTunnelOptions } from './tunnel-BA52DD9e.js';
3
3
 
4
4
  type LocalGhostPluginOptions = {
5
5
  cwd?: string;
@@ -10,6 +10,7 @@ type LocalGhostPluginOptions = {
10
10
  https?: boolean;
11
11
  bindHost?: string | boolean;
12
12
  dynamicPort?: boolean;
13
+ autoRepair?: boolean;
13
14
  primaryHost?: string;
14
15
  log?: boolean;
15
16
  setup?: boolean | "prompt";
package/dist/vite.js CHANGED
@@ -270,6 +270,17 @@ var DEFAULT_GHOST_TUNNEL_SUBDOMAIN = "ghost";
270
270
  var DEFAULT_GHOST_TUNNEL_NAMESPACE_TAGS = ["route", "project", "owner"];
271
271
  var DEFAULT_GHOST_TUNNEL_NAMESPACE_SEPARATOR = "-";
272
272
  var DEFAULT_GHOST_TUNNEL_MODE = "manual";
273
+ var DEFAULT_GHOST_TUNNEL_ADAPTER_STRATEGY = "same-project";
274
+ var DEFAULT_GHOST_TUNNEL_TRANSPORT_KIND = "none";
275
+ var DEFAULT_GHOST_TUNNEL_TUNNEL_STORE_PROVIDER = "vercel-redis";
276
+ var DEFAULT_GHOST_TUNNEL_TUNNEL_STORE_ENV = "auto";
277
+ var DEFAULT_GHOST_TUNNEL_TUNNEL_STORE_NAMESPACE = "localghost";
278
+ var DEFAULT_GHOST_TUNNEL_TUNNEL_WAIT_MS = 25e3;
279
+ var DEFAULT_GHOST_TUNNEL_TUNNEL_POLL_INTERVAL_MS = 250;
280
+ var DEFAULT_GHOST_TUNNEL_TUNNEL_ROUTE_TTL_SECONDS = 30;
281
+ var DEFAULT_GHOST_TUNNEL_TUNNEL_REQUEST_TTL_SECONDS = 60;
282
+ var DEFAULT_GHOST_TUNNEL_TUNNEL_MAX_REQUEST_BODY_BYTES = 1024 * 1024;
283
+ var DEFAULT_GHOST_TUNNEL_TUNNEL_MAX_RESPONSE_BODY_BYTES = 5 * 1024 * 1024;
273
284
  function isResolvedGhostTunnelConfig(value) {
274
285
  return typeof value === "object" && value !== null && "enabled" in value;
275
286
  }
@@ -319,6 +330,86 @@ function normalizeDomains(domains) {
319
330
  function parseGhostTunnelMode(value) {
320
331
  return value ?? DEFAULT_GHOST_TUNNEL_MODE;
321
332
  }
333
+ function parseGhostTunnelAdapterStrategy(value) {
334
+ if (typeof value === "undefined") return DEFAULT_GHOST_TUNNEL_ADAPTER_STRATEGY;
335
+ if (value === "same-project" || value === "separate-relay") return value;
336
+ throw new Error(`Unsupported ghost tunnel adapter strategy: ${String(value)}`);
337
+ }
338
+ function parseGhostTunnelTransportKind(value) {
339
+ if (typeof value === "undefined") return DEFAULT_GHOST_TUNNEL_TRANSPORT_KIND;
340
+ if (value === "none" || value === "ip" || value === "tunnel") return value;
341
+ throw new Error(`Unsupported ghost tunnel transport: ${String(value)}`);
342
+ }
343
+ function parsePositiveInteger(value, fallback, name) {
344
+ if (typeof value === "undefined") return fallback;
345
+ if (!Number.isInteger(value) || value < 1) {
346
+ throw new Error(`Invalid ghost tunnel ${name}: ${value}`);
347
+ }
348
+ return value;
349
+ }
350
+ function parseTunnelStoreProvider(value) {
351
+ if (typeof value === "undefined") return DEFAULT_GHOST_TUNNEL_TUNNEL_STORE_PROVIDER;
352
+ if (value === "vercel-redis" || value === "redis") return value;
353
+ throw new Error(`Unsupported ghost tunnel tunnel store provider: ${String(value)}`);
354
+ }
355
+ function parseTunnelStoreEnv(value) {
356
+ if (typeof value === "undefined") return DEFAULT_GHOST_TUNNEL_TUNNEL_STORE_ENV;
357
+ if (value === "auto") return value;
358
+ throw new Error(`Unsupported ghost tunnel tunnel store env: ${String(value)}`);
359
+ }
360
+ function parseTunnelStoreNamespace(value) {
361
+ const namespace = value ?? DEFAULT_GHOST_TUNNEL_TUNNEL_STORE_NAMESPACE;
362
+ if (!/^[a-z][a-z0-9:_-]{0,63}$/i.test(namespace)) {
363
+ throw new Error(`Invalid ghost tunnel tunnel store namespace: ${namespace}`);
364
+ }
365
+ return namespace;
366
+ }
367
+ function resolveGhostTunnelAdapter(input2) {
368
+ if (!input2) return void 0;
369
+ const provider = typeof input2 === "string" ? input2 : input2.provider;
370
+ if (provider !== "vercel") {
371
+ throw new Error(`Unsupported ghost tunnel adapter provider: ${String(provider)}`);
372
+ }
373
+ return {
374
+ provider,
375
+ strategy: typeof input2 === "string" ? DEFAULT_GHOST_TUNNEL_ADAPTER_STRATEGY : parseGhostTunnelAdapterStrategy(input2.strategy)
376
+ };
377
+ }
378
+ function getLegacyGhostTunnelTransport(input2) {
379
+ if (!input2 || typeof input2 === "string" || !("transport" in input2)) return void 0;
380
+ return input2.transport;
381
+ }
382
+ function resolveGhostTunnelTransport(input2) {
383
+ if (!input2) {
384
+ return { kind: "none" };
385
+ }
386
+ const kind = typeof input2 === "string" ? parseGhostTunnelTransportKind(input2) : parseGhostTunnelTransportKind(input2.kind);
387
+ if (kind === "ip") {
388
+ return {
389
+ kind,
390
+ allowPrivateNetworkAddress: typeof input2 === "string" ? false : input2.kind === "ip" ? input2.allowPrivateNetworkAddress ?? false : false
391
+ };
392
+ }
393
+ if (kind === "tunnel") {
394
+ const config = typeof input2 === "string" || input2.kind !== "tunnel" ? void 0 : input2;
395
+ const store = config?.store ?? {};
396
+ return {
397
+ kind,
398
+ store: {
399
+ provider: parseTunnelStoreProvider(store.provider),
400
+ env: parseTunnelStoreEnv(store.env),
401
+ namespace: parseTunnelStoreNamespace(store.namespace)
402
+ },
403
+ waitMs: parsePositiveInteger(config?.waitMs, DEFAULT_GHOST_TUNNEL_TUNNEL_WAIT_MS, "tunnel waitMs"),
404
+ pollIntervalMs: parsePositiveInteger(config?.pollIntervalMs, DEFAULT_GHOST_TUNNEL_TUNNEL_POLL_INTERVAL_MS, "tunnel pollIntervalMs"),
405
+ routeTtlSeconds: parsePositiveInteger(config?.routeTtlSeconds, DEFAULT_GHOST_TUNNEL_TUNNEL_ROUTE_TTL_SECONDS, "tunnel routeTtlSeconds"),
406
+ requestTtlSeconds: parsePositiveInteger(config?.requestTtlSeconds, DEFAULT_GHOST_TUNNEL_TUNNEL_REQUEST_TTL_SECONDS, "tunnel requestTtlSeconds"),
407
+ maxRequestBodyBytes: parsePositiveInteger(config?.maxRequestBodyBytes, DEFAULT_GHOST_TUNNEL_TUNNEL_MAX_REQUEST_BODY_BYTES, "tunnel maxRequestBodyBytes"),
408
+ maxResponseBodyBytes: parsePositiveInteger(config?.maxResponseBodyBytes, DEFAULT_GHOST_TUNNEL_TUNNEL_MAX_RESPONSE_BODY_BYTES, "tunnel maxResponseBodyBytes")
409
+ };
410
+ }
411
+ return { kind: "none" };
412
+ }
322
413
  function resolveNamespaceConfig(options) {
323
414
  const tags = isNamespaceTagList(options) ? [...options] : [...options?.tags ?? DEFAULT_GHOST_TUNNEL_NAMESPACE_TAGS];
324
415
  let separator = DEFAULT_GHOST_TUNNEL_NAMESPACE_SEPARATOR;
@@ -404,8 +495,11 @@ function getDisplayValues(input2) {
404
495
  ...input2.values
405
496
  };
406
497
  }
498
+ function getDisplayDefaults(defaults) {
499
+ return defaults;
500
+ }
407
501
  function createDisplayUrl(config, defaults, domain) {
408
- const input2 = getPreviewDefaults(config.preview, defaults);
502
+ const input2 = getPreviewDefaults(config.preview, getDisplayDefaults(defaults));
409
503
  const protocol = input2.protocol ?? "https";
410
504
  const slug = createNamespaceDisplaySlug(config.namespace, getDisplayValues(input2));
411
505
  const entryHost = domain ? getGhostTunnelEntryHost(domain, config) : input2.domain ? getGhostTunnelEntryHost(input2.domain, config) : `${config.subdomain}.*`;
@@ -414,8 +508,9 @@ function createDisplayUrl(config, defaults, domain) {
414
508
  return `${url}${input2.path.replace(/^\/+/, "")}`;
415
509
  }
416
510
  function createDisplayUrls(config, defaults) {
417
- const domains = config.domains.length > 0 ? config.domains : defaults?.domain ? [defaults.domain] : [];
418
- const urls = domains.length > 0 ? domains.map((domain) => createDisplayUrl(config, defaults, domain)) : [createDisplayUrl(config, defaults)];
511
+ const displayDefaults = getDisplayDefaults(defaults);
512
+ const domains = config.domains.length > 0 ? config.domains : displayDefaults?.domain ? [displayDefaults.domain] : [];
513
+ const urls = domains.length > 0 ? domains.map((domain) => createDisplayUrl(config, displayDefaults, domain)) : [createDisplayUrl(config, displayDefaults)];
419
514
  return [...new Set(urls)];
420
515
  }
421
516
  function maybeConstructPreviewUrl(config, defaults) {
@@ -443,7 +538,8 @@ function resolveGhostTunnelConfig(options, defaults) {
443
538
  namespace: resolveNamespaceConfig(void 0),
444
539
  displayUrls: [],
445
540
  requireHttps: true,
446
- requireAuth: true
541
+ requireAuth: true,
542
+ transport: resolveGhostTunnelTransport(void 0)
447
543
  };
448
544
  }
449
545
  const config = typeof options === "string" ? { mode: options } : options;
@@ -451,6 +547,8 @@ function resolveGhostTunnelConfig(options, defaults) {
451
547
  assertValidSubdomain(subdomain);
452
548
  const domains = normalizeDomains(config.domains);
453
549
  const enabled = config.enabled ?? true;
550
+ const adapter = resolveGhostTunnelAdapter(config.adapter);
551
+ const transport = resolveGhostTunnelTransport(config.transport ?? getLegacyGhostTunnelTransport(config.adapter));
454
552
  const resolved = {
455
553
  enabled,
456
554
  mode: parseGhostTunnelMode(config.mode),
@@ -460,7 +558,9 @@ function resolveGhostTunnelConfig(options, defaults) {
460
558
  ...config.preview ? { preview: config.preview } : {},
461
559
  displayUrls: [],
462
560
  requireHttps: config.requireHttps ?? true,
463
- requireAuth: config.requireAuth ?? true
561
+ requireAuth: config.requireAuth ?? true,
562
+ transport,
563
+ ...adapter ? { adapter } : {}
464
564
  };
465
565
  if (!enabled) {
466
566
  return resolved;
@@ -570,7 +670,7 @@ function withRuntimePort(entries, requestedPort, port) {
570
670
  if (requestedPort === port) return entries;
571
671
  const hasRequestedPort = entries.some((entry) => entry.port === requestedPort);
572
672
  if (!hasRequestedPort) return entries;
573
- return entries.map((entry) => entry.port === requestedPort ? { ...entry, port } : entry);
673
+ return entries.map((entry) => entry.port === requestedPort ? { ...entry, port, target: `127.0.0.1:${port}` } : entry);
574
674
  }
575
675
  function uniqueHosts(entries) {
576
676
  return [...new Set(entries.map((entry) => entry.host))];
@@ -621,6 +721,7 @@ async function resolveLocalghostContext(options = {}) {
621
721
  const configEntries = readDevHosts(readOptions);
622
722
  const requestedPort = merged.port ?? envPort() ?? configEntries[0]?.port ?? 5173;
623
723
  const dynamicPort = merged.dynamicPort ?? envDynamicPort() ?? true;
724
+ const autoRepair = merged.autoRepair ?? true;
624
725
  const bindHost = merged.bindHost ?? "127.0.0.1";
625
726
  const probeHost = typeof bindHost === "string" ? bindHost : "127.0.0.1";
626
727
  const port = dynamicPort ? await findAvailablePort(requestedPort, { host: probeHost }) : requestedPort;
@@ -646,6 +747,7 @@ async function resolveLocalghostContext(options = {}) {
646
747
  requestedPort,
647
748
  port,
648
749
  dynamicPort,
750
+ autoRepair,
649
751
  bindHost,
650
752
  primaryHost,
651
753
  https: merged.https ?? envHttps() ?? false,
@@ -715,7 +817,8 @@ function getManagedBlockPattern(projectName) {
715
817
  const end = `# localghost:end ${sanitizedProjectName}`;
716
818
  return new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}\\n?`, "m");
717
819
  }
718
- function getSystemHostsPath() {
820
+ function getSystemHostsPath(env = process.env) {
821
+ if (env.LOCALGHOST_HOSTS_PATH) return env.LOCALGHOST_HOSTS_PATH;
719
822
  return process.platform === "win32" ? "C:\\Windows\\System32\\drivers\\etc\\hosts" : "/etc/hosts";
720
823
  }
721
824
  function renderHostsBlock(projectName, entries) {
@@ -741,6 +844,10 @@ async function writeSystemHostsFile(hostsPath, next, projectName) {
741
844
  const sanitizedProjectName = sanitizeProjectName(projectName);
742
845
  const tempPath = join4(tmpdir(), `localghost-${sanitizedProjectName}-hosts`);
743
846
  writeFileSync3(tempPath, next, "utf8");
847
+ if (process.env.LOCALGHOST_HOSTS_PATH) {
848
+ writeFileSync3(hostsPath, next, "utf8");
849
+ return tempPath;
850
+ }
744
851
  if (process.platform === "win32") {
745
852
  throw new Error(`Windows support: run as administrator and copy ${tempPath} to ${hostsPath}.`);
746
853
  }
@@ -812,6 +919,12 @@ function writeLocalghostState(cwd, state) {
812
919
  // src/caddy.ts
813
920
  import { dirname as dirname3, join as join6 } from "path";
814
921
  import { execa as execa3 } from "execa";
922
+ function shouldShowCaddyLogs() {
923
+ return ["1", "true", "yes", "on"].includes((process.env.LOCALGHOST_CADDY_VERBOSE ?? "").toLowerCase());
924
+ }
925
+ function caddyStdio() {
926
+ return shouldShowCaddyLogs() ? "inherit" : "pipe";
927
+ }
815
928
  function groupByPort(entries) {
816
929
  const groups = /* @__PURE__ */ new Map();
817
930
  for (const entry of entries) {
@@ -830,7 +943,7 @@ function renderCaddyfile(entries, options = {}) {
830
943
  const blocks = [...groups.entries()].sort(([leftPort], [rightPort]) => leftPort - rightPort).map(([port, group]) => {
831
944
  const hosts = group.map((entry) => https ? entry.host : `http://${entry.host}`).sort().join(", ");
832
945
  return `${hosts} {
833
- reverse_proxy 127.0.0.1:${port}
946
+ reverse_proxy 127.0.0.1:${port}
834
947
  }`;
835
948
  });
836
949
  const globalOptions = https ? `{
@@ -849,7 +962,7 @@ async function writeCaddyfile(entries, cwd = process.cwd(), options = {}) {
849
962
  async function validateCaddyfile(path) {
850
963
  await execa3("caddy", ["validate", "--config", path], {
851
964
  cwd: dirname3(path),
852
- stdio: "inherit"
965
+ stdio: caddyStdio()
853
966
  });
854
967
  }
855
968
 
@@ -891,7 +1004,8 @@ function formatGhostTunnel(config, options = {}) {
891
1004
  if (options.verbose) {
892
1005
  lines.push(` domains: ${config.domains.length > 0 ? config.domains.join(", ") : "*"}`);
893
1006
  lines.push(` access: ${config.requireAuth ? "auth required" : "app decides"}`);
894
- lines.push(` transport: ${config.requireHttps ? "https required" : "http allowed"}`);
1007
+ lines.push(` protocol: ${config.requireHttps ? "https required" : "http allowed"}`);
1008
+ lines.push(` transport: ${config.transport.kind}`);
895
1009
  }
896
1010
  return lines.join("\n");
897
1011
  }
@@ -933,7 +1047,8 @@ function printLocalHosts(server, context) {
933
1047
  color: shouldColor(),
934
1048
  label: "ready",
935
1049
  verbose: optionsVerbose(context)
936
- }) : void 0
1050
+ }) : void 0,
1051
+ process.stdin.isTTY && context.ghostTunnel.enabled ? " help: press h + enter for Vite, g + enter for Localghost" : void 0
937
1052
  ].filter((line) => Boolean(line));
938
1053
  server.config.logger.info(lines.join("\n"), {
939
1054
  clear: false,
@@ -1111,12 +1226,11 @@ async function ensureLocalghostContext(options, vitePort, https) {
1111
1226
  ...typeof https === "boolean" ? { https } : {}
1112
1227
  });
1113
1228
  if (!hasReadySetup(cwd, context.entries, resolved.path, context.https)) {
1114
- if (options.setup === false || !canPrompt()) return context;
1115
- const setup = await confirm("Run caddy:setup now?", true);
1116
- if (setup) {
1117
- await setupProject(cwd, context.entries, resolved.path, context.https);
1118
- console.log(`All set. Setup state: ${getLocalghostStatePath(cwd)}`);
1119
- }
1229
+ if (options.setup === false || !context.autoRepair) return context;
1230
+ if (options.setup === "prompt" && (!canPrompt() || !await confirm("Repair Localghost setup now?", true))) return context;
1231
+ console.log("Localghost setup is stale; repairing it now.");
1232
+ await setupProject(cwd, context.entries, resolved.path, context.https);
1233
+ console.log(`Repair complete. Setup state: ${getLocalghostStatePath(cwd)}`);
1120
1234
  }
1121
1235
  return context;
1122
1236
  }