@hamedb89/localghost 0.1.6 → 0.1.8
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 +55 -6
- package/apps/macos-widget/LocalghostWidget.swift +218 -0
- package/apps/macos-widget/build.sh +53 -0
- package/dist/cli.js +224 -45
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +10 -2
- package/dist/index.js +85 -19
- package/dist/index.js.map +1 -1
- package/dist/vite.d.ts +2 -0
- package/dist/vite.js +76 -21
- package/dist/vite.js.map +1 -1
- package/docs/localghost.1.md +20 -5
- package/docs/macos-widget.md +46 -0
- package/package.json +3 -1
package/dist/vite.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/vite.ts
|
|
2
|
-
import { existsSync as
|
|
2
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
3
3
|
import { normalize, resolve as resolve2 } from "path";
|
|
4
4
|
|
|
5
5
|
// src/config.ts
|
|
@@ -118,6 +118,10 @@ function sanitizeProjectName(value) {
|
|
|
118
118
|
return projectName || "app";
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
// src/context.ts
|
|
122
|
+
import { existsSync as existsSync2 } from "fs";
|
|
123
|
+
import { pathToFileURL } from "url";
|
|
124
|
+
|
|
121
125
|
// src/port.ts
|
|
122
126
|
import { createServer } from "net";
|
|
123
127
|
async function isPortAvailable(port, host = "127.0.0.1") {
|
|
@@ -145,6 +149,11 @@ async function findAvailablePort(startPort, options = {}) {
|
|
|
145
149
|
}
|
|
146
150
|
|
|
147
151
|
// src/context.ts
|
|
152
|
+
var LOCALGHOST_PROJECT_CONFIG_FILES = [
|
|
153
|
+
"localghost.config.mjs",
|
|
154
|
+
"localghost.config.js",
|
|
155
|
+
"localghost.config.cjs"
|
|
156
|
+
];
|
|
148
157
|
function parsePort(value) {
|
|
149
158
|
if (!value) return void 0;
|
|
150
159
|
const port = Number.parseInt(value, 10);
|
|
@@ -158,6 +167,11 @@ function envDynamicPort() {
|
|
|
158
167
|
if (!value) return void 0;
|
|
159
168
|
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
160
169
|
}
|
|
170
|
+
function envHttps() {
|
|
171
|
+
const value = process.env.LOCALGHOST_HTTPS;
|
|
172
|
+
if (!value) return void 0;
|
|
173
|
+
return ["1", "true", "yes", "on"].includes(value.toLowerCase());
|
|
174
|
+
}
|
|
161
175
|
function readOptionsFromContext(options) {
|
|
162
176
|
return {
|
|
163
177
|
cwd: options.cwd ?? process.cwd(),
|
|
@@ -175,22 +189,58 @@ function withRuntimePort(entries, requestedPort, port) {
|
|
|
175
189
|
function uniqueHosts(entries) {
|
|
176
190
|
return [...new Set(entries.map((entry) => entry.host))];
|
|
177
191
|
}
|
|
192
|
+
function isAliasableHost(host) {
|
|
193
|
+
return host.includes(".") && !host.startsWith("www.") && !host.includes(":");
|
|
194
|
+
}
|
|
195
|
+
function getDefaultWwwAlias(host) {
|
|
196
|
+
return isAliasableHost(host) ? `www.${host}` : null;
|
|
197
|
+
}
|
|
198
|
+
function addDefaultWwwAliases(entries) {
|
|
199
|
+
const seen = new Set(entries.map((entry) => entry.host));
|
|
200
|
+
const aliases = [];
|
|
201
|
+
for (const entry of entries) {
|
|
202
|
+
const alias = getDefaultWwwAlias(entry.host);
|
|
203
|
+
if (alias && !seen.has(alias)) {
|
|
204
|
+
aliases.push({ host: alias, port: entry.port, target: `127.0.0.1:${entry.port}` });
|
|
205
|
+
seen.add(alias);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return [...entries, ...aliases];
|
|
209
|
+
}
|
|
210
|
+
function defined(input2) {
|
|
211
|
+
return Object.fromEntries(Object.entries(input2).filter(([, value]) => typeof value !== "undefined"));
|
|
212
|
+
}
|
|
213
|
+
async function readProjectConfig(cwd, configFile) {
|
|
214
|
+
if (configFile === false) return {};
|
|
215
|
+
const candidates = configFile ? [configFile] : LOCALGHOST_PROJECT_CONFIG_FILES;
|
|
216
|
+
const path = candidates.map((candidate) => resolveDevHostsPath({ cwd, fileName: candidate }).path).find((candidate) => existsSync2(candidate));
|
|
217
|
+
if (!path) return {};
|
|
218
|
+
const imported = await import(`${pathToFileURL(path).href}?localghost=${Date.now()}`);
|
|
219
|
+
const config = imported.default ?? imported;
|
|
220
|
+
return { config, path };
|
|
221
|
+
}
|
|
178
222
|
async function resolveLocalghostContext(options = {}) {
|
|
179
223
|
const cwd = options.cwd ?? process.cwd();
|
|
180
|
-
const
|
|
224
|
+
const projectConfig = await readProjectConfig(cwd, options.localghostConfig);
|
|
225
|
+
const merged = {
|
|
226
|
+
...projectConfig.config,
|
|
227
|
+
...defined(options)
|
|
228
|
+
};
|
|
229
|
+
const readOptions = readOptionsFromContext({ ...merged, cwd });
|
|
181
230
|
const resolvedPath = resolveDevHostsPath(readOptions);
|
|
182
231
|
const configEntries = readDevHosts(readOptions);
|
|
183
|
-
const requestedPort =
|
|
184
|
-
const dynamicPort =
|
|
185
|
-
const bindHost =
|
|
232
|
+
const requestedPort = merged.port ?? envPort() ?? configEntries[0]?.port ?? 5173;
|
|
233
|
+
const dynamicPort = merged.dynamicPort ?? envDynamicPort() ?? false;
|
|
234
|
+
const bindHost = merged.bindHost ?? "127.0.0.1";
|
|
186
235
|
const probeHost = typeof bindHost === "string" ? bindHost : "127.0.0.1";
|
|
187
236
|
const port = dynamicPort ? await findAvailablePort(requestedPort, { host: probeHost }) : requestedPort;
|
|
188
|
-
const
|
|
237
|
+
const wwwAlias = merged.wwwAlias ?? true;
|
|
238
|
+
const entries = wwwAlias ? addDefaultWwwAliases(withRuntimePort(configEntries, requestedPort, port)) : withRuntimePort(configEntries, requestedPort, port);
|
|
189
239
|
const hosts = uniqueHosts(entries);
|
|
190
|
-
const primaryHost =
|
|
240
|
+
const primaryHost = merged.primaryHost ?? entries.find((entry) => entry.port === port)?.host ?? hosts[0] ?? `${sanitizeProjectName(getProjectName(cwd))}.localhost`;
|
|
191
241
|
return {
|
|
192
242
|
cwd,
|
|
193
|
-
projectName: sanitizeProjectName(
|
|
243
|
+
projectName: sanitizeProjectName(merged.project ?? getProjectName(cwd)),
|
|
194
244
|
readOptions,
|
|
195
245
|
configPath: resolvedPath.path,
|
|
196
246
|
configFileName: resolvedPath.fileName,
|
|
@@ -202,7 +252,9 @@ async function resolveLocalghostContext(options = {}) {
|
|
|
202
252
|
dynamicPort,
|
|
203
253
|
bindHost,
|
|
204
254
|
primaryHost,
|
|
205
|
-
https:
|
|
255
|
+
https: merged.https ?? envHttps() ?? false,
|
|
256
|
+
wwwAlias,
|
|
257
|
+
...projectConfig.path ? { projectConfigPath: projectConfig.path } : {}
|
|
206
258
|
};
|
|
207
259
|
}
|
|
208
260
|
|
|
@@ -342,7 +394,7 @@ async function ask(question, defaultValue) {
|
|
|
342
394
|
}
|
|
343
395
|
|
|
344
396
|
// src/state.ts
|
|
345
|
-
import { existsSync as
|
|
397
|
+
import { existsSync as existsSync3 } from "fs";
|
|
346
398
|
import { join as join3 } from "path";
|
|
347
399
|
var LOCALGHOST_STATE_FILE = "ops/local/localghost-state.json";
|
|
348
400
|
function getLocalghostStatePath(cwd = process.cwd()) {
|
|
@@ -350,12 +402,12 @@ function getLocalghostStatePath(cwd = process.cwd()) {
|
|
|
350
402
|
}
|
|
351
403
|
function readLocalghostState(cwd = process.cwd()) {
|
|
352
404
|
const path = getLocalghostStatePath(cwd);
|
|
353
|
-
if (!
|
|
405
|
+
if (!existsSync3(path)) return null;
|
|
354
406
|
return JSON.parse(readTextFile(path));
|
|
355
407
|
}
|
|
356
408
|
function writeLocalghostState(cwd, state) {
|
|
357
409
|
const path = getLocalghostStatePath(cwd);
|
|
358
|
-
writeTextFile(path, `${JSON.stringify({ version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
410
|
+
writeTextFile(path, `${JSON.stringify({ ...state, version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
|
|
359
411
|
`);
|
|
360
412
|
return path;
|
|
361
413
|
}
|
|
@@ -452,7 +504,8 @@ function getConfigWatchFiles(options) {
|
|
|
452
504
|
const cwd = readOptions.cwd ?? process.cwd();
|
|
453
505
|
const resolvedPath = resolveDevHostsPath(readOptions);
|
|
454
506
|
const candidatePaths = getConfigFileCandidates(readOptions).map((fileName) => resolve2(cwd, fileName));
|
|
455
|
-
|
|
507
|
+
const projectConfigPaths = options.localghostConfig === false ? [] : options.localghostConfig ? [resolve2(cwd, options.localghostConfig)] : ["localghost.config.mjs", "localghost.config.js", "localghost.config.cjs"].map((fileName) => resolve2(cwd, fileName));
|
|
508
|
+
return [.../* @__PURE__ */ new Set([...candidatePaths, resolvedPath.path, ...projectConfigPaths])];
|
|
456
509
|
}
|
|
457
510
|
function normalizeWatchPath(filePath) {
|
|
458
511
|
return normalize(resolve2(filePath));
|
|
@@ -476,7 +529,7 @@ async function promptForHosts(cwd, port) {
|
|
|
476
529
|
const host = await ask("Domain");
|
|
477
530
|
if (host) hosts.push(host.toLowerCase());
|
|
478
531
|
}
|
|
479
|
-
return [...new Set(hosts)];
|
|
532
|
+
return [...new Set(addDefaultWwwAliases(hosts.map((host) => ({ host, port, target: `127.0.0.1:${port}` }))).map((entry) => entry.host))];
|
|
480
533
|
}
|
|
481
534
|
function hasReadySetup(cwd, entries, configPath, https) {
|
|
482
535
|
const state = readLocalghostState(cwd);
|
|
@@ -489,7 +542,7 @@ function hasReadySetup(cwd, entries, configPath, https) {
|
|
|
489
542
|
return false;
|
|
490
543
|
}
|
|
491
544
|
const caddyfilePath = getCaddyfilePath(cwd);
|
|
492
|
-
return
|
|
545
|
+
return existsSync4(caddyfilePath) && readFileSync3(caddyfilePath, "utf8") === renderCaddyfile(entries, { https });
|
|
493
546
|
}
|
|
494
547
|
async function setupProject(cwd, entries, configPath, https) {
|
|
495
548
|
const caddy = await checkCaddy();
|
|
@@ -541,13 +594,13 @@ async function ensureLocalghostContext(options, vitePort, https) {
|
|
|
541
594
|
...options,
|
|
542
595
|
cwd,
|
|
543
596
|
port: vitePort,
|
|
544
|
-
https
|
|
597
|
+
...typeof https === "boolean" ? { https } : {}
|
|
545
598
|
});
|
|
546
|
-
if (!hasReadySetup(cwd, context.entries, resolved.path, https)) {
|
|
599
|
+
if (!hasReadySetup(cwd, context.entries, resolved.path, context.https)) {
|
|
547
600
|
if (options.setup === false || !canPrompt()) return context;
|
|
548
601
|
const setup = await confirm("Run caddy:setup now?", true);
|
|
549
602
|
if (setup) {
|
|
550
|
-
await setupProject(cwd, context.entries, resolved.path, https);
|
|
603
|
+
await setupProject(cwd, context.entries, resolved.path, context.https);
|
|
551
604
|
console.log(`All set. Setup state: ${getLocalghostStatePath(cwd)}`);
|
|
552
605
|
}
|
|
553
606
|
}
|
|
@@ -556,6 +609,7 @@ async function ensureLocalghostContext(options, vitePort, https) {
|
|
|
556
609
|
function localGhostPlugin(options = {}) {
|
|
557
610
|
let resolvedEntries = [];
|
|
558
611
|
let resolvedVitePort;
|
|
612
|
+
let resolvedHttps = false;
|
|
559
613
|
let restartTimer;
|
|
560
614
|
return {
|
|
561
615
|
name: "localghost:vite",
|
|
@@ -567,12 +621,13 @@ function localGhostPlugin(options = {}) {
|
|
|
567
621
|
const existingServer = userConfig.server ?? {};
|
|
568
622
|
const envVitePort = Number.parseInt(process.env.LOCALGHOST_PORT ?? process.env.VITE_PORT ?? "", 10);
|
|
569
623
|
const requestedVitePort = options.port ?? existingServer.port ?? (Number.isInteger(envVitePort) ? envVitePort : 5173);
|
|
570
|
-
const context = await ensureLocalghostContext(options, requestedVitePort,
|
|
624
|
+
const context = await ensureLocalghostContext(options, requestedVitePort, options.https);
|
|
571
625
|
const entries = context.entries;
|
|
572
626
|
const hosts = context.hosts;
|
|
573
627
|
const primaryHost = context.primaryHost;
|
|
574
628
|
resolvedEntries = entries;
|
|
575
629
|
resolvedVitePort = context.port;
|
|
630
|
+
resolvedHttps = context.https;
|
|
576
631
|
const server = {
|
|
577
632
|
...existingServer,
|
|
578
633
|
allowedHosts: mergeAllowedHosts(existingServer.allowedHosts, hosts),
|
|
@@ -584,7 +639,7 @@ function localGhostPlugin(options = {}) {
|
|
|
584
639
|
if (context.port) {
|
|
585
640
|
server.port = context.port;
|
|
586
641
|
}
|
|
587
|
-
if (
|
|
642
|
+
if (context.https && primaryHost) {
|
|
588
643
|
const existingWs = typeof server.ws === "object" && server.ws ? server.ws : {};
|
|
589
644
|
const existingHmr = typeof existingServer.hmr === "object" && existingServer.hmr ? existingServer.hmr : {};
|
|
590
645
|
server.ws = {
|
|
@@ -632,7 +687,7 @@ function localGhostPlugin(options = {}) {
|
|
|
632
687
|
server.watcher.on("unlink", restartOnLocalghostConfigChange);
|
|
633
688
|
if (options.log !== false) {
|
|
634
689
|
server.printUrls = () => {
|
|
635
|
-
printLocalHosts(server, resolvedEntries, resolvedVitePort,
|
|
690
|
+
printLocalHosts(server, resolvedEntries, resolvedVitePort, resolvedHttps);
|
|
636
691
|
};
|
|
637
692
|
}
|
|
638
693
|
}
|
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vite.ts","../src/config.ts","../src/parse.ts","../src/port.ts","../src/context.ts","../src/doctor.ts","../src/env.ts","../src/fs.ts","../src/hosts-file.ts","../src/prompt.ts","../src/state.ts","../src/caddy.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { normalize, resolve } from \"node:path\";\nimport type { ConfigEnv, HmrOptions, Plugin, UserConfig, ViteDevServer, WsOptions } from \"vite\";\nimport {\n getConfigFileCandidates,\n getProjectName,\n resolveDevHostsPath,\n sanitizeProjectName,\n type ConfigPattern,\n type ReadDevHostsOptions\n} from \"./config.js\";\nimport { resolveLocalghostContext, type LocalghostContext } from \"./context.js\";\nimport { checkCaddy } from \"./doctor.js\";\nimport { isProductionLike } from \"./env.js\";\nimport { writeTextFile } from \"./fs.js\";\nimport { getSystemHostsPath, renderHostsBlock, updateSystemHosts } from \"./hosts-file.js\";\nimport { ask, canPrompt, confirm } from \"./prompt.js\";\nimport { getLocalghostStatePath, readLocalghostState, writeLocalghostState } from \"./state.js\";\nimport { getCaddyfilePath, renderCaddyfile, validateCaddyfile, writeCaddyfile } from \"./caddy.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type LocalGhostPluginOptions = {\n cwd?: string;\n fileName?: string;\n configFiles?: string[];\n configPattern?: ConfigPattern;\n port?: number;\n https?: boolean;\n bindHost?: string | boolean;\n dynamicPort?: boolean;\n primaryHost?: string;\n log?: boolean;\n setup?: boolean | \"prompt\";\n};\n\ntype ServerOptions = NonNullable<UserConfig[\"server\"]>;\n\nfunction mergeAllowedHosts(current: ServerOptions[\"allowedHosts\"], hosts: string[]) {\n if (Array.isArray(current)) {\n return [...new Set([...current, ...hosts])];\n }\n\n return hosts;\n}\n\nfunction getDisplayEntries(entries: DevHostEntry[], vitePort: number | undefined) {\n if (!vitePort) {\n return entries;\n }\n\n const matchingEntries = entries.filter((entry) => entry.port === vitePort);\n return matchingEntries.length > 0 ? matchingEntries : entries;\n}\n\nfunction printLocalHosts(server: ViteDevServer, entries: DevHostEntry[], vitePort: number | undefined, https: boolean) {\n const displayEntries = getDisplayEntries(entries, vitePort);\n const protocol = https ? \"https\" : \"http\";\n const urls = displayEntries.map((entry) => `${protocol}://${entry.host}/`);\n const primaryUrl = urls[0];\n\n if (!primaryUrl) {\n return;\n }\n\n const lines = [\n \"\",\n \" localghost\",\n ` local: ${primaryUrl}`,\n ...urls.slice(1).map((url) => ` also: ${url}`),\n vitePort ? ` target: http://127.0.0.1:${vitePort}/` : undefined,\n https ? \" proxy: Caddy local HTTPS\" : undefined\n ].filter((line): line is string => Boolean(line));\n\n server.config.logger.info(lines.join(\"\\n\"), {\n clear: false,\n timestamp: false\n });\n}\n\nfunction readOptionsFromPlugin(options: LocalGhostPluginOptions): ReadDevHostsOptions {\n return {\n cwd: options.cwd ?? process.cwd(),\n ...(options.fileName ? { fileName: options.fileName } : {}),\n ...(options.configFiles ? { configFiles: options.configFiles } : {}),\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n}\n\nfunction getConfigWatchFiles(options: LocalGhostPluginOptions) {\n const readOptions = readOptionsFromPlugin(options);\n const cwd = readOptions.cwd ?? process.cwd();\n const resolvedPath = resolveDevHostsPath(readOptions);\n const candidatePaths = getConfigFileCandidates(readOptions).map((fileName) => resolve(cwd, fileName));\n\n return [...new Set([...candidatePaths, resolvedPath.path])];\n}\n\nfunction normalizeWatchPath(filePath: string) {\n return normalize(resolve(filePath));\n}\n\nfunction renderConfig(hosts: string[], port: number) {\n return [\n \"# Buh. Friendly names for local services.\",\n \"# Format: <host> <port>\",\n ...hosts.map((host) => `${host} ${port}`),\n \"\"\n ].join(\"\\n\");\n}\n\nfunction defaultHost(cwd: string) {\n const projectName = sanitizeProjectName(getProjectName(cwd).split(\"/\").pop() ?? \"app\");\n return `${projectName}.localhost`;\n}\n\nasync function promptForHosts(cwd: string, port: number) {\n const primaryHost = await ask(\"Primary local domain\", defaultHost(cwd));\n const hosts = [primaryHost.toLowerCase()];\n\n while (await confirm(\"Add another local domain?\", false)) {\n const host = await ask(\"Domain\");\n if (host) hosts.push(host.toLowerCase());\n }\n\n return [...new Set(hosts)];\n}\n\nfunction hasReadySetup(cwd: string, entries: DevHostEntry[], configPath: string, https: boolean) {\n const state = readLocalghostState(cwd);\n const projectName = sanitizeProjectName(getProjectName(cwd));\n if (state?.action !== \"setup\" || state.configPath !== configPath) return false;\n\n try {\n const hosts = readFileSync(getSystemHostsPath(), \"utf8\");\n if (!hosts.includes(renderHostsBlock(projectName, entries).trimEnd())) return false;\n } catch {\n return false;\n }\n\n const caddyfilePath = getCaddyfilePath(cwd);\n return existsSync(caddyfilePath) && readFileSync(caddyfilePath, \"utf8\") === renderCaddyfile(entries, { https });\n}\n\nasync function setupProject(cwd: string, entries: DevHostEntry[], configPath: string, https: boolean) {\n const caddy = await checkCaddy();\n if (!caddy.found) {\n throw new Error([\n \"Caddy is missing.\",\n `Run: ${caddy.installHint}`,\n \"Localghost will not install it for you.\"\n ].join(\"\\n\"));\n }\n\n const projectName = sanitizeProjectName(getProjectName(cwd));\n console.log(\"Buh. macOS keeps local hostnames in /etc/hosts, so Localghost may ask for your password.\");\n console.log(\"It will only touch its managed Localghost block.\");\n const hostsResult = await updateSystemHosts(projectName, entries);\n const caddyfilePath = await writeCaddyfile(entries, cwd, { https });\n await validateCaddyfile(caddyfilePath);\n writeLocalghostState(cwd, {\n action: \"setup\",\n projectName,\n cwd,\n configPath,\n hostsPath: hostsResult.hostsPath,\n hostsChanged: hostsResult.changed,\n ...(hostsResult.tempPath ? { hostsTempPath: hostsResult.tempPath } : {}),\n caddyfilePath,\n caddyHttps: https,\n entries\n });\n}\n\nasync function ensureLocalghostContext(options: LocalGhostPluginOptions, vitePort: number, https: boolean) {\n const cwd = options.cwd ?? process.cwd();\n const readOptions = readOptionsFromPlugin(options);\n const resolved = resolveDevHostsPath(readOptions);\n\n if (!resolved.exists) {\n if (options.setup === false || !canPrompt()) {\n throw new Error(\n `No .localghost found at ${resolved.path}. Run \\`localghost init --write-scripts\\` or start Vite in an interactive terminal.`\n );\n }\n\n console.log(`No .localghost found at ${resolved.path}.`);\n if (!(await confirm(\"Create one now?\", true))) {\n throw new Error(\"Localghost setup skipped. Create .localghost before running the Vite plugin.\");\n }\n\n const hosts = await promptForHosts(cwd, vitePort);\n writeTextFile(resolved.path, renderConfig(hosts, vitePort));\n console.log(`Created ${resolved.path}`);\n }\n\n const context = await resolveLocalghostContext({\n ...options,\n cwd,\n port: vitePort,\n https\n });\n\n if (!hasReadySetup(cwd, context.entries, resolved.path, https)) {\n if (options.setup === false || !canPrompt()) return context;\n\n const setup = await confirm(\"Run caddy:setup now?\", true);\n if (setup) {\n await setupProject(cwd, context.entries, resolved.path, https);\n console.log(`All set. Setup state: ${getLocalghostStatePath(cwd)}`);\n }\n }\n\n return context;\n}\n\nexport function localGhostPlugin(options: LocalGhostPluginOptions = {}): Plugin {\n let resolvedEntries: DevHostEntry[] = [];\n let resolvedVitePort: number | undefined;\n let restartTimer: NodeJS.Timeout | undefined;\n\n return {\n name: \"localghost:vite\",\n enforce: \"pre\",\n\n async config(userConfig, configEnv: ConfigEnv): Promise<UserConfig> {\n if (configEnv.command !== \"serve\" || configEnv.mode === \"production\" || isProductionLike()) {\n return {};\n }\n\n const existingServer = userConfig.server ?? {};\n const envVitePort = Number.parseInt(process.env.LOCALGHOST_PORT ?? process.env.VITE_PORT ?? \"\", 10);\n const requestedVitePort =\n options.port ??\n existingServer.port ??\n (Number.isInteger(envVitePort) ? envVitePort : 5173);\n const context: LocalghostContext = await ensureLocalghostContext(options, requestedVitePort, Boolean(options.https));\n const entries = context.entries;\n const hosts = context.hosts;\n const primaryHost = context.primaryHost;\n\n resolvedEntries = entries;\n resolvedVitePort = context.port;\n\n const server: ServerOptions = {\n ...existingServer,\n allowedHosts: mergeAllowedHosts(existingServer.allowedHosts, hosts),\n strictPort: existingServer.strictPort ?? true\n };\n\n if (typeof existingServer.host === \"undefined\") {\n server.host = context.bindHost;\n }\n\n if (context.port) {\n server.port = context.port;\n }\n\n if (options.https && primaryHost) {\n const existingWs = typeof server.ws === \"object\" && server.ws ? server.ws : {};\n const existingHmr = typeof existingServer.hmr === \"object\" && existingServer.hmr ? existingServer.hmr : {};\n\n server.ws = {\n ...existingWs,\n protocol: \"wss\",\n host: primaryHost,\n clientPort: 443\n } satisfies WsOptions;\n\n server.hmr = {\n ...existingHmr,\n protocol: \"wss\",\n host: primaryHost,\n clientPort: 443\n } satisfies HmrOptions;\n }\n\n return { server };\n },\n\n configureServer(server) {\n const watchFiles = getConfigWatchFiles(options);\n const watchedConfigFiles = new Set(watchFiles.map(normalizeWatchPath));\n\n server.watcher.add(watchFiles);\n\n const restartOnLocalghostConfigChange = (filePath: string) => {\n if (!watchedConfigFiles.has(normalizeWatchPath(filePath))) {\n return;\n }\n\n if (restartTimer) {\n clearTimeout(restartTimer);\n }\n\n restartTimer = setTimeout(() => {\n if (options.log !== false) {\n server.config.logger.info(\"localghost config changed; restarting Vite dev server\", {\n clear: false,\n timestamp: false\n });\n }\n\n void server.restart().catch((error: unknown) => {\n server.config.logger.error(error instanceof Error ? error.message : String(error), {\n timestamp: false\n });\n });\n }, 50);\n };\n\n server.watcher.on(\"add\", restartOnLocalghostConfigChange);\n server.watcher.on(\"change\", restartOnLocalghostConfigChange);\n server.watcher.on(\"unlink\", restartOnLocalghostConfigChange);\n\n if (options.log !== false) {\n server.printUrls = () => {\n printLocalHosts(server, resolvedEntries, resolvedVitePort, Boolean(options.https));\n };\n }\n }\n };\n}\n\nexport const localHostsPlugin = localGhostPlugin;\nexport type LocalHostsPluginOptions = LocalGhostPluginOptions;\n","import { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { basename, join, resolve } from \"node:path\";\nimport { parseDevHosts } from \"./parse.js\";\n\nexport const LOCALGHOST_CONFIG_FILE = \".localghost\";\n\nexport type ConfigPattern = string | RegExp;\n\nexport type ReadDevHostsOptions = {\n cwd?: string;\n fileName?: string;\n configFiles?: string[];\n configPattern?: ConfigPattern;\n};\n\nexport type ResolvedDevHostsPath = {\n path: string;\n fileName: string;\n exists: boolean;\n searchedFiles: string[];\n configPattern?: ConfigPattern;\n};\n\nfunction unique(values: string[]) {\n return [...new Set(values.filter(Boolean))];\n}\n\nfunction toRegExp(pattern: ConfigPattern) {\n return typeof pattern === \"string\" ? new RegExp(pattern) : pattern;\n}\n\nfunction findPatternMatches(cwd: string, pattern: ConfigPattern) {\n const matcher = toRegExp(pattern);\n\n return readdirSync(cwd, { withFileTypes: true })\n .filter((entry) => entry.isFile())\n .map((entry) => entry.name)\n .filter((name) => {\n matcher.lastIndex = 0;\n return matcher.test(name);\n })\n .sort();\n}\n\nexport function getConfigFileCandidates(options: ReadDevHostsOptions = {}) {\n const cwd = options.cwd ?? process.cwd();\n const exactFiles = unique([\n ...(options.fileName ? [options.fileName] : []),\n ...(options.configFiles ?? [])\n ]);\n const patternFiles = options.configPattern ? findPatternMatches(cwd, options.configPattern) : [];\n const candidates = unique([...exactFiles, ...patternFiles]);\n\n if (candidates.length > 0) return candidates;\n if (exactFiles.length > 0 || options.configPattern) return [];\n return [LOCALGHOST_CONFIG_FILE];\n}\n\nexport function resolveDevHostsPath(options: ReadDevHostsOptions = {}): ResolvedDevHostsPath {\n const cwd = options.cwd ?? process.cwd();\n const searchedFiles = getConfigFileCandidates(options);\n\n for (const fileName of searchedFiles) {\n const path = resolve(cwd, fileName);\n if (existsSync(path)) {\n return {\n path,\n fileName: basename(fileName),\n exists: true,\n searchedFiles,\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n }\n }\n\n const fileName = searchedFiles[0] ?? LOCALGHOST_CONFIG_FILE;\n\n return {\n path: resolve(cwd, fileName),\n fileName: basename(fileName),\n exists: false,\n searchedFiles,\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n}\n\nexport function getDevHostsPath(options: ReadDevHostsOptions = {}) {\n return resolveDevHostsPath(options).path;\n}\n\nfunction formatSearchedFiles(files: string[], pattern?: ConfigPattern) {\n if (files.length > 0) return files.map((file) => `\\`${file}\\``).join(\", \");\n if (pattern) return `files matching ${pattern.toString()}`;\n return `\\`${LOCALGHOST_CONFIG_FILE}\\``;\n}\n\nexport function readDevHosts(options: ReadDevHostsOptions | string = {}) {\n const resolvedOptions = typeof options === \"string\" ? { cwd: options } : options;\n const resolvedPath = resolveDevHostsPath(resolvedOptions);\n\n if (!resolvedPath.exists) {\n const cwd = resolvedOptions.cwd ?? process.cwd();\n throw new Error(\n `Missing Localghost config in ${cwd}. Looked for ${formatSearchedFiles(resolvedPath.searchedFiles, resolvedPath.configPattern)}. Run \\`localghost init\\` or pass --config/--config-pattern.`\n );\n }\n\n return parseDevHosts(readFileSync(resolvedPath.path, \"utf8\"), resolvedPath.fileName);\n}\n\nexport function getProjectName(cwd = process.cwd()) {\n try {\n const pkg = JSON.parse(readFileSync(join(cwd, \"package.json\"), \"utf8\")) as { name?: unknown };\n const name = typeof pkg.name === \"string\" && pkg.name ? pkg.name : \"app\";\n return sanitizeProjectName(name.replace(/^@/, \"\"));\n } catch {\n return \"app\";\n }\n}\n\nexport function sanitizeProjectName(value: string) {\n const projectName = value.replace(/[^\\w.-]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n return projectName || \"app\";\n}\n","export type DevHostEntry = {\n host: string;\n port: number;\n target: string;\n};\n\nconst HOST_PATTERN = /^(?=.{1,253}$)(?!-)[a-z0-9-]+(\\.[a-z0-9-]+)*\\.?$/i;\n\nexport function parseDevHosts(input: string, fileName = \".localghost\"): DevHostEntry[] {\n const entries: DevHostEntry[] = [];\n\n input.split(/\\r?\\n/).forEach((rawLine, index) => {\n const line = rawLine.replace(/#.*/, \"\").trim();\n\n if (!line) {\n return;\n }\n\n const parts = line.split(/\\s+/);\n const host = parts[0];\n const portRaw = parts[1];\n\n if (!host || !portRaw || parts.length > 2) {\n throw new Error(`Invalid ${fileName} line ${index + 1}: \"${rawLine}\"`);\n }\n\n if (!HOST_PATTERN.test(host)) {\n throw new Error(`Invalid host on line ${index + 1}: \"${host}\"`);\n }\n\n const port = Number(portRaw);\n\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(`Invalid port on line ${index + 1}: \"${portRaw}\"`);\n }\n\n entries.push({\n host: host.toLowerCase().replace(/\\.$/, \"\"),\n port,\n target: `127.0.0.1:${port}`\n });\n });\n\n return entries;\n}\n\nexport function findLocalMdnsHosts(entries: DevHostEntry[]): string[] {\n return [...new Set(entries.map((entry) => entry.host).filter((host) => host.endsWith(\".local\")))];\n}\n","import { createServer } from \"node:net\";\n\nexport type FindAvailablePortOptions = {\n host?: string;\n maxAttempts?: number;\n};\n\nexport async function isPortAvailable(port: number, host = \"127.0.0.1\") {\n return new Promise<boolean>((resolve) => {\n const server = createServer();\n\n server.once(\"error\", () => {\n resolve(false);\n });\n\n server.once(\"listening\", () => {\n server.close(() => resolve(true));\n });\n\n server.listen(port, host);\n });\n}\n\nexport async function findAvailablePort(startPort: number, options: FindAvailablePortOptions = {}) {\n const host = options.host ?? \"127.0.0.1\";\n const maxAttempts = options.maxAttempts ?? 50;\n\n for (let offset = 0; offset < maxAttempts; offset += 1) {\n const port = startPort + offset;\n if (await isPortAvailable(port, host)) {\n return port;\n }\n }\n\n throw new Error(`No available port found from ${startPort} to ${startPort + maxAttempts - 1}.`);\n}\n","import {\n getProjectName,\n readDevHosts,\n resolveDevHostsPath,\n sanitizeProjectName,\n type ConfigPattern,\n type ReadDevHostsOptions\n} from \"./config.js\";\nimport { findAvailablePort } from \"./port.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type LocalghostContextOptions = {\n cwd?: string;\n project?: string;\n fileName?: string;\n configFiles?: string[];\n configPattern?: ConfigPattern;\n port?: number;\n https?: boolean;\n bindHost?: string | boolean;\n primaryHost?: string;\n dynamicPort?: boolean;\n};\n\nexport type LocalghostContext = {\n cwd: string;\n projectName: string;\n readOptions: ReadDevHostsOptions;\n configPath: string;\n configFileName: string;\n configEntries: DevHostEntry[];\n entries: DevHostEntry[];\n hosts: string[];\n requestedPort: number;\n port: number;\n dynamicPort: boolean;\n bindHost: string | boolean;\n primaryHost: string;\n https: boolean;\n};\n\nfunction parsePort(value: string | undefined) {\n if (!value) return undefined;\n const port = Number.parseInt(value, 10);\n return Number.isInteger(port) && port > 0 && port <= 65535 ? port : undefined;\n}\n\nfunction envPort() {\n return parsePort(process.env.LOCALGHOST_PORT) ?? parsePort(process.env.VITE_PORT);\n}\n\nfunction envDynamicPort() {\n const value = process.env.LOCALGHOST_DYNAMIC_PORT;\n if (!value) return undefined;\n return [\"1\", \"true\", \"yes\", \"on\"].includes(value.toLowerCase());\n}\n\nfunction readOptionsFromContext(options: LocalghostContextOptions): ReadDevHostsOptions {\n return {\n cwd: options.cwd ?? process.cwd(),\n ...(options.fileName ? { fileName: options.fileName } : {}),\n ...(options.configFiles ? { configFiles: options.configFiles } : {}),\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n}\n\nfunction withRuntimePort(entries: DevHostEntry[], requestedPort: number, port: number) {\n if (requestedPort === port) return entries;\n\n const hasRequestedPort = entries.some((entry) => entry.port === requestedPort);\n if (!hasRequestedPort) return entries;\n\n return entries.map((entry) => (entry.port === requestedPort ? { ...entry, port } : entry));\n}\n\nfunction uniqueHosts(entries: DevHostEntry[]) {\n return [...new Set(entries.map((entry) => entry.host))];\n}\n\nexport function defineLocalghostConfig<T extends LocalghostContextOptions>(config: T) {\n return config;\n}\n\nexport async function resolveLocalghostContext(options: LocalghostContextOptions = {}): Promise<LocalghostContext> {\n const cwd = options.cwd ?? process.cwd();\n const readOptions = readOptionsFromContext({ ...options, cwd });\n const resolvedPath = resolveDevHostsPath(readOptions);\n const configEntries = readDevHosts(readOptions);\n const requestedPort = options.port ?? envPort() ?? configEntries[0]?.port ?? 5173;\n const dynamicPort = options.dynamicPort ?? envDynamicPort() ?? false;\n const bindHost = options.bindHost ?? \"127.0.0.1\";\n const probeHost = typeof bindHost === \"string\" ? bindHost : \"127.0.0.1\";\n const port = dynamicPort ? await findAvailablePort(requestedPort, { host: probeHost }) : requestedPort;\n const entries = withRuntimePort(configEntries, requestedPort, port);\n const hosts = uniqueHosts(entries);\n const primaryHost =\n options.primaryHost ??\n entries.find((entry) => entry.port === port)?.host ??\n hosts[0] ??\n `${sanitizeProjectName(getProjectName(cwd))}.localhost`;\n\n return {\n cwd,\n projectName: sanitizeProjectName(options.project ?? getProjectName(cwd)),\n readOptions,\n configPath: resolvedPath.path,\n configFileName: resolvedPath.fileName,\n configEntries,\n entries,\n hosts,\n requestedPort,\n port,\n dynamicPort,\n bindHost,\n primaryHost,\n https: options.https === true\n };\n}\n","import { execa } from \"execa\";\n\nexport type DoctorResult = {\n ok: boolean;\n caddy: {\n found: boolean;\n version?: string;\n installHint: string;\n };\n};\n\nexport async function checkCaddy(): Promise<DoctorResult[\"caddy\"]> {\n try {\n const result = await execa(\"caddy\", [\"version\"], { reject: false });\n const version = [result.stdout, result.stderr].filter(Boolean).join(\"\\n\").trim();\n\n return {\n found: result.exitCode === 0,\n ...(version ? { version } : {}),\n installHint: \"brew install caddy\"\n };\n } catch {\n return {\n found: false,\n installHint: \"brew install caddy\"\n };\n }\n}\n\nexport async function runDoctor(): Promise<DoctorResult> {\n const caddy = await checkCaddy();\n return {\n ok: caddy.found,\n caddy\n };\n}\n","export type LocalghostEnvironment = NodeJS.ProcessEnv;\n\nconst PRODUCTION_ENV_KEYS = [\"NODE_ENV\", \"VERCEL_ENV\", \"NETLIFY\", \"CF_PAGES_BRANCH\", \"LOCALGHOST_ENV\"] as const;\n\nexport function getProductionReason(env: LocalghostEnvironment = process.env) {\n if (env.LOCALGHOST_ENV === \"production\") return \"LOCALGHOST_ENV=production\";\n if (env.NODE_ENV === \"production\") return \"NODE_ENV=production\";\n if (env.VERCEL_ENV === \"production\") return \"VERCEL_ENV=production\";\n if (env.NETLIFY === \"true\" && env.CONTEXT === \"production\") return \"NETLIFY=true and CONTEXT=production\";\n if (env.CF_PAGES_BRANCH && env.CF_PAGES_BRANCH === env.CF_PAGES_PRODUCTION_BRANCH) {\n return \"CF_PAGES_BRANCH matches CF_PAGES_PRODUCTION_BRANCH\";\n }\n\n return null;\n}\n\nexport function isProductionLike(env: LocalghostEnvironment = process.env) {\n return getProductionReason(env) !== null;\n}\n\nexport function assertLocalDevelopment(command: string, env: LocalghostEnvironment = process.env) {\n const reason = getProductionReason(env);\n if (!reason) return;\n\n throw new Error(`Localghost only runs in local development. Refusing \\`${command}\\` because ${reason}.`);\n}\n\nexport function getProductionEnvKeys() {\n return PRODUCTION_ENV_KEYS;\n}\n","import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport function readTextFile(path: string) {\n return readFileSync(path, \"utf8\");\n}\n\nexport function writeTextFile(path: string, value: string) {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, value, \"utf8\");\n return path;\n}\n","import { writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { execa } from \"execa\";\nimport { sanitizeProjectName } from \"./config.js\";\nimport { readTextFile } from \"./fs.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type UpdateSystemHostsResult = {\n changed: boolean;\n hostsPath: string;\n tempPath?: string;\n};\n\nexport type RemoveSystemHostsResult = UpdateSystemHostsResult & {\n removed: boolean;\n};\n\nfunction escapeRegExp(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction getManagedBlockPattern(projectName: string) {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const start = `# localghost:start ${sanitizedProjectName}`;\n const end = `# localghost:end ${sanitizedProjectName}`;\n return new RegExp(`${escapeRegExp(start)}[\\\\s\\\\S]*?${escapeRegExp(end)}\\\\n?`, \"m\");\n}\n\nexport function getSystemHostsPath() {\n return process.platform === \"win32\" ? \"C:\\\\Windows\\\\System32\\\\drivers\\\\etc\\\\hosts\" : \"/etc/hosts\";\n}\n\nexport function renderHostsBlock(projectName: string, entries: DevHostEntry[]) {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const hosts = [...new Set(entries.map((entry) => entry.host))].sort();\n\n return [\n `# localghost:start ${sanitizedProjectName}`,\n ...hosts.map((host) => `127.0.0.1 ${host}`),\n `# localghost:end ${sanitizedProjectName}`,\n \"\"\n ].join(\"\\n\");\n}\n\nexport function upsertManagedBlock(existing: string, projectName: string, block: string) {\n const pattern = getManagedBlockPattern(projectName);\n\n if (pattern.test(existing)) {\n return existing.replace(pattern, block);\n }\n\n return `${existing.trimEnd()}\\n\\n${block}`;\n}\n\nexport function removeManagedBlock(existing: string, projectName: string) {\n const pattern = getManagedBlockPattern(projectName);\n\n if (!pattern.test(existing)) {\n return existing;\n }\n\n return existing.replace(pattern, \"\").replace(/\\n{3,}/g, \"\\n\\n\").trimEnd() + \"\\n\";\n}\n\nasync function writeSystemHostsFile(hostsPath: string, next: string, projectName: string) {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const tempPath = join(tmpdir(), `localghost-${sanitizedProjectName}-hosts`);\n writeFileSync(tempPath, next, \"utf8\");\n\n if (process.platform === \"win32\") {\n throw new Error(`Windows support: run as administrator and copy ${tempPath} to ${hostsPath}.`);\n }\n\n await execa(\"sudo\", [\"cp\", tempPath, hostsPath], { stdio: \"inherit\" });\n\n return tempPath;\n}\n\nexport async function updateSystemHosts(projectName: string, entries: DevHostEntry[]): Promise<UpdateSystemHostsResult> {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const hostsPath = getSystemHostsPath();\n const existing = readTextFile(hostsPath);\n const block = renderHostsBlock(sanitizedProjectName, entries);\n const next = upsertManagedBlock(existing, sanitizedProjectName, block);\n\n if (next === existing) {\n return { changed: false, hostsPath };\n }\n\n const tempPath = await writeSystemHostsFile(hostsPath, next, sanitizedProjectName);\n\n return { changed: true, hostsPath, tempPath };\n}\n\nexport async function removeSystemHosts(projectName: string): Promise<RemoveSystemHostsResult> {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const hostsPath = getSystemHostsPath();\n const existing = readTextFile(hostsPath);\n const next = removeManagedBlock(existing, sanitizedProjectName);\n\n if (next === existing) {\n return { changed: false, removed: false, hostsPath };\n }\n\n const tempPath = await writeSystemHostsFile(hostsPath, next, sanitizedProjectName);\n\n return { changed: true, removed: true, hostsPath, tempPath };\n}\n","import { stdin as input, stdout as output } from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\n\nexport function canPrompt() {\n return Boolean(input.isTTY && output.isTTY);\n}\n\nexport async function withPrompt<T>(run: (prompt: (question: string) => Promise<string>) => Promise<T>) {\n const rl = createInterface({ input, output });\n try {\n return await run((question) => rl.question(question));\n } finally {\n rl.close();\n }\n}\n\nexport async function confirm(question: string, defaultValue = true) {\n return withPrompt(async (prompt) => {\n const suffix = defaultValue ? \" [Y/n] \" : \" [y/N] \";\n const answer = (await prompt(`${question}${suffix}`)).trim().toLowerCase();\n if (!answer) return defaultValue;\n return answer === \"y\" || answer === \"yes\";\n });\n}\n\nexport async function ask(question: string, defaultValue?: string) {\n return withPrompt(async (prompt) => {\n const suffix = defaultValue ? ` (${defaultValue}) ` : \" \";\n const answer = (await prompt(`${question}${suffix}`)).trim();\n return answer || defaultValue || \"\";\n });\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readTextFile, writeTextFile } from \"./fs.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport const LOCALGHOST_STATE_FILE = \"ops/local/localghost-state.json\";\n\nexport type LocalghostStateAction = \"setup\" | \"teardown\";\n\nexport type LocalghostState = {\n version: 1;\n action: LocalghostStateAction;\n updatedAt: string;\n projectName: string;\n cwd: string;\n configPath?: string;\n hostsPath?: string;\n hostsChanged?: boolean;\n hostsTempPath?: string;\n caddyfilePath?: string;\n caddyfileRemoved?: boolean;\n caddyHttps?: boolean;\n entries?: DevHostEntry[];\n};\n\nexport type WriteLocalghostStateInput = Omit<LocalghostState, \"version\" | \"updatedAt\">;\n\nexport function getLocalghostStatePath(cwd = process.cwd()) {\n return join(cwd, LOCALGHOST_STATE_FILE);\n}\n\nexport function readLocalghostState(cwd = process.cwd()): LocalghostState | null {\n const path = getLocalghostStatePath(cwd);\n if (!existsSync(path)) return null;\n return JSON.parse(readTextFile(path)) as LocalghostState;\n}\n\nexport function writeLocalghostState(cwd: string, state: WriteLocalghostStateInput) {\n const path = getLocalghostStatePath(cwd);\n writeTextFile(path, `${JSON.stringify({ version: 1, updatedAt: new Date().toISOString(), ...state }, null, 2)}\\n`);\n return path;\n}\n","import { dirname, join } from \"node:path\";\nimport { execa } from \"execa\";\nimport { writeTextFile } from \"./fs.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type CaddyModeOptions = {\n https?: boolean;\n};\n\nfunction groupByPort(entries: DevHostEntry[]) {\n const groups = new Map<number, DevHostEntry[]>();\n\n for (const entry of entries) {\n const group = groups.get(entry.port) ?? [];\n group.push(entry);\n groups.set(entry.port, group);\n }\n\n return groups;\n}\n\nexport function getCaddyfilePath(cwd = process.cwd()) {\n return join(cwd, \"ops/local/Caddyfile\");\n}\n\nexport function renderCaddyfile(entries: DevHostEntry[], options: CaddyModeOptions = {}) {\n const groups = groupByPort(entries);\n const https = options.https === true;\n const blocks = [...groups.entries()]\n .sort(([leftPort], [rightPort]) => leftPort - rightPort)\n .map(([port, group]) => {\n const hosts = group\n .map((entry) => (https ? entry.host : `http://${entry.host}`))\n .sort()\n .join(\", \");\n\n return `${hosts} {\n reverse_proxy 127.0.0.1:${port}\n}`;\n });\n\n const globalOptions = https\n ? `{\n local_certs\n}\n\n`\n : \"\";\n\n return `${globalOptions}${blocks.join(\"\\n\\n\")}\n`;\n}\n\nexport async function writeCaddyfile(entries: DevHostEntry[], cwd = process.cwd(), options: CaddyModeOptions = {}) {\n const path = getCaddyfilePath(cwd);\n writeTextFile(path, renderCaddyfile(entries, options));\n return path;\n}\n\nexport async function validateCaddyfile(path: string) {\n await execa(\"caddy\", [\"validate\", \"--config\", path], {\n cwd: dirname(path),\n stdio: \"inherit\"\n });\n}\n\nexport async function runCaddy(path: string) {\n await execa(\"caddy\", [\"run\", \"--config\", path], {\n cwd: dirname(path),\n stdio: \"inherit\"\n });\n}\n\nexport function startCaddy(path: string) {\n return execa(\"caddy\", [\"run\", \"--config\", path], {\n cwd: dirname(path),\n stdio: \"inherit\"\n });\n}\n"],"mappings":"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","names":["existsSync","readFileSync","resolve","input","fileName","resolve","readFileSync","writeFileSync","join","execa","join","writeFileSync","execa","existsSync","join","join","existsSync","dirname","join","execa","join","execa","dirname","resolve","readFileSync","existsSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/vite.ts","../src/config.ts","../src/parse.ts","../src/context.ts","../src/port.ts","../src/doctor.ts","../src/env.ts","../src/fs.ts","../src/hosts-file.ts","../src/prompt.ts","../src/state.ts","../src/caddy.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { normalize, resolve } from \"node:path\";\nimport type { ConfigEnv, HmrOptions, Plugin, UserConfig, ViteDevServer, WsOptions } from \"vite\";\nimport {\n getConfigFileCandidates,\n getProjectName,\n resolveDevHostsPath,\n sanitizeProjectName,\n type ConfigPattern,\n type ReadDevHostsOptions\n} from \"./config.js\";\nimport { addDefaultWwwAliases, resolveLocalghostContext, type LocalghostContext } from \"./context.js\";\nimport { checkCaddy } from \"./doctor.js\";\nimport { isProductionLike } from \"./env.js\";\nimport { writeTextFile } from \"./fs.js\";\nimport { getSystemHostsPath, renderHostsBlock, updateSystemHosts } from \"./hosts-file.js\";\nimport { ask, canPrompt, confirm } from \"./prompt.js\";\nimport { getLocalghostStatePath, readLocalghostState, writeLocalghostState } from \"./state.js\";\nimport { getCaddyfilePath, renderCaddyfile, validateCaddyfile, writeCaddyfile } from \"./caddy.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type LocalGhostPluginOptions = {\n cwd?: string;\n fileName?: string;\n configFiles?: string[];\n configPattern?: ConfigPattern;\n port?: number;\n https?: boolean;\n bindHost?: string | boolean;\n dynamicPort?: boolean;\n primaryHost?: string;\n log?: boolean;\n setup?: boolean | \"prompt\";\n localghostConfig?: string | false;\n wwwAlias?: boolean;\n};\n\ntype ServerOptions = NonNullable<UserConfig[\"server\"]>;\n\nfunction mergeAllowedHosts(current: ServerOptions[\"allowedHosts\"], hosts: string[]) {\n if (Array.isArray(current)) {\n return [...new Set([...current, ...hosts])];\n }\n\n return hosts;\n}\n\nfunction getDisplayEntries(entries: DevHostEntry[], vitePort: number | undefined) {\n if (!vitePort) {\n return entries;\n }\n\n const matchingEntries = entries.filter((entry) => entry.port === vitePort);\n return matchingEntries.length > 0 ? matchingEntries : entries;\n}\n\nfunction printLocalHosts(server: ViteDevServer, entries: DevHostEntry[], vitePort: number | undefined, https: boolean) {\n const displayEntries = getDisplayEntries(entries, vitePort);\n const protocol = https ? \"https\" : \"http\";\n const urls = displayEntries.map((entry) => `${protocol}://${entry.host}/`);\n const primaryUrl = urls[0];\n\n if (!primaryUrl) {\n return;\n }\n\n const lines = [\n \"\",\n \" localghost\",\n ` local: ${primaryUrl}`,\n ...urls.slice(1).map((url) => ` also: ${url}`),\n vitePort ? ` target: http://127.0.0.1:${vitePort}/` : undefined,\n https ? \" proxy: Caddy local HTTPS\" : undefined\n ].filter((line): line is string => Boolean(line));\n\n server.config.logger.info(lines.join(\"\\n\"), {\n clear: false,\n timestamp: false\n });\n}\n\nfunction readOptionsFromPlugin(options: LocalGhostPluginOptions): ReadDevHostsOptions {\n return {\n cwd: options.cwd ?? process.cwd(),\n ...(options.fileName ? { fileName: options.fileName } : {}),\n ...(options.configFiles ? { configFiles: options.configFiles } : {}),\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n}\n\nfunction getConfigWatchFiles(options: LocalGhostPluginOptions) {\n const readOptions = readOptionsFromPlugin(options);\n const cwd = readOptions.cwd ?? process.cwd();\n const resolvedPath = resolveDevHostsPath(readOptions);\n const candidatePaths = getConfigFileCandidates(readOptions).map((fileName) => resolve(cwd, fileName));\n const projectConfigPaths = options.localghostConfig === false\n ? []\n : options.localghostConfig\n ? [resolve(cwd, options.localghostConfig)]\n : [\"localghost.config.mjs\", \"localghost.config.js\", \"localghost.config.cjs\"].map((fileName) => resolve(cwd, fileName));\n\n return [...new Set([...candidatePaths, resolvedPath.path, ...projectConfigPaths])];\n}\n\nfunction normalizeWatchPath(filePath: string) {\n return normalize(resolve(filePath));\n}\n\nfunction renderConfig(hosts: string[], port: number) {\n return [\n \"# Buh. Friendly names for local services.\",\n \"# Format: <host> <port>\",\n ...hosts.map((host) => `${host} ${port}`),\n \"\"\n ].join(\"\\n\");\n}\n\nfunction defaultHost(cwd: string) {\n const projectName = sanitizeProjectName(getProjectName(cwd).split(\"/\").pop() ?? \"app\");\n return `${projectName}.localhost`;\n}\n\nasync function promptForHosts(cwd: string, port: number) {\n const primaryHost = await ask(\"Primary local domain\", defaultHost(cwd));\n const hosts = [primaryHost.toLowerCase()];\n\n while (await confirm(\"Add another local domain?\", false)) {\n const host = await ask(\"Domain\");\n if (host) hosts.push(host.toLowerCase());\n }\n\n return [...new Set(addDefaultWwwAliases(hosts.map((host) => ({ host, port, target: `127.0.0.1:${port}` }))).map((entry) => entry.host))];\n}\n\nfunction hasReadySetup(cwd: string, entries: DevHostEntry[], configPath: string, https: boolean) {\n const state = readLocalghostState(cwd);\n const projectName = sanitizeProjectName(getProjectName(cwd));\n if (state?.action !== \"setup\" || state.configPath !== configPath) return false;\n\n try {\n const hosts = readFileSync(getSystemHostsPath(), \"utf8\");\n if (!hosts.includes(renderHostsBlock(projectName, entries).trimEnd())) return false;\n } catch {\n return false;\n }\n\n const caddyfilePath = getCaddyfilePath(cwd);\n return existsSync(caddyfilePath) && readFileSync(caddyfilePath, \"utf8\") === renderCaddyfile(entries, { https });\n}\n\nasync function setupProject(cwd: string, entries: DevHostEntry[], configPath: string, https: boolean) {\n const caddy = await checkCaddy();\n if (!caddy.found) {\n throw new Error([\n \"Caddy is missing.\",\n `Run: ${caddy.installHint}`,\n \"Localghost will not install it for you.\"\n ].join(\"\\n\"));\n }\n\n const projectName = sanitizeProjectName(getProjectName(cwd));\n console.log(\"Buh. macOS keeps local hostnames in /etc/hosts, so Localghost may ask for your password.\");\n console.log(\"It will only touch its managed Localghost block.\");\n const hostsResult = await updateSystemHosts(projectName, entries);\n const caddyfilePath = await writeCaddyfile(entries, cwd, { https });\n await validateCaddyfile(caddyfilePath);\n writeLocalghostState(cwd, {\n action: \"setup\",\n projectName,\n cwd,\n configPath,\n hostsPath: hostsResult.hostsPath,\n hostsChanged: hostsResult.changed,\n ...(hostsResult.tempPath ? { hostsTempPath: hostsResult.tempPath } : {}),\n caddyfilePath,\n caddyHttps: https,\n entries\n });\n}\n\nasync function ensureLocalghostContext(options: LocalGhostPluginOptions, vitePort: number, https: boolean | undefined) {\n const cwd = options.cwd ?? process.cwd();\n const readOptions = readOptionsFromPlugin(options);\n const resolved = resolveDevHostsPath(readOptions);\n\n if (!resolved.exists) {\n if (options.setup === false || !canPrompt()) {\n throw new Error(\n `No .localghost found at ${resolved.path}. Run \\`localghost init --write-scripts\\` or start Vite in an interactive terminal.`\n );\n }\n\n console.log(`No .localghost found at ${resolved.path}.`);\n if (!(await confirm(\"Create one now?\", true))) {\n throw new Error(\"Localghost setup skipped. Create .localghost before running the Vite plugin.\");\n }\n\n const hosts = await promptForHosts(cwd, vitePort);\n writeTextFile(resolved.path, renderConfig(hosts, vitePort));\n console.log(`Created ${resolved.path}`);\n }\n\n const context = await resolveLocalghostContext({\n ...options,\n cwd,\n port: vitePort,\n ...(typeof https === \"boolean\" ? { https } : {})\n });\n\n if (!hasReadySetup(cwd, context.entries, resolved.path, context.https)) {\n if (options.setup === false || !canPrompt()) return context;\n\n const setup = await confirm(\"Run caddy:setup now?\", true);\n if (setup) {\n await setupProject(cwd, context.entries, resolved.path, context.https);\n console.log(`All set. Setup state: ${getLocalghostStatePath(cwd)}`);\n }\n }\n\n return context;\n}\n\nexport function localGhostPlugin(options: LocalGhostPluginOptions = {}): Plugin {\n let resolvedEntries: DevHostEntry[] = [];\n let resolvedVitePort: number | undefined;\n let resolvedHttps = false;\n let restartTimer: NodeJS.Timeout | undefined;\n\n return {\n name: \"localghost:vite\",\n enforce: \"pre\",\n\n async config(userConfig, configEnv: ConfigEnv): Promise<UserConfig> {\n if (configEnv.command !== \"serve\" || configEnv.mode === \"production\" || isProductionLike()) {\n return {};\n }\n\n const existingServer = userConfig.server ?? {};\n const envVitePort = Number.parseInt(process.env.LOCALGHOST_PORT ?? process.env.VITE_PORT ?? \"\", 10);\n const requestedVitePort =\n options.port ??\n existingServer.port ??\n (Number.isInteger(envVitePort) ? envVitePort : 5173);\n const context: LocalghostContext = await ensureLocalghostContext(options, requestedVitePort, options.https);\n const entries = context.entries;\n const hosts = context.hosts;\n const primaryHost = context.primaryHost;\n\n resolvedEntries = entries;\n resolvedVitePort = context.port;\n resolvedHttps = context.https;\n\n const server: ServerOptions = {\n ...existingServer,\n allowedHosts: mergeAllowedHosts(existingServer.allowedHosts, hosts),\n strictPort: existingServer.strictPort ?? true\n };\n\n if (typeof existingServer.host === \"undefined\") {\n server.host = context.bindHost;\n }\n\n if (context.port) {\n server.port = context.port;\n }\n\n if (context.https && primaryHost) {\n const existingWs = typeof server.ws === \"object\" && server.ws ? server.ws : {};\n const existingHmr = typeof existingServer.hmr === \"object\" && existingServer.hmr ? existingServer.hmr : {};\n\n server.ws = {\n ...existingWs,\n protocol: \"wss\",\n host: primaryHost,\n clientPort: 443\n } satisfies WsOptions;\n\n server.hmr = {\n ...existingHmr,\n protocol: \"wss\",\n host: primaryHost,\n clientPort: 443\n } satisfies HmrOptions;\n }\n\n return { server };\n },\n\n configureServer(server) {\n const watchFiles = getConfigWatchFiles(options);\n const watchedConfigFiles = new Set(watchFiles.map(normalizeWatchPath));\n\n server.watcher.add(watchFiles);\n\n const restartOnLocalghostConfigChange = (filePath: string) => {\n if (!watchedConfigFiles.has(normalizeWatchPath(filePath))) {\n return;\n }\n\n if (restartTimer) {\n clearTimeout(restartTimer);\n }\n\n restartTimer = setTimeout(() => {\n if (options.log !== false) {\n server.config.logger.info(\"localghost config changed; restarting Vite dev server\", {\n clear: false,\n timestamp: false\n });\n }\n\n void server.restart().catch((error: unknown) => {\n server.config.logger.error(error instanceof Error ? error.message : String(error), {\n timestamp: false\n });\n });\n }, 50);\n };\n\n server.watcher.on(\"add\", restartOnLocalghostConfigChange);\n server.watcher.on(\"change\", restartOnLocalghostConfigChange);\n server.watcher.on(\"unlink\", restartOnLocalghostConfigChange);\n\n if (options.log !== false) {\n server.printUrls = () => {\n printLocalHosts(server, resolvedEntries, resolvedVitePort, resolvedHttps);\n };\n }\n }\n };\n}\n\nexport const localHostsPlugin = localGhostPlugin;\nexport type LocalHostsPluginOptions = LocalGhostPluginOptions;\n","import { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { basename, join, resolve } from \"node:path\";\nimport { parseDevHosts } from \"./parse.js\";\n\nexport const LOCALGHOST_CONFIG_FILE = \".localghost\";\n\nexport type ConfigPattern = string | RegExp;\n\nexport type ReadDevHostsOptions = {\n cwd?: string;\n fileName?: string;\n configFiles?: string[];\n configPattern?: ConfigPattern;\n};\n\nexport type ResolvedDevHostsPath = {\n path: string;\n fileName: string;\n exists: boolean;\n searchedFiles: string[];\n configPattern?: ConfigPattern;\n};\n\nfunction unique(values: string[]) {\n return [...new Set(values.filter(Boolean))];\n}\n\nfunction toRegExp(pattern: ConfigPattern) {\n return typeof pattern === \"string\" ? new RegExp(pattern) : pattern;\n}\n\nfunction findPatternMatches(cwd: string, pattern: ConfigPattern) {\n const matcher = toRegExp(pattern);\n\n return readdirSync(cwd, { withFileTypes: true })\n .filter((entry) => entry.isFile())\n .map((entry) => entry.name)\n .filter((name) => {\n matcher.lastIndex = 0;\n return matcher.test(name);\n })\n .sort();\n}\n\nexport function getConfigFileCandidates(options: ReadDevHostsOptions = {}) {\n const cwd = options.cwd ?? process.cwd();\n const exactFiles = unique([\n ...(options.fileName ? [options.fileName] : []),\n ...(options.configFiles ?? [])\n ]);\n const patternFiles = options.configPattern ? findPatternMatches(cwd, options.configPattern) : [];\n const candidates = unique([...exactFiles, ...patternFiles]);\n\n if (candidates.length > 0) return candidates;\n if (exactFiles.length > 0 || options.configPattern) return [];\n return [LOCALGHOST_CONFIG_FILE];\n}\n\nexport function resolveDevHostsPath(options: ReadDevHostsOptions = {}): ResolvedDevHostsPath {\n const cwd = options.cwd ?? process.cwd();\n const searchedFiles = getConfigFileCandidates(options);\n\n for (const fileName of searchedFiles) {\n const path = resolve(cwd, fileName);\n if (existsSync(path)) {\n return {\n path,\n fileName: basename(fileName),\n exists: true,\n searchedFiles,\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n }\n }\n\n const fileName = searchedFiles[0] ?? LOCALGHOST_CONFIG_FILE;\n\n return {\n path: resolve(cwd, fileName),\n fileName: basename(fileName),\n exists: false,\n searchedFiles,\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n}\n\nexport function getDevHostsPath(options: ReadDevHostsOptions = {}) {\n return resolveDevHostsPath(options).path;\n}\n\nfunction formatSearchedFiles(files: string[], pattern?: ConfigPattern) {\n if (files.length > 0) return files.map((file) => `\\`${file}\\``).join(\", \");\n if (pattern) return `files matching ${pattern.toString()}`;\n return `\\`${LOCALGHOST_CONFIG_FILE}\\``;\n}\n\nexport function readDevHosts(options: ReadDevHostsOptions | string = {}) {\n const resolvedOptions = typeof options === \"string\" ? { cwd: options } : options;\n const resolvedPath = resolveDevHostsPath(resolvedOptions);\n\n if (!resolvedPath.exists) {\n const cwd = resolvedOptions.cwd ?? process.cwd();\n throw new Error(\n `Missing Localghost config in ${cwd}. Looked for ${formatSearchedFiles(resolvedPath.searchedFiles, resolvedPath.configPattern)}. Run \\`localghost init\\` or pass --config/--config-pattern.`\n );\n }\n\n return parseDevHosts(readFileSync(resolvedPath.path, \"utf8\"), resolvedPath.fileName);\n}\n\nexport function getProjectName(cwd = process.cwd()) {\n try {\n const pkg = JSON.parse(readFileSync(join(cwd, \"package.json\"), \"utf8\")) as { name?: unknown };\n const name = typeof pkg.name === \"string\" && pkg.name ? pkg.name : \"app\";\n return sanitizeProjectName(name.replace(/^@/, \"\"));\n } catch {\n return \"app\";\n }\n}\n\nexport function sanitizeProjectName(value: string) {\n const projectName = value.replace(/[^\\w.-]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n return projectName || \"app\";\n}\n","export type DevHostEntry = {\n host: string;\n port: number;\n target: string;\n};\n\nconst HOST_PATTERN = /^(?=.{1,253}$)(?!-)[a-z0-9-]+(\\.[a-z0-9-]+)*\\.?$/i;\n\nexport function parseDevHosts(input: string, fileName = \".localghost\"): DevHostEntry[] {\n const entries: DevHostEntry[] = [];\n\n input.split(/\\r?\\n/).forEach((rawLine, index) => {\n const line = rawLine.replace(/#.*/, \"\").trim();\n\n if (!line) {\n return;\n }\n\n const parts = line.split(/\\s+/);\n const host = parts[0];\n const portRaw = parts[1];\n\n if (!host || !portRaw || parts.length > 2) {\n throw new Error(`Invalid ${fileName} line ${index + 1}: \"${rawLine}\"`);\n }\n\n if (!HOST_PATTERN.test(host)) {\n throw new Error(`Invalid host on line ${index + 1}: \"${host}\"`);\n }\n\n const port = Number(portRaw);\n\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(`Invalid port on line ${index + 1}: \"${portRaw}\"`);\n }\n\n entries.push({\n host: host.toLowerCase().replace(/\\.$/, \"\"),\n port,\n target: `127.0.0.1:${port}`\n });\n });\n\n return entries;\n}\n\nexport function findLocalMdnsHosts(entries: DevHostEntry[]): string[] {\n return [...new Set(entries.map((entry) => entry.host).filter((host) => host.endsWith(\".local\")))];\n}\n","import { existsSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport {\n getProjectName,\n readDevHosts,\n resolveDevHostsPath,\n sanitizeProjectName,\n type ConfigPattern,\n type ReadDevHostsOptions\n} from \"./config.js\";\nimport { findAvailablePort } from \"./port.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type LocalghostContextOptions = {\n cwd?: string;\n project?: string;\n localghostConfig?: string | false;\n fileName?: string;\n configFiles?: string[];\n configPattern?: ConfigPattern;\n port?: number;\n https?: boolean;\n bindHost?: string | boolean;\n primaryHost?: string;\n dynamicPort?: boolean;\n wwwAlias?: boolean;\n};\n\nexport type LocalghostContext = {\n cwd: string;\n projectName: string;\n readOptions: ReadDevHostsOptions;\n configPath: string;\n configFileName: string;\n configEntries: DevHostEntry[];\n entries: DevHostEntry[];\n hosts: string[];\n requestedPort: number;\n port: number;\n dynamicPort: boolean;\n bindHost: string | boolean;\n primaryHost: string;\n https: boolean;\n wwwAlias: boolean;\n projectConfigPath?: string;\n};\n\nexport type LocalghostProjectConfig = Omit<LocalghostContextOptions, \"cwd\" | \"localghostConfig\">;\n\nconst LOCALGHOST_PROJECT_CONFIG_FILES = [\n \"localghost.config.mjs\",\n \"localghost.config.js\",\n \"localghost.config.cjs\"\n];\n\nfunction parsePort(value: string | undefined) {\n if (!value) return undefined;\n const port = Number.parseInt(value, 10);\n return Number.isInteger(port) && port > 0 && port <= 65535 ? port : undefined;\n}\n\nfunction envPort() {\n return parsePort(process.env.LOCALGHOST_PORT) ?? parsePort(process.env.VITE_PORT);\n}\n\nfunction envDynamicPort() {\n const value = process.env.LOCALGHOST_DYNAMIC_PORT;\n if (!value) return undefined;\n return [\"1\", \"true\", \"yes\", \"on\"].includes(value.toLowerCase());\n}\n\nfunction envHttps() {\n const value = process.env.LOCALGHOST_HTTPS;\n if (!value) return undefined;\n return [\"1\", \"true\", \"yes\", \"on\"].includes(value.toLowerCase());\n}\n\nfunction readOptionsFromContext(options: LocalghostContextOptions): ReadDevHostsOptions {\n return {\n cwd: options.cwd ?? process.cwd(),\n ...(options.fileName ? { fileName: options.fileName } : {}),\n ...(options.configFiles ? { configFiles: options.configFiles } : {}),\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n}\n\nfunction withRuntimePort(entries: DevHostEntry[], requestedPort: number, port: number) {\n if (requestedPort === port) return entries;\n\n const hasRequestedPort = entries.some((entry) => entry.port === requestedPort);\n if (!hasRequestedPort) return entries;\n\n return entries.map((entry) => (entry.port === requestedPort ? { ...entry, port } : entry));\n}\n\nfunction uniqueHosts(entries: DevHostEntry[]) {\n return [...new Set(entries.map((entry) => entry.host))];\n}\n\nfunction isAliasableHost(host: string) {\n return host.includes(\".\") && !host.startsWith(\"www.\") && !host.includes(\":\");\n}\n\nexport function getDefaultWwwAlias(host: string) {\n return isAliasableHost(host) ? `www.${host}` : null;\n}\n\nexport function addDefaultWwwAliases(entries: DevHostEntry[]) {\n const seen = new Set(entries.map((entry) => entry.host));\n const aliases: DevHostEntry[] = [];\n\n for (const entry of entries) {\n const alias = getDefaultWwwAlias(entry.host);\n if (alias && !seen.has(alias)) {\n aliases.push({ host: alias, port: entry.port, target: `127.0.0.1:${entry.port}` });\n seen.add(alias);\n }\n }\n\n return [...entries, ...aliases];\n}\n\nfunction defined<T extends Record<string, unknown>>(input: T) {\n return Object.fromEntries(Object.entries(input).filter(([, value]) => typeof value !== \"undefined\")) as Partial<T>;\n}\n\nasync function readProjectConfig(cwd: string, configFile: string | false | undefined) {\n if (configFile === false) return {};\n\n const candidates = configFile ? [configFile] : LOCALGHOST_PROJECT_CONFIG_FILES;\n const path = candidates.map((candidate) => resolveDevHostsPath({ cwd, fileName: candidate }).path).find((candidate) => existsSync(candidate));\n if (!path) return {};\n\n const imported = await import(`${pathToFileURL(path).href}?localghost=${Date.now()}`);\n const config = (imported.default ?? imported) as LocalghostProjectConfig;\n\n return { config, path };\n}\n\nexport function defineLocalghostConfig<T extends LocalghostContextOptions>(config: T) {\n return config;\n}\n\nexport async function resolveLocalghostContext(options: LocalghostContextOptions = {}): Promise<LocalghostContext> {\n const cwd = options.cwd ?? process.cwd();\n const projectConfig = await readProjectConfig(cwd, options.localghostConfig);\n const merged = {\n ...projectConfig.config,\n ...defined(options)\n } as LocalghostContextOptions;\n const readOptions = readOptionsFromContext({ ...merged, cwd });\n const resolvedPath = resolveDevHostsPath(readOptions);\n const configEntries = readDevHosts(readOptions);\n const requestedPort = merged.port ?? envPort() ?? configEntries[0]?.port ?? 5173;\n const dynamicPort = merged.dynamicPort ?? envDynamicPort() ?? false;\n const bindHost = merged.bindHost ?? \"127.0.0.1\";\n const probeHost = typeof bindHost === \"string\" ? bindHost : \"127.0.0.1\";\n const port = dynamicPort ? await findAvailablePort(requestedPort, { host: probeHost }) : requestedPort;\n const wwwAlias = merged.wwwAlias ?? true;\n const entries = wwwAlias\n ? addDefaultWwwAliases(withRuntimePort(configEntries, requestedPort, port))\n : withRuntimePort(configEntries, requestedPort, port);\n const hosts = uniqueHosts(entries);\n const primaryHost =\n merged.primaryHost ??\n entries.find((entry) => entry.port === port)?.host ??\n hosts[0] ??\n `${sanitizeProjectName(getProjectName(cwd))}.localhost`;\n\n return {\n cwd,\n projectName: sanitizeProjectName(merged.project ?? getProjectName(cwd)),\n readOptions,\n configPath: resolvedPath.path,\n configFileName: resolvedPath.fileName,\n configEntries,\n entries,\n hosts,\n requestedPort,\n port,\n dynamicPort,\n bindHost,\n primaryHost,\n https: merged.https ?? envHttps() ?? false,\n wwwAlias,\n ...(projectConfig.path ? { projectConfigPath: projectConfig.path } : {})\n };\n}\n","import { createServer } from \"node:net\";\n\nexport type FindAvailablePortOptions = {\n host?: string;\n maxAttempts?: number;\n};\n\nexport async function isPortAvailable(port: number, host = \"127.0.0.1\") {\n return new Promise<boolean>((resolve) => {\n const server = createServer();\n\n server.once(\"error\", () => {\n resolve(false);\n });\n\n server.once(\"listening\", () => {\n server.close(() => resolve(true));\n });\n\n server.listen(port, host);\n });\n}\n\nexport async function findAvailablePort(startPort: number, options: FindAvailablePortOptions = {}) {\n const host = options.host ?? \"127.0.0.1\";\n const maxAttempts = options.maxAttempts ?? 50;\n\n for (let offset = 0; offset < maxAttempts; offset += 1) {\n const port = startPort + offset;\n if (await isPortAvailable(port, host)) {\n return port;\n }\n }\n\n throw new Error(`No available port found from ${startPort} to ${startPort + maxAttempts - 1}.`);\n}\n","import { execa } from \"execa\";\n\nexport type DoctorResult = {\n ok: boolean;\n caddy: {\n found: boolean;\n version?: string;\n installHint: string;\n };\n};\n\nexport async function checkCaddy(): Promise<DoctorResult[\"caddy\"]> {\n try {\n const result = await execa(\"caddy\", [\"version\"], { reject: false });\n const version = [result.stdout, result.stderr].filter(Boolean).join(\"\\n\").trim();\n\n return {\n found: result.exitCode === 0,\n ...(version ? { version } : {}),\n installHint: \"brew install caddy\"\n };\n } catch {\n return {\n found: false,\n installHint: \"brew install caddy\"\n };\n }\n}\n\nexport async function runDoctor(): Promise<DoctorResult> {\n const caddy = await checkCaddy();\n return {\n ok: caddy.found,\n caddy\n };\n}\n","export type LocalghostEnvironment = NodeJS.ProcessEnv;\n\nconst PRODUCTION_ENV_KEYS = [\"NODE_ENV\", \"VERCEL_ENV\", \"NETLIFY\", \"CF_PAGES_BRANCH\", \"LOCALGHOST_ENV\"] as const;\n\nexport function getProductionReason(env: LocalghostEnvironment = process.env) {\n if (env.LOCALGHOST_ENV === \"production\") return \"LOCALGHOST_ENV=production\";\n if (env.NODE_ENV === \"production\") return \"NODE_ENV=production\";\n if (env.VERCEL_ENV === \"production\") return \"VERCEL_ENV=production\";\n if (env.NETLIFY === \"true\" && env.CONTEXT === \"production\") return \"NETLIFY=true and CONTEXT=production\";\n if (env.CF_PAGES_BRANCH && env.CF_PAGES_BRANCH === env.CF_PAGES_PRODUCTION_BRANCH) {\n return \"CF_PAGES_BRANCH matches CF_PAGES_PRODUCTION_BRANCH\";\n }\n\n return null;\n}\n\nexport function isProductionLike(env: LocalghostEnvironment = process.env) {\n return getProductionReason(env) !== null;\n}\n\nexport function assertLocalDevelopment(command: string, env: LocalghostEnvironment = process.env) {\n const reason = getProductionReason(env);\n if (!reason) return;\n\n throw new Error(`Localghost only runs in local development. Refusing \\`${command}\\` because ${reason}.`);\n}\n\nexport function getProductionEnvKeys() {\n return PRODUCTION_ENV_KEYS;\n}\n","import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport function readTextFile(path: string) {\n return readFileSync(path, \"utf8\");\n}\n\nexport function writeTextFile(path: string, value: string) {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, value, \"utf8\");\n return path;\n}\n","import { writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { execa } from \"execa\";\nimport { sanitizeProjectName } from \"./config.js\";\nimport { readTextFile } from \"./fs.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type UpdateSystemHostsResult = {\n changed: boolean;\n hostsPath: string;\n tempPath?: string;\n};\n\nexport type RemoveSystemHostsResult = UpdateSystemHostsResult & {\n removed: boolean;\n};\n\nfunction escapeRegExp(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction getManagedBlockPattern(projectName: string) {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const start = `# localghost:start ${sanitizedProjectName}`;\n const end = `# localghost:end ${sanitizedProjectName}`;\n return new RegExp(`${escapeRegExp(start)}[\\\\s\\\\S]*?${escapeRegExp(end)}\\\\n?`, \"m\");\n}\n\nexport function getSystemHostsPath() {\n return process.platform === \"win32\" ? \"C:\\\\Windows\\\\System32\\\\drivers\\\\etc\\\\hosts\" : \"/etc/hosts\";\n}\n\nexport function renderHostsBlock(projectName: string, entries: DevHostEntry[]) {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const hosts = [...new Set(entries.map((entry) => entry.host))].sort();\n\n return [\n `# localghost:start ${sanitizedProjectName}`,\n ...hosts.map((host) => `127.0.0.1 ${host}`),\n `# localghost:end ${sanitizedProjectName}`,\n \"\"\n ].join(\"\\n\");\n}\n\nexport function upsertManagedBlock(existing: string, projectName: string, block: string) {\n const pattern = getManagedBlockPattern(projectName);\n\n if (pattern.test(existing)) {\n return existing.replace(pattern, block);\n }\n\n return `${existing.trimEnd()}\\n\\n${block}`;\n}\n\nexport function removeManagedBlock(existing: string, projectName: string) {\n const pattern = getManagedBlockPattern(projectName);\n\n if (!pattern.test(existing)) {\n return existing;\n }\n\n return existing.replace(pattern, \"\").replace(/\\n{3,}/g, \"\\n\\n\").trimEnd() + \"\\n\";\n}\n\nasync function writeSystemHostsFile(hostsPath: string, next: string, projectName: string) {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const tempPath = join(tmpdir(), `localghost-${sanitizedProjectName}-hosts`);\n writeFileSync(tempPath, next, \"utf8\");\n\n if (process.platform === \"win32\") {\n throw new Error(`Windows support: run as administrator and copy ${tempPath} to ${hostsPath}.`);\n }\n\n await execa(\"sudo\", [\"cp\", tempPath, hostsPath], { stdio: \"inherit\" });\n\n return tempPath;\n}\n\nexport async function updateSystemHosts(projectName: string, entries: DevHostEntry[]): Promise<UpdateSystemHostsResult> {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const hostsPath = getSystemHostsPath();\n const existing = readTextFile(hostsPath);\n const block = renderHostsBlock(sanitizedProjectName, entries);\n const next = upsertManagedBlock(existing, sanitizedProjectName, block);\n\n if (next === existing) {\n return { changed: false, hostsPath };\n }\n\n const tempPath = await writeSystemHostsFile(hostsPath, next, sanitizedProjectName);\n\n return { changed: true, hostsPath, tempPath };\n}\n\nexport async function removeSystemHosts(projectName: string): Promise<RemoveSystemHostsResult> {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const hostsPath = getSystemHostsPath();\n const existing = readTextFile(hostsPath);\n const next = removeManagedBlock(existing, sanitizedProjectName);\n\n if (next === existing) {\n return { changed: false, removed: false, hostsPath };\n }\n\n const tempPath = await writeSystemHostsFile(hostsPath, next, sanitizedProjectName);\n\n return { changed: true, removed: true, hostsPath, tempPath };\n}\n","import { stdin as input, stdout as output } from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\n\nexport function canPrompt() {\n return Boolean(input.isTTY && output.isTTY);\n}\n\nexport async function withPrompt<T>(run: (prompt: (question: string) => Promise<string>) => Promise<T>) {\n const rl = createInterface({ input, output });\n try {\n return await run((question) => rl.question(question));\n } finally {\n rl.close();\n }\n}\n\nexport async function confirm(question: string, defaultValue = true) {\n return withPrompt(async (prompt) => {\n const suffix = defaultValue ? \" [Y/n] \" : \" [y/N] \";\n const answer = (await prompt(`${question}${suffix}`)).trim().toLowerCase();\n if (!answer) return defaultValue;\n return answer === \"y\" || answer === \"yes\";\n });\n}\n\nexport async function ask(question: string, defaultValue?: string) {\n return withPrompt(async (prompt) => {\n const suffix = defaultValue ? ` (${defaultValue}) ` : \" \";\n const answer = (await prompt(`${question}${suffix}`)).trim();\n return answer || defaultValue || \"\";\n });\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readTextFile, writeTextFile } from \"./fs.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport const LOCALGHOST_STATE_FILE = \"ops/local/localghost-state.json\";\n\nexport type LocalghostStateAction = \"setup\" | \"teardown\";\n\nexport type LocalghostState = {\n version: 1;\n action: LocalghostStateAction;\n updatedAt: string;\n projectName: string;\n cwd: string;\n configPath?: string;\n hostsPath?: string;\n hostsChanged?: boolean;\n hostsTempPath?: string;\n caddyfilePath?: string;\n caddyfileRemoved?: boolean;\n caddyHttps?: boolean;\n caddyTrustedAt?: string;\n caddyTrustPromptedAt?: string;\n entries?: DevHostEntry[];\n};\n\nexport type WriteLocalghostStateInput = Omit<LocalghostState, \"version\" | \"updatedAt\">;\n\nexport function getLocalghostStatePath(cwd = process.cwd()) {\n return join(cwd, LOCALGHOST_STATE_FILE);\n}\n\nexport function readLocalghostState(cwd = process.cwd()): LocalghostState | null {\n const path = getLocalghostStatePath(cwd);\n if (!existsSync(path)) return null;\n return JSON.parse(readTextFile(path)) as LocalghostState;\n}\n\nexport function writeLocalghostState(cwd: string, state: WriteLocalghostStateInput) {\n const path = getLocalghostStatePath(cwd);\n writeTextFile(path, `${JSON.stringify({ ...state, version: 1, updatedAt: new Date().toISOString() }, null, 2)}\\n`);\n return path;\n}\n\nexport function patchLocalghostState(cwd: string, patch: Partial<WriteLocalghostStateInput>) {\n const current = readLocalghostState(cwd);\n if (!current) return null;\n return writeLocalghostState(cwd, { ...(current as unknown as WriteLocalghostStateInput), ...patch });\n}\n","import { dirname, join } from \"node:path\";\nimport { execa } from \"execa\";\nimport { writeTextFile } from \"./fs.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type CaddyModeOptions = {\n https?: boolean;\n};\n\nfunction groupByPort(entries: DevHostEntry[]) {\n const groups = new Map<number, DevHostEntry[]>();\n\n for (const entry of entries) {\n const group = groups.get(entry.port) ?? [];\n group.push(entry);\n groups.set(entry.port, group);\n }\n\n return groups;\n}\n\nexport function getCaddyfilePath(cwd = process.cwd()) {\n return join(cwd, \"ops/local/Caddyfile\");\n}\n\nexport function renderCaddyfile(entries: DevHostEntry[], options: CaddyModeOptions = {}) {\n const groups = groupByPort(entries);\n const https = options.https === true;\n const blocks = [...groups.entries()]\n .sort(([leftPort], [rightPort]) => leftPort - rightPort)\n .map(([port, group]) => {\n const hosts = group\n .map((entry) => (https ? entry.host : `http://${entry.host}`))\n .sort()\n .join(\", \");\n\n return `${hosts} {\n reverse_proxy 127.0.0.1:${port}\n}`;\n });\n\n const globalOptions = https\n ? `{\n local_certs\n}\n\n`\n : \"\";\n\n return `${globalOptions}${blocks.join(\"\\n\\n\")}\n`;\n}\n\nexport async function writeCaddyfile(entries: DevHostEntry[], cwd = process.cwd(), options: CaddyModeOptions = {}) {\n const path = getCaddyfilePath(cwd);\n writeTextFile(path, renderCaddyfile(entries, options));\n return path;\n}\n\nexport async function validateCaddyfile(path: string) {\n await execa(\"caddy\", [\"validate\", \"--config\", path], {\n cwd: dirname(path),\n stdio: \"inherit\"\n });\n}\n\nexport async function runCaddy(path: string) {\n await execa(\"caddy\", [\"run\", \"--config\", path], {\n cwd: dirname(path),\n stdio: \"inherit\"\n });\n}\n\nexport function startCaddy(path: string) {\n return execa(\"caddy\", [\"run\", \"--config\", path], {\n cwd: dirname(path),\n stdio: \"inherit\"\n });\n}\n\nexport async function trustCaddy(path: string) {\n await execa(\"caddy\", [\"trust\", \"--config\", path], {\n cwd: dirname(path),\n stdio: \"inherit\"\n });\n}\n"],"mappings":"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","names":["existsSync","readFileSync","resolve","input","fileName","existsSync","resolve","input","existsSync","readFileSync","writeFileSync","join","execa","join","writeFileSync","execa","existsSync","join","join","existsSync","dirname","join","execa","join","execa","dirname","resolve","readFileSync","existsSync"]}
|
package/docs/localghost.1.md
CHANGED
|
@@ -10,24 +10,27 @@ localghost - friendly local hostnames for app repos
|
|
|
10
10
|
localghost init [--write-scripts] [--config file] [--host host] [--port port]
|
|
11
11
|
localghost doctor
|
|
12
12
|
localghost setup [--project name] [--config file] [--config-pattern regex] [--https|--ssl]
|
|
13
|
+
localghost trust [--project name] [--config file] [--config-pattern regex] [--https|--ssl]
|
|
13
14
|
localghost reset [--project name]
|
|
14
15
|
localghost teardown [--project name] [--remove-caddyfile]
|
|
15
16
|
localghost status [--ready] [--json]
|
|
16
17
|
localghost ps [--json]
|
|
17
18
|
localghost update [--json]
|
|
18
|
-
localghost dev [--config file] [--config-pattern regex] [--https|--ssl] [--setup]
|
|
19
|
-
localghost run [--config file] [--config-pattern regex] [--https|--ssl] [--setup] [--dynamic-port] -- command
|
|
19
|
+
localghost dev [--config file] [--config-pattern regex] [--https|--ssl] [--setup] [--trust]
|
|
20
|
+
localghost run [--config file] [--config-pattern regex] [--https|--ssl] [--setup] [--trust] [--dynamic-port] -- command
|
|
20
21
|
localghost print [--config file] [--config-pattern regex]
|
|
21
22
|
```
|
|
22
23
|
|
|
23
24
|
## Description
|
|
24
25
|
|
|
25
|
-
Localghost reads `.localghost`, writes a managed `/etc/hosts` block, records `ops/local/localghost-state.json`, generates `ops/local/Caddyfile`, and runs a Caddy local proxy. HTTP is the default; local HTTPS is explicit with `--https
|
|
26
|
+
Localghost reads `.localghost`, optionally reads `localghost.config.mjs`, writes a managed `/etc/hosts` block, records `ops/local/localghost-state.json`, generates `ops/local/Caddyfile`, and runs a Caddy local proxy. HTTP is the default; local HTTPS is explicit with `--https`, `--ssl`, or `https: true` in `localghost.config.mjs`. It is intentionally small and explicit: no hidden installs, no full hosts-file rewrites, no surprise browser tabs, and no broad Vite `allowedHosts: true` shortcut.
|
|
26
27
|
|
|
27
28
|
Localghost checks npm for newer releases after successful commands. The check is best-effort, cached for 24 hours, and can be disabled with `LOCALGHOST_NO_UPDATE_CHECK=1` or `--no-update-check`.
|
|
28
29
|
|
|
29
30
|
`setup`, `dev`, and `teardown` refuse to run in production-like environments such as `NODE_ENV=production`, `VERCEL_ENV=production`, or `LOCALGHOST_ENV=production`.
|
|
30
31
|
|
|
32
|
+
When HTTPS is enabled, `dev` and `run` can trust Caddy's local HTTPS CA before the child app starts. Localghost asks once in interactive terminals, records the answer in `ops/local/localghost-state.json`, and supports `--trust` or `localghost trust` when you want to rerun the trust step intentionally.
|
|
33
|
+
|
|
31
34
|
## Commands
|
|
32
35
|
|
|
33
36
|
### init
|
|
@@ -67,6 +70,14 @@ Updates the managed Localghost block in `/etc/hosts`, writes `ops/local/Caddyfil
|
|
|
67
70
|
localghost setup --project app
|
|
68
71
|
```
|
|
69
72
|
|
|
73
|
+
### trust
|
|
74
|
+
|
|
75
|
+
Validates the HTTPS Caddyfile and runs `caddy trust --config <Caddyfile>` so browsers can trust Caddy's local development certificates. macOS may ask for your password to add Caddy's local CA to Keychain.
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
localghost trust
|
|
79
|
+
```
|
|
80
|
+
|
|
70
81
|
### teardown
|
|
71
82
|
|
|
72
83
|
Removes the managed Localghost block from `/etc/hosts` for the selected project and records the action in `ops/local/localghost-state.json`. It leaves `ops/local/Caddyfile` in place unless `--remove-caddyfile` is passed.
|
|
@@ -119,7 +130,7 @@ localghost routes
|
|
|
119
130
|
|
|
120
131
|
### dev
|
|
121
132
|
|
|
122
|
-
Requires setup to be ready, writes `ops/local/Caddyfile`, validates it, and runs Caddy. Supports `--config` and `--config-pattern`. HTTP is the default. Pass `--https` or `--ssl` to run a local HTTPS proxy. Pass `--setup` to explicitly allow `dev` to run setup first when setup is missing or stale.
|
|
133
|
+
Requires setup to be ready, writes `ops/local/Caddyfile`, validates it, and runs Caddy. Supports `--config` and `--config-pattern`. HTTP is the default. Pass `--https` or `--ssl` to run a local HTTPS proxy. Pass `--setup` to explicitly allow `dev` to run setup first when setup is missing or stale. Pass `--trust` to force the Caddy trust step before the proxy stays running.
|
|
123
134
|
|
|
124
135
|
```sh
|
|
125
136
|
localghost dev
|
|
@@ -127,15 +138,18 @@ localghost dev
|
|
|
127
138
|
|
|
128
139
|
### run
|
|
129
140
|
|
|
130
|
-
Resolves one Localghost context, ensures setup is ready, writes the runtime Caddyfile, starts Caddy, and runs a child dev command. The selected port is passed to the child as `LOCALGHOST_PORT` and `VITE_PORT`.
|
|
141
|
+
Resolves one Localghost context, ensures setup is ready, writes the runtime Caddyfile, starts Caddy, handles the optional HTTPS trust prompt, and then runs a child dev command. The selected port is passed to the child as `LOCALGHOST_PORT` and `VITE_PORT`.
|
|
131
142
|
|
|
132
143
|
```sh
|
|
133
144
|
localghost run -- vite
|
|
145
|
+
localghost run --trust -- vite
|
|
134
146
|
localghost run --dynamic-port -- turbo dev
|
|
135
147
|
```
|
|
136
148
|
|
|
137
149
|
Pass `--dynamic-port` or `--dynamic-port=yes` to start at the configured port and walk upward until `127.0.0.1:<port>` is free. Pass `--setup` to explicitly allow setup when the hosts block is missing or stale.
|
|
138
150
|
|
|
151
|
+
When `localghost.config.mjs` exists, `run`, `dev`, `setup`, `status`, `routes`, and the Vite plugin use it as the shared context. This keeps `https`, `dynamicPort`, `project`, and `wwwAlias` decisions consistent.
|
|
152
|
+
|
|
139
153
|
`dev` and `run` register active sessions in a user-local activity file so `localghost ps` can show what is running across projects.
|
|
140
154
|
|
|
141
155
|
### print
|
|
@@ -149,6 +163,7 @@ localghost print
|
|
|
149
163
|
## Files
|
|
150
164
|
|
|
151
165
|
- `.localghost`: default project hostname config.
|
|
166
|
+
- `localghost.config.mjs`: optional shared context for CLI and Vite settings.
|
|
152
167
|
- custom config files: pass `--config <file>` or `--config-pattern <regex>`.
|
|
153
168
|
- `ops/local/Caddyfile`: generated local Caddy config.
|
|
154
169
|
- `ops/local/localghost-state.json`: last setup or teardown action.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Localghost macOS Widget
|
|
2
|
+
|
|
3
|
+
The Localghost widget is a tiny native macOS menu-bar helper. It does not start or stop apps. It only shows what Localghost already knows is running from `localghost ps --json`.
|
|
4
|
+
|
|
5
|
+
The menu-bar title is `LG n`, where `n` is the number of active Localghost-managed sessions. The menu lists each project, wrapper PID, working directory, route, target port, and whether the upstream port is listening.
|
|
6
|
+
|
|
7
|
+
## Build
|
|
8
|
+
|
|
9
|
+
Build the CLI first, then build the app bundle:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm run build
|
|
13
|
+
npm run macos:widget:build
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The app is written to:
|
|
17
|
+
|
|
18
|
+
```txt
|
|
19
|
+
dist/LocalghostWidget.app
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Run
|
|
23
|
+
|
|
24
|
+
If `localghost` is installed on your shell path, launch the app bundle normally.
|
|
25
|
+
|
|
26
|
+
For source development, point the widget at the repo build:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
LOCALGHOST_CLI="$PWD/dist/cli.js" dist/LocalghostWidget.app/Contents/MacOS/LocalghostWidget
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Data Source
|
|
33
|
+
|
|
34
|
+
The widget polls:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
localghost --no-update-check ps --json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
That command reads the user-local activity file, prunes stale records, and probes each upstream port. The activity file defaults to:
|
|
41
|
+
|
|
42
|
+
```txt
|
|
43
|
+
~/.local/state/localghost/activity.json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Set `LOCALGHOST_ACTIVITY_PATH` when you want the CLI and widget to share a custom activity file during tests.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hamedb89/localghost",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Friendly local hostnames for app repos with .localghost, Caddy, /etc/hosts, and Vite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
}
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
+
"apps/macos-widget",
|
|
20
21
|
"dist",
|
|
21
22
|
"assets",
|
|
22
23
|
"docs",
|
|
@@ -41,6 +42,7 @@
|
|
|
41
42
|
"scripts": {
|
|
42
43
|
"clean": "rm -rf dist",
|
|
43
44
|
"build": "tsup src/index.ts src/vite.ts src/cli.ts --format esm --dts",
|
|
45
|
+
"macos:widget:build": "bash apps/macos-widget/build.sh",
|
|
44
46
|
"dev": "tsx src/cli.ts",
|
|
45
47
|
"typecheck": "tsc --noEmit",
|
|
46
48
|
"prepack": "npm run build",
|