@hamedb89/localghost 0.1.3 → 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 +148 -14
- package/apps/macos-widget/LocalghostWidget.swift +218 -0
- package/apps/macos-widget/build.sh +53 -0
- package/dist/cli.js +831 -90
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +110 -6
- package/dist/index.js +361 -51
- package/dist/index.js.map +1 -1
- package/dist/vite.d.ts +5 -0
- package/dist/vite.js +516 -17
- package/dist/vite.js.map +1 -1
- package/docs/flows.md +27 -5
- package/docs/github.md +5 -5
- package/docs/localghost.1.md +60 -9
- package/docs/macos-widget.md +46 -0
- package/package.json +8 -4
package/dist/vite.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts","../src/parse.ts","../src/vite.ts"],"sourcesContent":["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 type { HmrOptions, Plugin, UserConfig, ViteDevServer, WsOptions } from \"vite\";\nimport { readDevHosts, type ConfigPattern, type ReadDevHostsOptions } from \"./config.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 primaryHost?: string;\n log?: 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 ` open: ${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\nexport function localGhostPlugin(options: LocalGhostPluginOptions = {}): Plugin {\n let resolvedEntries: DevHostEntry[] = [];\n let resolvedVitePort: number | undefined;\n\n return {\n name: \"localghost:vite\",\n enforce: \"pre\",\n\n config(userConfig): UserConfig {\n const entries = readDevHosts(readOptionsFromPlugin(options));\n const hosts = [...new Set(entries.map((entry) => entry.host))];\n const existingServer = userConfig.server ?? {};\n const vitePort =\n options.port ??\n existingServer.port ??\n entries.find((entry) => !entry.host.startsWith(\"api.\"))?.port ??\n entries[0]?.port;\n const primaryHost =\n options.primaryHost ??\n entries.find((entry) => entry.port === vitePort)?.host ??\n hosts[0];\n\n resolvedEntries = entries;\n resolvedVitePort = vitePort;\n\n const server: ServerOptions = {\n ...existingServer,\n allowedHosts: mergeAllowedHosts(existingServer.allowedHosts, hosts),\n strictPort: existingServer.strictPort ?? true\n };\n\n if (vitePort) {\n server.port = vitePort;\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 if (options.log === false) {\n return;\n }\n\n server.httpServer?.once(\"listening\", () => {\n printLocalHosts(server, resolvedEntries, resolvedVitePort, Boolean(options.https));\n });\n }\n };\n}\n\nexport const localHostsPlugin = localGhostPlugin;\nexport type LocalHostsPluginOptions = LocalGhostPluginOptions;\n"],"mappings":"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","names":["fileName"]}
|
|
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/flows.md
CHANGED
|
@@ -58,14 +58,21 @@ yarn localghost:setup
|
|
|
58
58
|
|
|
59
59
|
## Daily Dev
|
|
60
60
|
|
|
61
|
-
As a developer, I want a daily command that starts the local
|
|
61
|
+
As a developer, I want a daily command that starts the local HTTP proxy from the same config file, with local HTTPS available only when I ask for it.
|
|
62
62
|
|
|
63
63
|
```sh
|
|
64
|
+
yarn localghost:ready
|
|
64
65
|
yarn localghost:proxy
|
|
65
66
|
```
|
|
66
67
|
|
|
67
68
|
Most repos will run this next to their app server, for example Vite on `127.0.0.1:5173`.
|
|
68
69
|
|
|
70
|
+
When a repo really needs local certificates:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
yarn localghost:proxy:https
|
|
74
|
+
```
|
|
75
|
+
|
|
69
76
|
## Config Discovery
|
|
70
77
|
|
|
71
78
|
As a developer, I want Localghost to fit repos that already have naming conventions without hidden file searches.
|
|
@@ -88,24 +95,39 @@ yarn localghost routes
|
|
|
88
95
|
|
|
89
96
|
```txt
|
|
90
97
|
localghost routes
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
http://app.localhost/ -> http://127.0.0.1:5173
|
|
99
|
+
http://api.app.localhost/ -> http://127.0.0.1:8787
|
|
93
100
|
```
|
|
94
101
|
|
|
95
102
|
`setup` and `dev` print this same map before Caddy is validated or run.
|
|
96
103
|
|
|
97
104
|
## Vite Integration
|
|
98
105
|
|
|
99
|
-
As a Vite user, I want Localghost to set strict `allowedHosts
|
|
106
|
+
As a Vite user, I want Localghost to set the dev host to my configured domain, keep strict `allowedHosts`, print browser-facing URLs, and never run in production/build mode.
|
|
100
107
|
|
|
101
108
|
```ts
|
|
102
109
|
import { localGhostPlugin } from "@hamedb89/localghost/vite";
|
|
103
110
|
|
|
104
111
|
export default {
|
|
105
|
-
plugins: [localGhostPlugin({ port: 5173
|
|
112
|
+
plugins: [localGhostPlugin({ port: 5173 })]
|
|
106
113
|
};
|
|
107
114
|
```
|
|
108
115
|
|
|
116
|
+
The plugin defaults to HTTP. Pass `https: true` only when Vite is expected to sit behind a Caddy HTTPS proxy. Localghost prints URLs but does not open browser tabs.
|
|
117
|
+
|
|
118
|
+
If `.localghost` is missing, an interactive `yarn dev` asks whether to create it, asks for the primary `.localhost` domain, allows extra domains, explains the `/etc/hosts` password prompt, and runs setup when confirmed. Non-interactive runs fail with the exact setup command instead of guessing.
|
|
119
|
+
|
|
120
|
+
## Reset For Testing
|
|
121
|
+
|
|
122
|
+
As a developer, I want to retest setup without deleting my project config.
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
yarn localghost reset
|
|
126
|
+
yarn localghost setup
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`reset` removes only the managed hosts block, generated Caddyfile, and setup state. It leaves `.localghost` in place.
|
|
130
|
+
|
|
109
131
|
## Teardown
|
|
110
132
|
|
|
111
133
|
As a developer, I want to cleanly remove Localghost from a project when the repo is archived or no longer needs friendly hostnames.
|
package/docs/github.md
CHANGED
|
@@ -4,11 +4,11 @@ Use this copy for the GitHub repo About box, topics, and social cards. Keep it s
|
|
|
4
4
|
|
|
5
5
|
## Repository Description
|
|
6
6
|
|
|
7
|
-
Friendly local
|
|
7
|
+
Friendly local hostnames for app repos. A tiny CLI for `.localghost` configs, `/etc/hosts` blocks, Caddy reverse proxies, and Vite `allowedHosts`.
|
|
8
8
|
|
|
9
9
|
Shorter alternative:
|
|
10
10
|
|
|
11
|
-
Friendly local
|
|
11
|
+
Friendly local hostnames for app repos, powered by `.localghost`, Caddy, `/etc/hosts`, and Vite.
|
|
12
12
|
|
|
13
13
|
## Topics
|
|
14
14
|
|
|
@@ -49,7 +49,7 @@ reverse-proxy
|
|
|
49
49
|
|
|
50
50
|
## Search Phrases To Own
|
|
51
51
|
|
|
52
|
-
- local
|
|
52
|
+
- local hostnames for Vite
|
|
53
53
|
- Caddy localhost reverse proxy
|
|
54
54
|
- friendly localhost domains
|
|
55
55
|
- manage /etc/hosts for local development
|
|
@@ -62,7 +62,7 @@ After creating `hamedb89/localghost`, this sets the public repo metadata:
|
|
|
62
62
|
|
|
63
63
|
```sh
|
|
64
64
|
gh repo edit hamedb89/localghost \
|
|
65
|
-
--description "Friendly local
|
|
65
|
+
--description "Friendly local hostnames for app repos. A tiny CLI for .localghost configs, /etc/hosts blocks, Caddy reverse proxies, and Vite allowedHosts." \
|
|
66
66
|
--homepage "https://hamedb89.github.io/localghost/" \
|
|
67
67
|
--add-topic localhost \
|
|
68
68
|
--add-topic local-development \
|
|
@@ -81,7 +81,7 @@ gh repo edit hamedb89/localghost \
|
|
|
81
81
|
The first visible paragraph should say what it is, who it is for, and what tools it touches:
|
|
82
82
|
|
|
83
83
|
```txt
|
|
84
|
-
Localghost is a tiny Node.js CLI for local
|
|
84
|
+
Localghost is a tiny Node.js CLI for local domains in app repos. It gives each project one small contract for `.localhost` hostnames, Caddy reverse proxies, Vite `allowedHosts`, and the system hosts file.
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
That phrasing helps GitHub search and npm search without making the README feel like SEO sludge.
|
package/docs/localghost.1.md
CHANGED
|
@@ -9,20 +9,28 @@ localghost - friendly local hostnames for app repos
|
|
|
9
9
|
```sh
|
|
10
10
|
localghost init [--write-scripts] [--config file] [--host host] [--port port]
|
|
11
11
|
localghost doctor
|
|
12
|
-
localghost setup [--project name] [--config file] [--config-pattern regex]
|
|
12
|
+
localghost setup [--project name] [--config file] [--config-pattern regex] [--https|--ssl]
|
|
13
|
+
localghost trust [--project name] [--config file] [--config-pattern regex] [--https|--ssl]
|
|
14
|
+
localghost reset [--project name]
|
|
13
15
|
localghost teardown [--project name] [--remove-caddyfile]
|
|
14
|
-
localghost status [--json]
|
|
16
|
+
localghost status [--ready] [--json]
|
|
17
|
+
localghost ps [--json]
|
|
15
18
|
localghost update [--json]
|
|
16
|
-
localghost dev [--config file] [--config-pattern regex]
|
|
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
|
|
17
21
|
localghost print [--config file] [--config-pattern regex]
|
|
18
22
|
```
|
|
19
23
|
|
|
20
24
|
## Description
|
|
21
25
|
|
|
22
|
-
Localghost reads `.localghost`, writes a managed `/etc/hosts` block, records `ops/local/localghost-state.json`, generates `ops/local/Caddyfile`, and runs a Caddy local 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.
|
|
23
27
|
|
|
24
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`.
|
|
25
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`.
|
|
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
|
+
|
|
26
34
|
## Commands
|
|
27
35
|
|
|
28
36
|
### init
|
|
@@ -56,12 +64,20 @@ Currently checks Caddy and prints `brew install caddy` when missing.
|
|
|
56
64
|
|
|
57
65
|
### setup
|
|
58
66
|
|
|
59
|
-
Updates the managed Localghost block in `/etc/hosts`, writes `ops/local/Caddyfile`, and validates it with Caddy. Pass `--config <file>` to look for a specific config file. Repeat `--config` to use the first existing file from an ordered list. Pass `--config-pattern <regex>` to search matching filenames in the project root.
|
|
67
|
+
Updates the managed Localghost block in `/etc/hosts`, writes `ops/local/Caddyfile`, and validates it with Caddy. Pass `--config <file>` to look for a specific config file. Repeat `--config` to use the first existing file from an ordered list. Pass `--config-pattern <regex>` to search matching filenames in the project root. Pass `--https` or `--ssl` to generate a local HTTPS Caddyfile.
|
|
60
68
|
|
|
61
69
|
```sh
|
|
62
70
|
localghost setup --project app
|
|
63
71
|
```
|
|
64
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
|
+
|
|
65
81
|
### teardown
|
|
66
82
|
|
|
67
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.
|
|
@@ -70,12 +86,30 @@ Removes the managed Localghost block from `/etc/hosts` for the selected project
|
|
|
70
86
|
localghost teardown --remove-caddyfile
|
|
71
87
|
```
|
|
72
88
|
|
|
89
|
+
### reset
|
|
90
|
+
|
|
91
|
+
Removes the managed Localghost hosts block, generated Caddyfile, and setup state, but keeps `.localghost` in place so setup can be tested again.
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
localghost reset
|
|
95
|
+
localghost setup
|
|
96
|
+
```
|
|
97
|
+
|
|
73
98
|
### status
|
|
74
99
|
|
|
75
|
-
Prints Localghost's project-local state file. Pass `--json` for scripts and agents.
|
|
100
|
+
Prints Localghost's project-local state file and setup readiness. Pass `--ready` to exit non-zero when the hosts block or Caddyfile is missing or stale. Pass `--json` for scripts and agents.
|
|
76
101
|
|
|
77
102
|
```sh
|
|
78
|
-
localghost status --
|
|
103
|
+
localghost status --ready
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### ps
|
|
107
|
+
|
|
108
|
+
Shows Localghost `dev` and `run` sessions that are currently running on the machine. Stale records are pruned automatically when their wrapper process is gone. Each route also reports whether its upstream `127.0.0.1:<port>` is listening. Pass `--json` for menu bar helpers or other polling tools.
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
localghost ps
|
|
112
|
+
localghost ps --json
|
|
79
113
|
```
|
|
80
114
|
|
|
81
115
|
### update
|
|
@@ -88,7 +122,7 @@ localghost update
|
|
|
88
122
|
|
|
89
123
|
### routes
|
|
90
124
|
|
|
91
|
-
Prints the local domain layer as `domain -> upstream` routes. Pass `--
|
|
125
|
+
Prints the local domain layer as `domain -> upstream` routes. HTTP is the default. Pass `--https` or `--ssl` if the browser-facing domain should be shown as HTTPS.
|
|
92
126
|
|
|
93
127
|
```sh
|
|
94
128
|
localghost routes
|
|
@@ -96,12 +130,28 @@ localghost routes
|
|
|
96
130
|
|
|
97
131
|
### dev
|
|
98
132
|
|
|
99
|
-
|
|
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.
|
|
100
134
|
|
|
101
135
|
```sh
|
|
102
136
|
localghost dev
|
|
103
137
|
```
|
|
104
138
|
|
|
139
|
+
### run
|
|
140
|
+
|
|
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`.
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
localghost run -- vite
|
|
145
|
+
localghost run --trust -- vite
|
|
146
|
+
localghost run --dynamic-port -- turbo dev
|
|
147
|
+
```
|
|
148
|
+
|
|
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.
|
|
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
|
+
|
|
153
|
+
`dev` and `run` register active sessions in a user-local activity file so `localghost ps` can show what is running across projects.
|
|
154
|
+
|
|
105
155
|
### print
|
|
106
156
|
|
|
107
157
|
Prints parsed Localghost config entries as JSON. Supports `--config` and `--config-pattern`.
|
|
@@ -113,6 +163,7 @@ localghost print
|
|
|
113
163
|
## Files
|
|
114
164
|
|
|
115
165
|
- `.localghost`: default project hostname config.
|
|
166
|
+
- `localghost.config.mjs`: optional shared context for CLI and Vite settings.
|
|
116
167
|
- custom config files: pass `--config <file>` or `--config-pattern <regex>`.
|
|
117
168
|
- `ops/local/Caddyfile`: generated local Caddy config.
|
|
118
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,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hamedb89/localghost",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Friendly local
|
|
3
|
+
"version": "0.1.8",
|
|
4
|
+
"description": "Friendly local hostnames for app repos with .localghost, Caddy, /etc/hosts, and Vite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"localghost": "dist/cli.js"
|
|
@@ -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,14 +42,17 @@
|
|
|
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",
|
|
47
49
|
"prepublishOnly": "npm run release:check",
|
|
48
50
|
"pack:dry": "npm pack --dry-run",
|
|
49
|
-
"release:check": "npm run typecheck && npm run build && npm run site:build && npm pack --dry-run",
|
|
51
|
+
"release:check": "npm run version:check && npm run typecheck && npm run build && npm run site:build && npm pack --dry-run",
|
|
50
52
|
"site:build": "node scripts/build-site.mjs",
|
|
51
|
-
"version": "node scripts/sync-readme-version.mjs
|
|
53
|
+
"sync:version": "node scripts/sync-readme-version.mjs",
|
|
54
|
+
"version:check": "node scripts/sync-readme-version.mjs --check",
|
|
55
|
+
"version": "npm run sync:version && git add README.md src/update-check.ts",
|
|
52
56
|
"publish:public": "npm publish --access public --provenance"
|
|
53
57
|
},
|
|
54
58
|
"dependencies": {
|