@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.
- package/README.md +524 -34
- package/dist/cli.js +995 -93
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +343 -93
- package/dist/index.js +2051 -929
- 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 +126 -17
- package/dist/vite.js.map +1 -1
- package/docs/flows.md +28 -25
- package/docs/ghost-tunnel.md +206 -20
- package/docs/github.md +4 -4
- 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,11 +495,11 @@ function getDisplayValues(input2) {
|
|
|
404
495
|
...input2.values
|
|
405
496
|
};
|
|
406
497
|
}
|
|
407
|
-
function getDisplayDefaults(
|
|
408
|
-
return
|
|
498
|
+
function getDisplayDefaults(defaults) {
|
|
499
|
+
return defaults;
|
|
409
500
|
}
|
|
410
501
|
function createDisplayUrl(config, defaults, domain) {
|
|
411
|
-
const input2 = getPreviewDefaults(config.preview, getDisplayDefaults(
|
|
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(
|
|
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
|
-
|
|
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:
|
|
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(`
|
|
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 || !
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
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
|
}
|