@hamedb89/localghost 0.1.0
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/LICENSE +21 -0
- package/README.md +278 -0
- package/assets/localghost-app-icon.png +0 -0
- package/assets/localghost-banner.png +0 -0
- package/assets/localghost-mascot.png +0 -0
- package/assets/localghost-wordmark.png +0 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +804 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-Cde1Bich.d.ts +31 -0
- package/dist/index.d.ts +132 -0
- package/dist/index.js +648 -0
- package/dist/index.js.map +1 -0
- package/dist/vite.d.ts +18 -0
- package/dist/vite.js +202 -0
- package/dist/vite.js.map +1 -0
- package/docs/brand.md +43 -0
- package/docs/flows.md +141 -0
- package/docs/github.md +129 -0
- package/docs/localghost.1.md +124 -0
- package/package.json +92 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/parse.ts","../src/caddy.ts","../src/fs.ts","../src/doctor.ts","../src/hosts-file.ts","../src/init.ts","../src/routes.ts","../src/state.ts","../src/update-check.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 { dirname, join } from \"node:path\";\nimport { execa } from \"execa\";\nimport { writeTextFile } from \"./fs.js\";\nimport type { DevHostEntry } from \"./parse.js\";\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[]) {\n const groups = groupByPort(entries);\n const blocks = [...groups.entries()]\n .sort(([leftPort], [rightPort]) => leftPort - rightPort)\n .map(([port, group]) => {\n const hosts = group\n .map((entry) => entry.host)\n .sort()\n .join(\", \");\n\n return `${hosts} {\n reverse_proxy 127.0.0.1:${port}\n}`;\n });\n\n return `{\n local_certs\n}\n\n${blocks.join(\"\\n\\n\")}\n`;\n}\n\nexport async function writeCaddyfile(entries: DevHostEntry[], cwd = process.cwd()) {\n const path = getCaddyfilePath(cwd);\n writeTextFile(path, renderCaddyfile(entries));\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","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 { 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","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 { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getProjectName, LOCALGHOST_CONFIG_FILE, sanitizeProjectName } from \"./config.js\";\nimport { writeTextFile } from \"./fs.js\";\n\nexport type PackageManager = \"npm\" | \"yarn\" | \"pnpm\";\n\nexport type InitOptions = {\n cwd?: string;\n host?: string;\n port?: number;\n apiHost?: string;\n apiPort?: number;\n force?: boolean;\n packageManager?: PackageManager;\n writeScripts?: boolean;\n configFile?: string;\n};\n\nexport type InitResult = {\n configPath: string;\n configCreated: boolean;\n packageJsonPath?: string;\n packageJsonChanged: boolean;\n packageManager: PackageManager;\n nextSteps: string[];\n};\n\nexport function detectPackageManager(cwd = process.cwd()): PackageManager {\n if (existsSync(join(cwd, \"pnpm-lock.yaml\"))) return \"pnpm\";\n if (existsSync(join(cwd, \"yarn.lock\"))) return \"yarn\";\n return \"npm\";\n}\n\nexport function packageRunCommand(packageManager: PackageManager, script: string): string {\n if (packageManager === \"yarn\") return `yarn ${script}`;\n if (packageManager === \"pnpm\") return `pnpm ${script}`;\n return `npm run ${script}`;\n}\n\nexport function packageAddCommand(packageManager: PackageManager, packageName = \"@hamedb89/localghost\"): string {\n if (packageManager === \"yarn\") return `yarn add -D ${packageName}`;\n if (packageManager === \"pnpm\") return `pnpm add -D ${packageName}`;\n return `npm install -D ${packageName}`;\n}\n\nfunction renderConfig(options: Required<Pick<InitOptions, \"host\" | \"port\" | \"apiHost\" | \"apiPort\">>) {\n return [\n \"# Buh. Friendly names for local services.\",\n \"# Format: <host> <port>\",\n `${options.host} ${options.port}`,\n `www.${options.host} ${options.port}`,\n `${options.apiHost} ${options.apiPort}`,\n \"\"\n ].join(\"\\n\");\n}\n\nfunction readPackageJson(path: string): Record<string, unknown> | null {\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction shellQuote(value: string) {\n if (/^[A-Za-z0-9_./:-]+$/.test(value)) return value;\n return `'${value.replace(/'/g, `'\"'\"'`)}'`;\n}\n\nfunction getConfigFlag(configFile: string) {\n return configFile === LOCALGHOST_CONFIG_FILE ? \"\" : ` --config ${shellQuote(configFile)}`;\n}\n\nfunction updatePackageScripts(packageJsonPath: string, configFile: string): boolean {\n const pkg = readPackageJson(packageJsonPath);\n if (!pkg) return false;\n\n const scripts = typeof pkg.scripts === \"object\" && pkg.scripts ? (pkg.scripts as Record<string, unknown>) : {};\n const configFlag = getConfigFlag(configFile);\n const nextScripts = {\n ...scripts,\n \"localghost:setup\": scripts[\"localghost:setup\"] ?? `localghost setup${configFlag}`,\n \"localghost:proxy\": scripts[\"localghost:proxy\"] ?? `localghost dev${configFlag}`,\n \"localghost:print\": scripts[\"localghost:print\"] ?? `localghost print${configFlag}`,\n \"localghost:routes\": scripts[\"localghost:routes\"] ?? `localghost routes${configFlag}`,\n \"localghost:status\": scripts[\"localghost:status\"] ?? \"localghost status\",\n \"localghost:teardown\": scripts[\"localghost:teardown\"] ?? \"localghost teardown\",\n \"localghost:doctor\": scripts[\"localghost:doctor\"] ?? \"localghost doctor\",\n \"localghost:update\": scripts[\"localghost:update\"] ?? \"localghost update\"\n };\n\n const changed = JSON.stringify(scripts) !== JSON.stringify(nextScripts);\n if (!changed) return false;\n\n pkg.scripts = nextScripts;\n writeFileSync(packageJsonPath, `${JSON.stringify(pkg, null, 2)}\\n`, \"utf8\");\n return true;\n}\n\nexport function initLocalghost(options: InitOptions = {}): InitResult {\n const cwd = options.cwd ?? process.cwd();\n const projectName = sanitizeProjectName(getProjectName(cwd).split(\"/\").pop() ?? \"app\");\n const host = options.host ?? `${projectName}.localhost`;\n const port = options.port ?? 5173;\n const apiHost = options.apiHost ?? `api.${host}`;\n const apiPort = options.apiPort ?? 8787;\n const packageManager = options.packageManager ?? detectPackageManager(cwd);\n const configFile = options.configFile ?? LOCALGHOST_CONFIG_FILE;\n const configPath = join(cwd, configFile);\n const configExists = existsSync(configPath);\n\n if (configExists && !options.force) {\n return {\n configPath,\n configCreated: false,\n packageJsonChanged: false,\n packageManager,\n nextSteps: [\n packageRunCommand(packageManager, \"localghost:doctor\"),\n packageRunCommand(packageManager, \"localghost:setup\"),\n packageRunCommand(packageManager, \"localghost:proxy\")\n ]\n };\n }\n\n writeTextFile(configPath, renderConfig({ host, port, apiHost, apiPort }));\n\n const packageJsonPath = join(cwd, \"package.json\");\n const packageJsonChanged = options.writeScripts ? updatePackageScripts(packageJsonPath, configFile) : false;\n\n return {\n configPath,\n configCreated: true,\n ...(existsSync(packageJsonPath) ? { packageJsonPath } : {}),\n packageJsonChanged,\n packageManager,\n nextSteps: [\n packageRunCommand(packageManager, \"localghost:doctor\"),\n packageRunCommand(packageManager, \"localghost:setup\"),\n packageRunCommand(packageManager, \"localghost:proxy\")\n ]\n };\n}\n","import type { DevHostEntry } from \"./parse.js\";\n\nexport type DomainRoute = {\n host: string;\n port: number;\n url: string;\n upstream: string;\n};\n\nexport type DomainRouteOptions = {\n https?: boolean;\n};\n\nexport function getDomainRoutes(entries: DevHostEntry[], options: DomainRouteOptions = {}): DomainRoute[] {\n const protocol = options.https === false ? \"http\" : \"https\";\n\n return [...entries]\n .sort((left, right) => left.host.localeCompare(right.host) || left.port - right.port)\n .map((entry) => ({\n host: entry.host,\n port: entry.port,\n url: `${protocol}://${entry.host}/`,\n upstream: `http://${entry.target}`\n }));\n}\n\nexport function formatDomainRoutes(entries: DevHostEntry[], options: DomainRouteOptions = {}) {\n const routes = getDomainRoutes(entries, options);\n\n if (routes.length === 0) {\n return \"localghost routes\\n no routes\";\n }\n\n return [\n \"localghost routes\",\n ...routes.map((route) => ` ${route.url} -> ${route.upstream}`)\n ].join(\"\\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 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 { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport const LOCALGHOST_PACKAGE_NAME = \"@hamedb89/localghost\";\nexport const LOCALGHOST_VERSION = \"0.1.0\";\nexport const UPDATE_CHECK_CACHE_TTL_MS = 24 * 60 * 60 * 1000;\nexport const UPDATE_CHECK_NOTIFY_TTL_MS = 24 * 60 * 60 * 1000;\nexport const UPDATE_CHECK_TIMEOUT_MS = 900;\n\nexport type UpdateCheckCache = {\n checkedAt: string;\n latestVersion?: string;\n notifiedVersion?: string;\n notifiedAt?: string;\n};\n\nexport type UpdateCheckResult = {\n currentVersion: string;\n packageName: string;\n latestVersion?: string;\n updateAvailable: boolean;\n source: \"cache\" | \"registry\" | \"disabled\" | \"error\";\n error?: string;\n};\n\ntype RegistryPackageResponse = {\n \"dist-tags\"?: {\n latest?: unknown;\n };\n};\n\nfunction truthyEnv(value: string | undefined) {\n return value === \"1\" || value === \"true\" || value === \"yes\";\n}\n\nexport function isUpdateCheckDisabled(env: NodeJS.ProcessEnv = process.env) {\n return truthyEnv(env.LOCALGHOST_NO_UPDATE_CHECK);\n}\n\nexport function getUpdateCheckCachePath(env: NodeJS.ProcessEnv = process.env) {\n if (env.LOCALGHOST_UPDATE_CHECK_CACHE) return env.LOCALGHOST_UPDATE_CHECK_CACHE;\n\n const cacheRoot = env.XDG_CACHE_HOME || join(homedir(), \".cache\");\n return join(cacheRoot, \"localghost\", \"update-check.json\");\n}\n\nfunction readCache(path = getUpdateCheckCachePath()): UpdateCheckCache | null {\n if (!existsSync(path)) return null;\n\n try {\n return JSON.parse(readFileSync(path, \"utf8\")) as UpdateCheckCache;\n } catch {\n return null;\n }\n}\n\nfunction writeCache(cache: UpdateCheckCache, path = getUpdateCheckCachePath()) {\n try {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, `${JSON.stringify(cache, null, 2)}\\n`, \"utf8\");\n } catch {\n // Update checks should never make the real command fail.\n }\n}\n\nfunction ageMs(date: string | undefined, now = Date.now()) {\n if (!date) return Number.POSITIVE_INFINITY;\n const time = Date.parse(date);\n return Number.isFinite(time) ? now - time : Number.POSITIVE_INFINITY;\n}\n\nfunction isCacheFresh(cache: UpdateCheckCache | null, ttlMs: number, now = Date.now()) {\n return Boolean(cache?.latestVersion && ageMs(cache.checkedAt, now) >= 0 && ageMs(cache.checkedAt, now) < ttlMs);\n}\n\ntype ParsedVersion = {\n major: number;\n minor: number;\n patch: number;\n prerelease?: string;\n};\n\nfunction parseVersion(version: string): ParsedVersion | null {\n const match = version.trim().replace(/^v/, \"\").match(/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/);\n if (!match) return null;\n\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n patch: Number(match[3]),\n ...(match[4] ? { prerelease: match[4] } : {})\n };\n}\n\nexport function compareVersions(a: string, b: string) {\n const left = parseVersion(a);\n const right = parseVersion(b);\n\n if (!left || !right) return a.localeCompare(b);\n\n for (const key of [\"major\", \"minor\", \"patch\"] as const) {\n if (left[key] !== right[key]) return left[key] > right[key] ? 1 : -1;\n }\n\n if (left.prerelease === right.prerelease) return 0;\n if (!left.prerelease) return 1;\n if (!right.prerelease) return -1;\n return left.prerelease.localeCompare(right.prerelease);\n}\n\nexport function isNewerVersion(candidate: string | undefined, current = LOCALGHOST_VERSION) {\n return Boolean(candidate && compareVersions(candidate, current) > 0);\n}\n\nasync function fetchLatestVersion(packageName: string, timeoutMs: number) {\n const encodedName = packageName.startsWith(\"@\") ? `@${packageName.slice(1).replace(\"/\", \"%2f\")}` : packageName;\n const response = await fetch(`https://registry.npmjs.org/${encodedName}`, {\n signal: AbortSignal.timeout(timeoutMs),\n headers: {\n accept: \"application/vnd.npm.install-v1+json\"\n }\n });\n\n if (!response.ok) throw new Error(`npm registry returned ${response.status}`);\n\n const data = (await response.json()) as RegistryPackageResponse;\n const latest = data[\"dist-tags\"]?.latest;\n if (typeof latest !== \"string\" || latest.length === 0) throw new Error(\"npm registry response did not include latest dist-tag\");\n\n return latest;\n}\n\nexport async function checkForUpdate(options: {\n force?: boolean;\n packageName?: string;\n currentVersion?: string;\n timeoutMs?: number;\n cachePath?: string;\n env?: NodeJS.ProcessEnv;\n} = {}): Promise<UpdateCheckResult> {\n const env = options.env ?? process.env;\n const packageName = options.packageName ?? LOCALGHOST_PACKAGE_NAME;\n const currentVersion = options.currentVersion ?? LOCALGHOST_VERSION;\n const cachePath = options.cachePath ?? getUpdateCheckCachePath(env);\n\n if (!options.force && isUpdateCheckDisabled(env)) {\n return {\n currentVersion,\n packageName,\n updateAvailable: false,\n source: \"disabled\"\n };\n }\n\n const cache = readCache(cachePath);\n if (!options.force && isCacheFresh(cache, UPDATE_CHECK_CACHE_TTL_MS)) {\n const latestVersion = cache?.latestVersion;\n return {\n currentVersion,\n packageName,\n ...(latestVersion ? { latestVersion } : {}),\n updateAvailable: isNewerVersion(latestVersion, currentVersion),\n source: \"cache\"\n };\n }\n\n try {\n const latestVersion = await fetchLatestVersion(packageName, options.timeoutMs ?? UPDATE_CHECK_TIMEOUT_MS);\n writeCache({ checkedAt: new Date().toISOString(), latestVersion }, cachePath);\n\n return {\n currentVersion,\n packageName,\n latestVersion,\n updateAvailable: isNewerVersion(latestVersion, currentVersion),\n source: \"registry\"\n };\n } catch (error) {\n const latestVersion = cache?.latestVersion;\n return {\n currentVersion,\n packageName,\n ...(latestVersion ? { latestVersion } : {}),\n updateAvailable: isNewerVersion(latestVersion, currentVersion),\n source: \"error\",\n error: error instanceof Error ? error.message : String(error)\n };\n }\n}\n\nexport function formatUpdateMessage(result: UpdateCheckResult) {\n if (!result.updateAvailable || !result.latestVersion) return null;\n\n return [\n `localghost ${result.latestVersion} is available. Current: ${result.currentVersion}`,\n `Update with: npm i -g ${result.packageName}@latest`\n ].join(\"\\n\");\n}\n\nexport function shouldNotifyAboutUpdate(result: UpdateCheckResult, cachePath = getUpdateCheckCachePath(), now = Date.now()) {\n if (!result.updateAvailable || !result.latestVersion) return false;\n\n const cache = readCache(cachePath);\n if (cache?.notifiedVersion !== result.latestVersion) return true;\n\n return ageMs(cache.notifiedAt, now) >= UPDATE_CHECK_NOTIFY_TTL_MS;\n}\n\nexport function markUpdateNotified(result: UpdateCheckResult, cachePath = getUpdateCheckCachePath()) {\n if (!result.latestVersion) return;\n\n const cache = readCache(cachePath) ?? { checkedAt: new Date().toISOString() };\n writeCache(\n {\n ...cache,\n latestVersion: result.latestVersion,\n notifiedVersion: result.latestVersion,\n notifiedAt: new Date().toISOString()\n },\n cachePath\n );\n}\n\nexport async function maybeNotifyAboutUpdate(options: { disabled?: boolean } = {}) {\n if (options.disabled) return;\n\n const cachePath = getUpdateCheckCachePath();\n const result = await checkForUpdate({ cachePath });\n if (!shouldNotifyAboutUpdate(result, cachePath)) return;\n\n const message = formatUpdateMessage(result);\n if (!message) return;\n\n console.warn(`\\n${message}`);\n markUpdateNotified(result, cachePath);\n}\n"],"mappings":"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","names":["fileName","dirname","join","readFileSync","join","dirname","execa","writeFileSync","join","execa","join","writeFileSync","execa","existsSync","readFileSync","writeFileSync","join","existsSync","join","readFileSync","writeFileSync","existsSync","join","join","existsSync","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","join"]}
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import { C as ConfigPattern } from './config-Cde1Bich.js';
|
|
3
|
+
|
|
4
|
+
type LocalGhostPluginOptions = {
|
|
5
|
+
cwd?: string;
|
|
6
|
+
fileName?: string;
|
|
7
|
+
configFiles?: string[];
|
|
8
|
+
configPattern?: ConfigPattern;
|
|
9
|
+
port?: number;
|
|
10
|
+
https?: boolean;
|
|
11
|
+
primaryHost?: string;
|
|
12
|
+
log?: boolean;
|
|
13
|
+
};
|
|
14
|
+
declare function localGhostPlugin(options?: LocalGhostPluginOptions): Plugin;
|
|
15
|
+
declare const localHostsPlugin: typeof localGhostPlugin;
|
|
16
|
+
type LocalHostsPluginOptions = LocalGhostPluginOptions;
|
|
17
|
+
|
|
18
|
+
export { type LocalGhostPluginOptions, type LocalHostsPluginOptions, localGhostPlugin, localHostsPlugin };
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from "fs";
|
|
3
|
+
import { basename, join, resolve } from "path";
|
|
4
|
+
|
|
5
|
+
// src/parse.ts
|
|
6
|
+
var HOST_PATTERN = /^(?=.{1,253}$)(?!-)[a-z0-9-]+(\.[a-z0-9-]+)*\.?$/i;
|
|
7
|
+
function parseDevHosts(input, fileName = ".localghost") {
|
|
8
|
+
const entries = [];
|
|
9
|
+
input.split(/\r?\n/).forEach((rawLine, index) => {
|
|
10
|
+
const line = rawLine.replace(/#.*/, "").trim();
|
|
11
|
+
if (!line) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const parts = line.split(/\s+/);
|
|
15
|
+
const host = parts[0];
|
|
16
|
+
const portRaw = parts[1];
|
|
17
|
+
if (!host || !portRaw || parts.length > 2) {
|
|
18
|
+
throw new Error(`Invalid ${fileName} line ${index + 1}: "${rawLine}"`);
|
|
19
|
+
}
|
|
20
|
+
if (!HOST_PATTERN.test(host)) {
|
|
21
|
+
throw new Error(`Invalid host on line ${index + 1}: "${host}"`);
|
|
22
|
+
}
|
|
23
|
+
const port = Number(portRaw);
|
|
24
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
25
|
+
throw new Error(`Invalid port on line ${index + 1}: "${portRaw}"`);
|
|
26
|
+
}
|
|
27
|
+
entries.push({
|
|
28
|
+
host: host.toLowerCase().replace(/\.$/, ""),
|
|
29
|
+
port,
|
|
30
|
+
target: `127.0.0.1:${port}`
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
return entries;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/config.ts
|
|
37
|
+
var LOCALGHOST_CONFIG_FILE = ".localghost";
|
|
38
|
+
function unique(values) {
|
|
39
|
+
return [...new Set(values.filter(Boolean))];
|
|
40
|
+
}
|
|
41
|
+
function toRegExp(pattern) {
|
|
42
|
+
return typeof pattern === "string" ? new RegExp(pattern) : pattern;
|
|
43
|
+
}
|
|
44
|
+
function findPatternMatches(cwd, pattern) {
|
|
45
|
+
const matcher = toRegExp(pattern);
|
|
46
|
+
return readdirSync(cwd, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).filter((name) => {
|
|
47
|
+
matcher.lastIndex = 0;
|
|
48
|
+
return matcher.test(name);
|
|
49
|
+
}).sort();
|
|
50
|
+
}
|
|
51
|
+
function getConfigFileCandidates(options = {}) {
|
|
52
|
+
const cwd = options.cwd ?? process.cwd();
|
|
53
|
+
const exactFiles = unique([
|
|
54
|
+
...options.fileName ? [options.fileName] : [],
|
|
55
|
+
...options.configFiles ?? []
|
|
56
|
+
]);
|
|
57
|
+
const patternFiles = options.configPattern ? findPatternMatches(cwd, options.configPattern) : [];
|
|
58
|
+
const candidates = unique([...exactFiles, ...patternFiles]);
|
|
59
|
+
if (candidates.length > 0) return candidates;
|
|
60
|
+
if (exactFiles.length > 0 || options.configPattern) return [];
|
|
61
|
+
return [LOCALGHOST_CONFIG_FILE];
|
|
62
|
+
}
|
|
63
|
+
function resolveDevHostsPath(options = {}) {
|
|
64
|
+
const cwd = options.cwd ?? process.cwd();
|
|
65
|
+
const searchedFiles = getConfigFileCandidates(options);
|
|
66
|
+
for (const fileName2 of searchedFiles) {
|
|
67
|
+
const path = resolve(cwd, fileName2);
|
|
68
|
+
if (existsSync(path)) {
|
|
69
|
+
return {
|
|
70
|
+
path,
|
|
71
|
+
fileName: basename(fileName2),
|
|
72
|
+
exists: true,
|
|
73
|
+
searchedFiles,
|
|
74
|
+
...options.configPattern ? { configPattern: options.configPattern } : {}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const fileName = searchedFiles[0] ?? LOCALGHOST_CONFIG_FILE;
|
|
79
|
+
return {
|
|
80
|
+
path: resolve(cwd, fileName),
|
|
81
|
+
fileName: basename(fileName),
|
|
82
|
+
exists: false,
|
|
83
|
+
searchedFiles,
|
|
84
|
+
...options.configPattern ? { configPattern: options.configPattern } : {}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function formatSearchedFiles(files, pattern) {
|
|
88
|
+
if (files.length > 0) return files.map((file) => `\`${file}\``).join(", ");
|
|
89
|
+
if (pattern) return `files matching ${pattern.toString()}`;
|
|
90
|
+
return `\`${LOCALGHOST_CONFIG_FILE}\``;
|
|
91
|
+
}
|
|
92
|
+
function readDevHosts(options = {}) {
|
|
93
|
+
const resolvedOptions = typeof options === "string" ? { cwd: options } : options;
|
|
94
|
+
const resolvedPath = resolveDevHostsPath(resolvedOptions);
|
|
95
|
+
if (!resolvedPath.exists) {
|
|
96
|
+
const cwd = resolvedOptions.cwd ?? process.cwd();
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Missing Localghost config in ${cwd}. Looked for ${formatSearchedFiles(resolvedPath.searchedFiles, resolvedPath.configPattern)}. Run \`localghost init\` or pass --config/--config-pattern.`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return parseDevHosts(readFileSync(resolvedPath.path, "utf8"), resolvedPath.fileName);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// src/vite.ts
|
|
105
|
+
function mergeAllowedHosts(current, hosts) {
|
|
106
|
+
if (Array.isArray(current)) {
|
|
107
|
+
return [.../* @__PURE__ */ new Set([...current, ...hosts])];
|
|
108
|
+
}
|
|
109
|
+
return hosts;
|
|
110
|
+
}
|
|
111
|
+
function getDisplayEntries(entries, vitePort) {
|
|
112
|
+
if (!vitePort) {
|
|
113
|
+
return entries;
|
|
114
|
+
}
|
|
115
|
+
const matchingEntries = entries.filter((entry) => entry.port === vitePort);
|
|
116
|
+
return matchingEntries.length > 0 ? matchingEntries : entries;
|
|
117
|
+
}
|
|
118
|
+
function printLocalHosts(server, entries, vitePort, https) {
|
|
119
|
+
const displayEntries = getDisplayEntries(entries, vitePort);
|
|
120
|
+
const protocol = https ? "https" : "http";
|
|
121
|
+
const urls = displayEntries.map((entry) => `${protocol}://${entry.host}/`);
|
|
122
|
+
const primaryUrl = urls[0];
|
|
123
|
+
if (!primaryUrl) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const lines = [
|
|
127
|
+
"",
|
|
128
|
+
" localghost",
|
|
129
|
+
` open: ${primaryUrl}`,
|
|
130
|
+
...urls.slice(1).map((url) => ` also: ${url}`),
|
|
131
|
+
vitePort ? ` target: http://127.0.0.1:${vitePort}/` : void 0,
|
|
132
|
+
https ? " proxy: Caddy local HTTPS" : void 0
|
|
133
|
+
].filter((line) => Boolean(line));
|
|
134
|
+
server.config.logger.info(lines.join("\n"), {
|
|
135
|
+
clear: false,
|
|
136
|
+
timestamp: false
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function readOptionsFromPlugin(options) {
|
|
140
|
+
return {
|
|
141
|
+
cwd: options.cwd ?? process.cwd(),
|
|
142
|
+
...options.fileName ? { fileName: options.fileName } : {},
|
|
143
|
+
...options.configFiles ? { configFiles: options.configFiles } : {},
|
|
144
|
+
...options.configPattern ? { configPattern: options.configPattern } : {}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function localGhostPlugin(options = {}) {
|
|
148
|
+
let resolvedEntries = [];
|
|
149
|
+
let resolvedVitePort;
|
|
150
|
+
return {
|
|
151
|
+
name: "localghost:vite",
|
|
152
|
+
enforce: "pre",
|
|
153
|
+
config(userConfig) {
|
|
154
|
+
const entries = readDevHosts(readOptionsFromPlugin(options));
|
|
155
|
+
const hosts = [...new Set(entries.map((entry) => entry.host))];
|
|
156
|
+
const existingServer = userConfig.server ?? {};
|
|
157
|
+
const vitePort = options.port ?? existingServer.port ?? entries.find((entry) => !entry.host.startsWith("api."))?.port ?? entries[0]?.port;
|
|
158
|
+
const primaryHost = options.primaryHost ?? entries.find((entry) => entry.port === vitePort)?.host ?? hosts[0];
|
|
159
|
+
resolvedEntries = entries;
|
|
160
|
+
resolvedVitePort = vitePort;
|
|
161
|
+
const server = {
|
|
162
|
+
...existingServer,
|
|
163
|
+
allowedHosts: mergeAllowedHosts(existingServer.allowedHosts, hosts),
|
|
164
|
+
strictPort: existingServer.strictPort ?? true
|
|
165
|
+
};
|
|
166
|
+
if (vitePort) {
|
|
167
|
+
server.port = vitePort;
|
|
168
|
+
}
|
|
169
|
+
if (options.https && primaryHost) {
|
|
170
|
+
const existingWs = typeof server.ws === "object" && server.ws ? server.ws : {};
|
|
171
|
+
const existingHmr = typeof existingServer.hmr === "object" && existingServer.hmr ? existingServer.hmr : {};
|
|
172
|
+
server.ws = {
|
|
173
|
+
...existingWs,
|
|
174
|
+
protocol: "wss",
|
|
175
|
+
host: primaryHost,
|
|
176
|
+
clientPort: 443
|
|
177
|
+
};
|
|
178
|
+
server.hmr = {
|
|
179
|
+
...existingHmr,
|
|
180
|
+
protocol: "wss",
|
|
181
|
+
host: primaryHost,
|
|
182
|
+
clientPort: 443
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return { server };
|
|
186
|
+
},
|
|
187
|
+
configureServer(server) {
|
|
188
|
+
if (options.log === false) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
server.httpServer?.once("listening", () => {
|
|
192
|
+
printLocalHosts(server, resolvedEntries, resolvedVitePort, Boolean(options.https));
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
var localHostsPlugin = localGhostPlugin;
|
|
198
|
+
export {
|
|
199
|
+
localGhostPlugin,
|
|
200
|
+
localHostsPlugin
|
|
201
|
+
};
|
|
202
|
+
//# sourceMappingURL=vite.js.map
|
package/dist/vite.js.map
ADDED
|
@@ -0,0 +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"]}
|
package/docs/brand.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Localghost Brand Guidelines
|
|
2
|
+
|
|
3
|
+
Localghost should feel like a tiny ghost-world utility: mysterious, goofy, magical, funny, and a little absurd. The vibe can nod toward playful platform games, spellbooks, hidden doors, and terminal tricks.
|
|
4
|
+
|
|
5
|
+
The guardrail: Localghost is still serious developer infrastructure. Copy must stay clear, exact, and trustworthy. The joke can wave hello, but the command must land.
|
|
6
|
+
|
|
7
|
+
## Voice
|
|
8
|
+
|
|
9
|
+
- Lead with the useful fact.
|
|
10
|
+
- Keep jokes short and optional.
|
|
11
|
+
- Make every error actionable.
|
|
12
|
+
- Use plain command names, paths, and file names.
|
|
13
|
+
- Treat system changes like serious business.
|
|
14
|
+
- Avoid spooky fog when the user needs a precise fix.
|
|
15
|
+
|
|
16
|
+
## Good Copy
|
|
17
|
+
|
|
18
|
+
```txt
|
|
19
|
+
Buh. Created .localghost
|
|
20
|
+
Caddy: missing
|
|
21
|
+
Run: brew install caddy
|
|
22
|
+
Localghost will not install it for you. No surprise spells.
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
```txt
|
|
26
|
+
Missing .localghost. Run `localghost init` or create .localghost.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```txt
|
|
30
|
+
Friendly names for local services.
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Avoid
|
|
34
|
+
|
|
35
|
+
```txt
|
|
36
|
+
The spirits demand a proxy before the portal can awaken.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Too much theme makes infrastructure feel untrustworthy. Keep the ghost world in the margins, not in the critical path.
|
|
40
|
+
|
|
41
|
+
## Copy Rule
|
|
42
|
+
|
|
43
|
+
Use the brand voice for greetings, empty states, docs headings, and small success messages. Use precise engineering language for permissions, `/etc/hosts`, Caddy, ports, HTTPS, package managers, and errors.
|
package/docs/flows.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Localghost User Flows
|
|
2
|
+
|
|
3
|
+
## Drop-In Install
|
|
4
|
+
|
|
5
|
+
As a developer, I want Localghost to be a drop-in dev dependency so I can add friendly local hostnames without rebuilding the same Caddy and hosts setup in every repo.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
yarn add -D @hamedb89/localghost
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Then initialize the project contract:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
yarn localghost init --write-scripts
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Project Contract
|
|
18
|
+
|
|
19
|
+
As a developer, I want one small file that explains the local domain map for this repo.
|
|
20
|
+
|
|
21
|
+
```txt
|
|
22
|
+
# .localghost
|
|
23
|
+
app.localhost 5173
|
|
24
|
+
www.app.localhost 5173
|
|
25
|
+
api.app.localhost 8787
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`.localghost` is the default config file. Repos can opt into another file name with `--config`, an ordered list of names, or a filename regex.
|
|
29
|
+
|
|
30
|
+
## Machine Readiness
|
|
31
|
+
|
|
32
|
+
As a developer, I want to know whether my laptop is ready before Localghost changes system files.
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
yarn localghost doctor
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Localghost checks for Caddy and prints the exact install command when it is missing. It does not run Homebrew automatically.
|
|
39
|
+
|
|
40
|
+
## Update Awareness
|
|
41
|
+
|
|
42
|
+
As a developer, I want to know when Localghost itself is stale without paying for a network check on every run.
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
yarn localghost update
|
|
46
|
+
LOCALGHOST_NO_UPDATE_CHECK=1 yarn localghost doctor
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Localghost checks npm after successful commands, caches the result for 24 hours, and ignores check failures. `LOCALGHOST_NO_UPDATE_CHECK=1` and `--no-update-check` disable the automatic check.
|
|
50
|
+
|
|
51
|
+
## One-Time Setup
|
|
52
|
+
|
|
53
|
+
As a developer, I want one explicit setup command that updates only the managed Localghost block in `/etc/hosts` and validates Caddy.
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
yarn localghost:setup
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Daily Dev
|
|
60
|
+
|
|
61
|
+
As a developer, I want a daily command that starts the local HTTPS proxy from the same config file.
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
yarn localghost:proxy
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Most repos will run this next to their app server, for example Vite on `127.0.0.1:5173`.
|
|
68
|
+
|
|
69
|
+
## Config Discovery
|
|
70
|
+
|
|
71
|
+
As a developer, I want Localghost to fit repos that already have naming conventions without hidden file searches.
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
yarn localghost print --config .localghost.preview
|
|
75
|
+
yarn localghost print --config .localghost.private --config .localghost
|
|
76
|
+
yarn localghost print --config-pattern '^\.localghost\.(private|preview)$'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Localghost uses the first existing configured file. Regex discovery scans filenames in the project root.
|
|
80
|
+
|
|
81
|
+
## Domain Routing
|
|
82
|
+
|
|
83
|
+
As a developer, I want to see the local domain layer as a simple `domain -> upstream` map.
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
yarn localghost routes
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```txt
|
|
90
|
+
localghost routes
|
|
91
|
+
https://app.localhost/ -> http://127.0.0.1:5173
|
|
92
|
+
https://api.app.localhost/ -> http://127.0.0.1:8787
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`setup` and `dev` print this same map before Caddy is validated or run.
|
|
96
|
+
|
|
97
|
+
## Vite Integration
|
|
98
|
+
|
|
99
|
+
As a Vite user, I want Localghost to set strict `allowedHosts` and print the browser-facing HTTPS URLs.
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
import { localGhostPlugin } from "@hamedb89/localghost/vite";
|
|
103
|
+
|
|
104
|
+
export default {
|
|
105
|
+
plugins: [localGhostPlugin({ port: 5173, https: true })]
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Teardown
|
|
110
|
+
|
|
111
|
+
As a developer, I want to cleanly remove Localghost from a project when the repo is archived or no longer needs friendly hostnames.
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
yarn localghost teardown
|
|
115
|
+
yarn localghost teardown --remove-caddyfile
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`teardown` removes only the Localghost managed `/etc/hosts` block. The generated Caddyfile is kept unless `--remove-caddyfile` is passed.
|
|
119
|
+
|
|
120
|
+
## State Tracking
|
|
121
|
+
|
|
122
|
+
As a developer or agent, I want to see what Localghost changed without reading system files directly.
|
|
123
|
+
|
|
124
|
+
```sh
|
|
125
|
+
yarn localghost status
|
|
126
|
+
yarn localghost status --json
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Localghost records setup and teardown in `ops/local/localghost-state.json`. That file is project-local state, not OS temp state.
|
|
130
|
+
|
|
131
|
+
## Agent-Friendly Tools
|
|
132
|
+
|
|
133
|
+
As a Codex or agent user, I want commands that are inspectable and scriptable without opening a browser.
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
yarn localghost print
|
|
137
|
+
yarn localghost doctor
|
|
138
|
+
yarn localghost update
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The CLI reference lives in [localghost(1)](./localghost.1.md). Future flows can add MCP helpers and repo templates, but the base package should remain a small, predictable CLI.
|