@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.
- package/README.md +340 -254
- package/dist/cli.js +993 -89
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +342 -92
- package/dist/index.js +2048 -925
- package/dist/index.js.map +1 -1
- package/dist/{tunnel-DzfLXZ8O.d.ts → tunnel-BA52DD9e.d.ts} +53 -1
- package/dist/vite.d.ts +2 -1
- package/dist/vite.js +131 -17
- package/dist/vite.js.map +1 -1
- package/docs/flows.md +3 -3
- package/docs/ghost-tunnel.md +248 -18
- package/docs/localghost.1.md +43 -7
- package/package.json +2 -1
|
@@ -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 {
|
|
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-
|
|
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
|
|
418
|
-
const
|
|
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
|
-
|
|
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:
|
|
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(`
|
|
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 || !
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
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
|
}
|