@hamedb89/localghost 0.1.10 → 0.1.13

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,11 +495,11 @@ function getDisplayValues(input2) {
404
495
  ...input2.values
405
496
  };
406
497
  }
407
- function getDisplayDefaults(config, defaults) {
408
- return config.mode === "public" && !config.preview ? void 0 : defaults;
498
+ function getDisplayDefaults(defaults) {
499
+ return defaults;
409
500
  }
410
501
  function createDisplayUrl(config, defaults, domain) {
411
- const input2 = getPreviewDefaults(config.preview, getDisplayDefaults(config, defaults));
502
+ const input2 = getPreviewDefaults(config.preview, getDisplayDefaults(defaults));
412
503
  const protocol = input2.protocol ?? "https";
413
504
  const slug = createNamespaceDisplaySlug(config.namespace, getDisplayValues(input2));
414
505
  const entryHost = domain ? getGhostTunnelEntryHost(domain, config) : input2.domain ? getGhostTunnelEntryHost(input2.domain, config) : `${config.subdomain}.*`;
@@ -417,7 +508,7 @@ function createDisplayUrl(config, defaults, domain) {
417
508
  return `${url}${input2.path.replace(/^\/+/, "")}`;
418
509
  }
419
510
  function createDisplayUrls(config, defaults) {
420
- const displayDefaults = getDisplayDefaults(config, defaults);
511
+ const displayDefaults = getDisplayDefaults(defaults);
421
512
  const domains = config.domains.length > 0 ? config.domains : displayDefaults?.domain ? [displayDefaults.domain] : [];
422
513
  const urls = domains.length > 0 ? domains.map((domain) => createDisplayUrl(config, displayDefaults, domain)) : [createDisplayUrl(config, displayDefaults)];
423
514
  return [...new Set(urls)];
@@ -447,7 +538,8 @@ function resolveGhostTunnelConfig(options, defaults) {
447
538
  namespace: resolveNamespaceConfig(void 0),
448
539
  displayUrls: [],
449
540
  requireHttps: true,
450
- requireAuth: true
541
+ requireAuth: true,
542
+ transport: resolveGhostTunnelTransport(void 0)
451
543
  };
452
544
  }
453
545
  const config = typeof options === "string" ? { mode: options } : options;
@@ -455,6 +547,8 @@ function resolveGhostTunnelConfig(options, defaults) {
455
547
  assertValidSubdomain(subdomain);
456
548
  const domains = normalizeDomains(config.domains);
457
549
  const enabled = config.enabled ?? true;
550
+ const adapter = resolveGhostTunnelAdapter(config.adapter);
551
+ const transport = resolveGhostTunnelTransport(config.transport ?? getLegacyGhostTunnelTransport(config.adapter));
458
552
  const resolved = {
459
553
  enabled,
460
554
  mode: parseGhostTunnelMode(config.mode),
@@ -464,7 +558,9 @@ function resolveGhostTunnelConfig(options, defaults) {
464
558
  ...config.preview ? { preview: config.preview } : {},
465
559
  displayUrls: [],
466
560
  requireHttps: config.requireHttps ?? true,
467
- requireAuth: config.requireAuth ?? true
561
+ requireAuth: config.requireAuth ?? true,
562
+ transport,
563
+ ...adapter ? { adapter } : {}
468
564
  };
469
565
  if (!enabled) {
470
566
  return resolved;
@@ -574,7 +670,7 @@ function withRuntimePort(entries, requestedPort, port) {
574
670
  if (requestedPort === port) return entries;
575
671
  const hasRequestedPort = entries.some((entry) => entry.port === requestedPort);
576
672
  if (!hasRequestedPort) return entries;
577
- 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);
578
674
  }
579
675
  function uniqueHosts(entries) {
580
676
  return [...new Set(entries.map((entry) => entry.host))];
@@ -625,6 +721,7 @@ async function resolveLocalghostContext(options = {}) {
625
721
  const configEntries = readDevHosts(readOptions);
626
722
  const requestedPort = merged.port ?? envPort() ?? configEntries[0]?.port ?? 5173;
627
723
  const dynamicPort = merged.dynamicPort ?? envDynamicPort() ?? true;
724
+ const autoRepair = merged.autoRepair ?? true;
628
725
  const bindHost = merged.bindHost ?? "127.0.0.1";
629
726
  const probeHost = typeof bindHost === "string" ? bindHost : "127.0.0.1";
630
727
  const port = dynamicPort ? await findAvailablePort(requestedPort, { host: probeHost }) : requestedPort;
@@ -650,6 +747,7 @@ async function resolveLocalghostContext(options = {}) {
650
747
  requestedPort,
651
748
  port,
652
749
  dynamicPort,
750
+ autoRepair,
653
751
  bindHost,
654
752
  primaryHost,
655
753
  https: merged.https ?? envHttps() ?? false,
@@ -719,7 +817,8 @@ function getManagedBlockPattern(projectName) {
719
817
  const end = `# localghost:end ${sanitizedProjectName}`;
720
818
  return new RegExp(`${escapeRegExp(start)}[\\s\\S]*?${escapeRegExp(end)}\\n?`, "m");
721
819
  }
722
- function getSystemHostsPath() {
820
+ function getSystemHostsPath(env = process.env) {
821
+ if (env.LOCALGHOST_HOSTS_PATH) return env.LOCALGHOST_HOSTS_PATH;
723
822
  return process.platform === "win32" ? "C:\\Windows\\System32\\drivers\\etc\\hosts" : "/etc/hosts";
724
823
  }
725
824
  function renderHostsBlock(projectName, entries) {
@@ -745,6 +844,10 @@ async function writeSystemHostsFile(hostsPath, next, projectName) {
745
844
  const sanitizedProjectName = sanitizeProjectName(projectName);
746
845
  const tempPath = join4(tmpdir(), `localghost-${sanitizedProjectName}-hosts`);
747
846
  writeFileSync3(tempPath, next, "utf8");
847
+ if (process.env.LOCALGHOST_HOSTS_PATH) {
848
+ writeFileSync3(hostsPath, next, "utf8");
849
+ return tempPath;
850
+ }
748
851
  if (process.platform === "win32") {
749
852
  throw new Error(`Windows support: run as administrator and copy ${tempPath} to ${hostsPath}.`);
750
853
  }
@@ -816,6 +919,12 @@ function writeLocalghostState(cwd, state) {
816
919
  // src/caddy.ts
817
920
  import { dirname as dirname3, join as join6 } from "path";
818
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
+ }
819
928
  function groupByPort(entries) {
820
929
  const groups = /* @__PURE__ */ new Map();
821
930
  for (const entry of entries) {
@@ -834,7 +943,7 @@ function renderCaddyfile(entries, options = {}) {
834
943
  const blocks = [...groups.entries()].sort(([leftPort], [rightPort]) => leftPort - rightPort).map(([port, group]) => {
835
944
  const hosts = group.map((entry) => https ? entry.host : `http://${entry.host}`).sort().join(", ");
836
945
  return `${hosts} {
837
- reverse_proxy 127.0.0.1:${port}
946
+ reverse_proxy 127.0.0.1:${port}
838
947
  }`;
839
948
  });
840
949
  const globalOptions = https ? `{
@@ -853,7 +962,7 @@ async function writeCaddyfile(entries, cwd = process.cwd(), options = {}) {
853
962
  async function validateCaddyfile(path) {
854
963
  await execa3("caddy", ["validate", "--config", path], {
855
964
  cwd: dirname3(path),
856
- stdio: "inherit"
965
+ stdio: caddyStdio()
857
966
  });
858
967
  }
859
968
 
@@ -895,7 +1004,8 @@ function formatGhostTunnel(config, options = {}) {
895
1004
  if (options.verbose) {
896
1005
  lines.push(` domains: ${config.domains.length > 0 ? config.domains.join(", ") : "*"}`);
897
1006
  lines.push(` access: ${config.requireAuth ? "auth required" : "app decides"}`);
898
- 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}`);
899
1009
  }
900
1010
  return lines.join("\n");
901
1011
  }
@@ -1116,12 +1226,11 @@ async function ensureLocalghostContext(options, vitePort, https) {
1116
1226
  ...typeof https === "boolean" ? { https } : {}
1117
1227
  });
1118
1228
  if (!hasReadySetup(cwd, context.entries, resolved.path, context.https)) {
1119
- if (options.setup === false || !canPrompt()) return context;
1120
- const setup = await confirm("Run caddy:setup now?", true);
1121
- if (setup) {
1122
- await setupProject(cwd, context.entries, resolved.path, context.https);
1123
- console.log(`All set. Setup state: ${getLocalghostStatePath(cwd)}`);
1124
- }
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)}`);
1125
1234
  }
1126
1235
  return context;
1127
1236
  }