@hamedb89/localghost 0.1.0 → 0.1.6

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/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":";AAAA,SAAS,YAAY,cAAc,mBAAmB;AACtD,SAAS,UAAU,MAAM,eAAe;;;ACKxC,IAAM,eAAe;AAEd,SAAS,cAAc,OAAe,WAAW,eAA+B;AACrF,QAAM,UAA0B,CAAC;AAEjC,QAAM,MAAM,OAAO,EAAE,QAAQ,CAAC,SAAS,UAAU;AAC/C,UAAM,OAAO,QAAQ,QAAQ,OAAO,EAAE,EAAE,KAAK;AAE7C,QAAI,CAAC,MAAM;AACT;AAAA,IACF;AAEA,UAAM,QAAQ,KAAK,MAAM,KAAK;AAC9B,UAAM,OAAO,MAAM,CAAC;AACpB,UAAM,UAAU,MAAM,CAAC;AAEvB,QAAI,CAAC,QAAQ,CAAC,WAAW,MAAM,SAAS,GAAG;AACzC,YAAM,IAAI,MAAM,WAAW,QAAQ,SAAS,QAAQ,CAAC,MAAM,OAAO,GAAG;AAAA,IACvE;AAEA,QAAI,CAAC,aAAa,KAAK,IAAI,GAAG;AAC5B,YAAM,IAAI,MAAM,wBAAwB,QAAQ,CAAC,MAAM,IAAI,GAAG;AAAA,IAChE;AAEA,UAAM,OAAO,OAAO,OAAO;AAE3B,QAAI,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,KAAK,OAAO,OAAO;AACvD,YAAM,IAAI,MAAM,wBAAwB,QAAQ,CAAC,MAAM,OAAO,GAAG;AAAA,IACnE;AAEA,YAAQ,KAAK;AAAA,MACX,MAAM,KAAK,YAAY,EAAE,QAAQ,OAAO,EAAE;AAAA,MAC1C;AAAA,MACA,QAAQ,aAAa,IAAI;AAAA,IAC3B,CAAC;AAAA,EACH,CAAC;AAED,SAAO;AACT;;;ADxCO,IAAM,yBAAyB;AAmBtC,SAAS,OAAO,QAAkB;AAChC,SAAO,CAAC,GAAG,IAAI,IAAI,OAAO,OAAO,OAAO,CAAC,CAAC;AAC5C;AAEA,SAAS,SAAS,SAAwB;AACxC,SAAO,OAAO,YAAY,WAAW,IAAI,OAAO,OAAO,IAAI;AAC7D;AAEA,SAAS,mBAAmB,KAAa,SAAwB;AAC/D,QAAM,UAAU,SAAS,OAAO;AAEhC,SAAO,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,EAC5C,OAAO,CAAC,UAAU,MAAM,OAAO,CAAC,EAChC,IAAI,CAAC,UAAU,MAAM,IAAI,EACzB,OAAO,CAAC,SAAS;AAChB,YAAQ,YAAY;AACpB,WAAO,QAAQ,KAAK,IAAI;AAAA,EAC1B,CAAC,EACA,KAAK;AACV;AAEO,SAAS,wBAAwB,UAA+B,CAAC,GAAG;AACzE,QAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AACvC,QAAM,aAAa,OAAO;AAAA,IACxB,GAAI,QAAQ,WAAW,CAAC,QAAQ,QAAQ,IAAI,CAAC;AAAA,IAC7C,GAAI,QAAQ,eAAe,CAAC;AAAA,EAC9B,CAAC;AACD,QAAM,eAAe,QAAQ,gBAAgB,mBAAmB,KAAK,QAAQ,aAAa,IAAI,CAAC;AAC/F,QAAM,aAAa,OAAO,CAAC,GAAG,YAAY,GAAG,YAAY,CAAC;AAE1D,MAAI,WAAW,SAAS,EAAG,QAAO;AAClC,MAAI,WAAW,SAAS,KAAK,QAAQ,cAAe,QAAO,CAAC;AAC5D,SAAO,CAAC,sBAAsB;AAChC;AAEO,SAAS,oBAAoB,UAA+B,CAAC,GAAyB;AAC3F,QAAM,MAAM,QAAQ,OAAO,QAAQ,IAAI;AACvC,QAAM,gBAAgB,wBAAwB,OAAO;AAErD,aAAWA,aAAY,eAAe;AACpC,UAAM,OAAO,QAAQ,KAAKA,SAAQ;AAClC,QAAI,WAAW,IAAI,GAAG;AACpB,aAAO;AAAA,QACL;AAAA,QACA,UAAU,SAASA,SAAQ;AAAA,QAC3B,QAAQ;AAAA,QACR;AAAA,QACA,GAAI,QAAQ,gBAAgB,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;AAAA,MAC1E;AAAA,IACF;AAAA,EACF;AAEA,QAAM,WAAW,cAAc,CAAC,KAAK;AAErC,SAAO;AAAA,IACL,MAAM,QAAQ,KAAK,QAAQ;AAAA,IAC3B,UAAU,SAAS,QAAQ;AAAA,IAC3B,QAAQ;AAAA,IACR;AAAA,IACA,GAAI,QAAQ,gBAAgB,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;AAAA,EAC1E;AACF;AAMA,SAAS,oBAAoB,OAAiB,SAAyB;AACrE,MAAI,MAAM,SAAS,EAAG,QAAO,MAAM,IAAI,CAAC,SAAS,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI;AACzE,MAAI,QAAS,QAAO,kBAAkB,QAAQ,SAAS,CAAC;AACxD,SAAO,KAAK,sBAAsB;AACpC;AAEO,SAAS,aAAa,UAAwC,CAAC,GAAG;AACvE,QAAM,kBAAkB,OAAO,YAAY,WAAW,EAAE,KAAK,QAAQ,IAAI;AACzE,QAAM,eAAe,oBAAoB,eAAe;AAExD,MAAI,CAAC,aAAa,QAAQ;AACxB,UAAM,MAAM,gBAAgB,OAAO,QAAQ,IAAI;AAC/C,UAAM,IAAI;AAAA,MACR,gCAAgC,GAAG,gBAAgB,oBAAoB,aAAa,eAAe,aAAa,aAAa,CAAC;AAAA,IAChI;AAAA,EACF;AAEA,SAAO,cAAc,aAAa,aAAa,MAAM,MAAM,GAAG,aAAa,QAAQ;AACrF;;;AE3FA,SAAS,kBAAkB,SAAwC,OAAiB;AAClF,MAAI,MAAM,QAAQ,OAAO,GAAG;AAC1B,WAAO,CAAC,GAAG,oBAAI,IAAI,CAAC,GAAG,SAAS,GAAG,KAAK,CAAC,CAAC;AAAA,EAC5C;AAEA,SAAO;AACT;AAEA,SAAS,kBAAkB,SAAyB,UAA8B;AAChF,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,EACT;AAEA,QAAM,kBAAkB,QAAQ,OAAO,CAAC,UAAU,MAAM,SAAS,QAAQ;AACzE,SAAO,gBAAgB,SAAS,IAAI,kBAAkB;AACxD;AAEA,SAAS,gBAAgB,QAAuB,SAAyB,UAA8B,OAAgB;AACrH,QAAM,iBAAiB,kBAAkB,SAAS,QAAQ;AAC1D,QAAM,WAAW,QAAQ,UAAU;AACnC,QAAM,OAAO,eAAe,IAAI,CAAC,UAAU,GAAG,QAAQ,MAAM,MAAM,IAAI,GAAG;AACzE,QAAM,aAAa,KAAK,CAAC;AAEzB,MAAI,CAAC,YAAY;AACf;AAAA,EACF;AAEA,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,aAAa,UAAU;AAAA,IACvB,GAAG,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,aAAa,GAAG,EAAE;AAAA,IAChD,WAAW,8BAA8B,QAAQ,MAAM;AAAA,IACvD,QAAQ,gCAAgC;AAAA,EAC1C,EAAE,OAAO,CAAC,SAAyB,QAAQ,IAAI,CAAC;AAEhD,SAAO,OAAO,OAAO,KAAK,MAAM,KAAK,IAAI,GAAG;AAAA,IAC1C,OAAO;AAAA,IACP,WAAW;AAAA,EACb,CAAC;AACH;AAEA,SAAS,sBAAsB,SAAuD;AACpF,SAAO;AAAA,IACL,KAAK,QAAQ,OAAO,QAAQ,IAAI;AAAA,IAChC,GAAI,QAAQ,WAAW,EAAE,UAAU,QAAQ,SAAS,IAAI,CAAC;AAAA,IACzD,GAAI,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC;AAAA,IAClE,GAAI,QAAQ,gBAAgB,EAAE,eAAe,QAAQ,cAAc,IAAI,CAAC;AAAA,EAC1E;AACF;AAEO,SAAS,iBAAiB,UAAmC,CAAC,GAAW;AAC9E,MAAI,kBAAkC,CAAC;AACvC,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,SAAS;AAAA,IAET,OAAO,YAAwB;AAC7B,YAAM,UAAU,aAAa,sBAAsB,OAAO,CAAC;AAC3D,YAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,QAAQ,IAAI,CAAC,UAAU,MAAM,IAAI,CAAC,CAAC;AAC7D,YAAM,iBAAiB,WAAW,UAAU,CAAC;AAC7C,YAAM,WACJ,QAAQ,QACR,eAAe,QACf,QAAQ,KAAK,CAAC,UAAU,CAAC,MAAM,KAAK,WAAW,MAAM,CAAC,GAAG,QACzD,QAAQ,CAAC,GAAG;AACd,YAAM,cACJ,QAAQ,eACR,QAAQ,KAAK,CAAC,UAAU,MAAM,SAAS,QAAQ,GAAG,QAClD,MAAM,CAAC;AAET,wBAAkB;AAClB,yBAAmB;AAEnB,YAAM,SAAwB;AAAA,QAC5B,GAAG;AAAA,QACH,cAAc,kBAAkB,eAAe,cAAc,KAAK;AAAA,QAClE,YAAY,eAAe,cAAc;AAAA,MAC3C;AAEA,UAAI,UAAU;AACZ,eAAO,OAAO;AAAA,MAChB;AAEA,UAAI,QAAQ,SAAS,aAAa;AAChC,cAAM,aAAa,OAAO,OAAO,OAAO,YAAY,OAAO,KAAK,OAAO,KAAK,CAAC;AAC7E,cAAM,cAAc,OAAO,eAAe,QAAQ,YAAY,eAAe,MAAM,eAAe,MAAM,CAAC;AAEzG,eAAO,KAAK;AAAA,UACV,GAAG;AAAA,UACH,UAAU;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,QACd;AAEA,eAAO,MAAM;AAAA,UACX,GAAG;AAAA,UACH,UAAU;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,QACd;AAAA,MACF;AAEA,aAAO,EAAE,OAAO;AAAA,IAClB;AAAA,IAEA,gBAAgB,QAAQ;AACtB,UAAI,QAAQ,QAAQ,OAAO;AACzB;AAAA,MACF;AAEA,aAAO,YAAY,KAAK,aAAa,MAAM;AACzC,wBAAgB,QAAQ,iBAAiB,kBAAkB,QAAQ,QAAQ,KAAK,CAAC;AAAA,MACnF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEO,IAAM,mBAAmB;","names":["fileName"]}
1
+ {"version":3,"sources":["../src/vite.ts","../src/config.ts","../src/parse.ts","../src/port.ts","../src/context.ts","../src/doctor.ts","../src/env.ts","../src/fs.ts","../src/hosts-file.ts","../src/prompt.ts","../src/state.ts","../src/caddy.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { normalize, resolve } from \"node:path\";\nimport type { ConfigEnv, HmrOptions, Plugin, UserConfig, ViteDevServer, WsOptions } from \"vite\";\nimport {\n getConfigFileCandidates,\n getProjectName,\n resolveDevHostsPath,\n sanitizeProjectName,\n type ConfigPattern,\n type ReadDevHostsOptions\n} from \"./config.js\";\nimport { resolveLocalghostContext, type LocalghostContext } from \"./context.js\";\nimport { checkCaddy } from \"./doctor.js\";\nimport { isProductionLike } from \"./env.js\";\nimport { writeTextFile } from \"./fs.js\";\nimport { getSystemHostsPath, renderHostsBlock, updateSystemHosts } from \"./hosts-file.js\";\nimport { ask, canPrompt, confirm } from \"./prompt.js\";\nimport { getLocalghostStatePath, readLocalghostState, writeLocalghostState } from \"./state.js\";\nimport { getCaddyfilePath, renderCaddyfile, validateCaddyfile, writeCaddyfile } from \"./caddy.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type LocalGhostPluginOptions = {\n cwd?: string;\n fileName?: string;\n configFiles?: string[];\n configPattern?: ConfigPattern;\n port?: number;\n https?: boolean;\n bindHost?: string | boolean;\n dynamicPort?: boolean;\n primaryHost?: string;\n log?: boolean;\n setup?: boolean | \"prompt\";\n};\n\ntype ServerOptions = NonNullable<UserConfig[\"server\"]>;\n\nfunction mergeAllowedHosts(current: ServerOptions[\"allowedHosts\"], hosts: string[]) {\n if (Array.isArray(current)) {\n return [...new Set([...current, ...hosts])];\n }\n\n return hosts;\n}\n\nfunction getDisplayEntries(entries: DevHostEntry[], vitePort: number | undefined) {\n if (!vitePort) {\n return entries;\n }\n\n const matchingEntries = entries.filter((entry) => entry.port === vitePort);\n return matchingEntries.length > 0 ? matchingEntries : entries;\n}\n\nfunction printLocalHosts(server: ViteDevServer, entries: DevHostEntry[], vitePort: number | undefined, https: boolean) {\n const displayEntries = getDisplayEntries(entries, vitePort);\n const protocol = https ? \"https\" : \"http\";\n const urls = displayEntries.map((entry) => `${protocol}://${entry.host}/`);\n const primaryUrl = urls[0];\n\n if (!primaryUrl) {\n return;\n }\n\n const lines = [\n \"\",\n \" localghost\",\n ` local: ${primaryUrl}`,\n ...urls.slice(1).map((url) => ` also: ${url}`),\n vitePort ? ` target: http://127.0.0.1:${vitePort}/` : undefined,\n https ? \" proxy: Caddy local HTTPS\" : undefined\n ].filter((line): line is string => Boolean(line));\n\n server.config.logger.info(lines.join(\"\\n\"), {\n clear: false,\n timestamp: false\n });\n}\n\nfunction readOptionsFromPlugin(options: LocalGhostPluginOptions): ReadDevHostsOptions {\n return {\n cwd: options.cwd ?? process.cwd(),\n ...(options.fileName ? { fileName: options.fileName } : {}),\n ...(options.configFiles ? { configFiles: options.configFiles } : {}),\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n}\n\nfunction getConfigWatchFiles(options: LocalGhostPluginOptions) {\n const readOptions = readOptionsFromPlugin(options);\n const cwd = readOptions.cwd ?? process.cwd();\n const resolvedPath = resolveDevHostsPath(readOptions);\n const candidatePaths = getConfigFileCandidates(readOptions).map((fileName) => resolve(cwd, fileName));\n\n return [...new Set([...candidatePaths, resolvedPath.path])];\n}\n\nfunction normalizeWatchPath(filePath: string) {\n return normalize(resolve(filePath));\n}\n\nfunction renderConfig(hosts: string[], port: number) {\n return [\n \"# Buh. Friendly names for local services.\",\n \"# Format: <host> <port>\",\n ...hosts.map((host) => `${host} ${port}`),\n \"\"\n ].join(\"\\n\");\n}\n\nfunction defaultHost(cwd: string) {\n const projectName = sanitizeProjectName(getProjectName(cwd).split(\"/\").pop() ?? \"app\");\n return `${projectName}.localhost`;\n}\n\nasync function promptForHosts(cwd: string, port: number) {\n const primaryHost = await ask(\"Primary local domain\", defaultHost(cwd));\n const hosts = [primaryHost.toLowerCase()];\n\n while (await confirm(\"Add another local domain?\", false)) {\n const host = await ask(\"Domain\");\n if (host) hosts.push(host.toLowerCase());\n }\n\n return [...new Set(hosts)];\n}\n\nfunction hasReadySetup(cwd: string, entries: DevHostEntry[], configPath: string, https: boolean) {\n const state = readLocalghostState(cwd);\n const projectName = sanitizeProjectName(getProjectName(cwd));\n if (state?.action !== \"setup\" || state.configPath !== configPath) return false;\n\n try {\n const hosts = readFileSync(getSystemHostsPath(), \"utf8\");\n if (!hosts.includes(renderHostsBlock(projectName, entries).trimEnd())) return false;\n } catch {\n return false;\n }\n\n const caddyfilePath = getCaddyfilePath(cwd);\n return existsSync(caddyfilePath) && readFileSync(caddyfilePath, \"utf8\") === renderCaddyfile(entries, { https });\n}\n\nasync function setupProject(cwd: string, entries: DevHostEntry[], configPath: string, https: boolean) {\n const caddy = await checkCaddy();\n if (!caddy.found) {\n throw new Error([\n \"Caddy is missing.\",\n `Run: ${caddy.installHint}`,\n \"Localghost will not install it for you.\"\n ].join(\"\\n\"));\n }\n\n const projectName = sanitizeProjectName(getProjectName(cwd));\n console.log(\"Buh. macOS keeps local hostnames in /etc/hosts, so Localghost may ask for your password.\");\n console.log(\"It will only touch its managed Localghost block.\");\n const hostsResult = await updateSystemHosts(projectName, entries);\n const caddyfilePath = await writeCaddyfile(entries, cwd, { https });\n await validateCaddyfile(caddyfilePath);\n writeLocalghostState(cwd, {\n action: \"setup\",\n projectName,\n cwd,\n configPath,\n hostsPath: hostsResult.hostsPath,\n hostsChanged: hostsResult.changed,\n ...(hostsResult.tempPath ? { hostsTempPath: hostsResult.tempPath } : {}),\n caddyfilePath,\n caddyHttps: https,\n entries\n });\n}\n\nasync function ensureLocalghostContext(options: LocalGhostPluginOptions, vitePort: number, https: boolean) {\n const cwd = options.cwd ?? process.cwd();\n const readOptions = readOptionsFromPlugin(options);\n const resolved = resolveDevHostsPath(readOptions);\n\n if (!resolved.exists) {\n if (options.setup === false || !canPrompt()) {\n throw new Error(\n `No .localghost found at ${resolved.path}. Run \\`localghost init --write-scripts\\` or start Vite in an interactive terminal.`\n );\n }\n\n console.log(`No .localghost found at ${resolved.path}.`);\n if (!(await confirm(\"Create one now?\", true))) {\n throw new Error(\"Localghost setup skipped. Create .localghost before running the Vite plugin.\");\n }\n\n const hosts = await promptForHosts(cwd, vitePort);\n writeTextFile(resolved.path, renderConfig(hosts, vitePort));\n console.log(`Created ${resolved.path}`);\n }\n\n const context = await resolveLocalghostContext({\n ...options,\n cwd,\n port: vitePort,\n https\n });\n\n if (!hasReadySetup(cwd, context.entries, resolved.path, https)) {\n if (options.setup === false || !canPrompt()) return context;\n\n const setup = await confirm(\"Run caddy:setup now?\", true);\n if (setup) {\n await setupProject(cwd, context.entries, resolved.path, https);\n console.log(`All set. Setup state: ${getLocalghostStatePath(cwd)}`);\n }\n }\n\n return context;\n}\n\nexport function localGhostPlugin(options: LocalGhostPluginOptions = {}): Plugin {\n let resolvedEntries: DevHostEntry[] = [];\n let resolvedVitePort: number | undefined;\n let restartTimer: NodeJS.Timeout | undefined;\n\n return {\n name: \"localghost:vite\",\n enforce: \"pre\",\n\n async config(userConfig, configEnv: ConfigEnv): Promise<UserConfig> {\n if (configEnv.command !== \"serve\" || configEnv.mode === \"production\" || isProductionLike()) {\n return {};\n }\n\n const existingServer = userConfig.server ?? {};\n const envVitePort = Number.parseInt(process.env.LOCALGHOST_PORT ?? process.env.VITE_PORT ?? \"\", 10);\n const requestedVitePort =\n options.port ??\n existingServer.port ??\n (Number.isInteger(envVitePort) ? envVitePort : 5173);\n const context: LocalghostContext = await ensureLocalghostContext(options, requestedVitePort, Boolean(options.https));\n const entries = context.entries;\n const hosts = context.hosts;\n const primaryHost = context.primaryHost;\n\n resolvedEntries = entries;\n resolvedVitePort = context.port;\n\n const server: ServerOptions = {\n ...existingServer,\n allowedHosts: mergeAllowedHosts(existingServer.allowedHosts, hosts),\n strictPort: existingServer.strictPort ?? true\n };\n\n if (typeof existingServer.host === \"undefined\") {\n server.host = context.bindHost;\n }\n\n if (context.port) {\n server.port = context.port;\n }\n\n if (options.https && primaryHost) {\n const existingWs = typeof server.ws === \"object\" && server.ws ? server.ws : {};\n const existingHmr = typeof existingServer.hmr === \"object\" && existingServer.hmr ? existingServer.hmr : {};\n\n server.ws = {\n ...existingWs,\n protocol: \"wss\",\n host: primaryHost,\n clientPort: 443\n } satisfies WsOptions;\n\n server.hmr = {\n ...existingHmr,\n protocol: \"wss\",\n host: primaryHost,\n clientPort: 443\n } satisfies HmrOptions;\n }\n\n return { server };\n },\n\n configureServer(server) {\n const watchFiles = getConfigWatchFiles(options);\n const watchedConfigFiles = new Set(watchFiles.map(normalizeWatchPath));\n\n server.watcher.add(watchFiles);\n\n const restartOnLocalghostConfigChange = (filePath: string) => {\n if (!watchedConfigFiles.has(normalizeWatchPath(filePath))) {\n return;\n }\n\n if (restartTimer) {\n clearTimeout(restartTimer);\n }\n\n restartTimer = setTimeout(() => {\n if (options.log !== false) {\n server.config.logger.info(\"localghost config changed; restarting Vite dev server\", {\n clear: false,\n timestamp: false\n });\n }\n\n void server.restart().catch((error: unknown) => {\n server.config.logger.error(error instanceof Error ? error.message : String(error), {\n timestamp: false\n });\n });\n }, 50);\n };\n\n server.watcher.on(\"add\", restartOnLocalghostConfigChange);\n server.watcher.on(\"change\", restartOnLocalghostConfigChange);\n server.watcher.on(\"unlink\", restartOnLocalghostConfigChange);\n\n if (options.log !== false) {\n server.printUrls = () => {\n printLocalHosts(server, resolvedEntries, resolvedVitePort, Boolean(options.https));\n };\n }\n }\n };\n}\n\nexport const localHostsPlugin = localGhostPlugin;\nexport type LocalHostsPluginOptions = LocalGhostPluginOptions;\n","import { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { basename, join, resolve } from \"node:path\";\nimport { parseDevHosts } from \"./parse.js\";\n\nexport const LOCALGHOST_CONFIG_FILE = \".localghost\";\n\nexport type ConfigPattern = string | RegExp;\n\nexport type ReadDevHostsOptions = {\n cwd?: string;\n fileName?: string;\n configFiles?: string[];\n configPattern?: ConfigPattern;\n};\n\nexport type ResolvedDevHostsPath = {\n path: string;\n fileName: string;\n exists: boolean;\n searchedFiles: string[];\n configPattern?: ConfigPattern;\n};\n\nfunction unique(values: string[]) {\n return [...new Set(values.filter(Boolean))];\n}\n\nfunction toRegExp(pattern: ConfigPattern) {\n return typeof pattern === \"string\" ? new RegExp(pattern) : pattern;\n}\n\nfunction findPatternMatches(cwd: string, pattern: ConfigPattern) {\n const matcher = toRegExp(pattern);\n\n return readdirSync(cwd, { withFileTypes: true })\n .filter((entry) => entry.isFile())\n .map((entry) => entry.name)\n .filter((name) => {\n matcher.lastIndex = 0;\n return matcher.test(name);\n })\n .sort();\n}\n\nexport function getConfigFileCandidates(options: ReadDevHostsOptions = {}) {\n const cwd = options.cwd ?? process.cwd();\n const exactFiles = unique([\n ...(options.fileName ? [options.fileName] : []),\n ...(options.configFiles ?? [])\n ]);\n const patternFiles = options.configPattern ? findPatternMatches(cwd, options.configPattern) : [];\n const candidates = unique([...exactFiles, ...patternFiles]);\n\n if (candidates.length > 0) return candidates;\n if (exactFiles.length > 0 || options.configPattern) return [];\n return [LOCALGHOST_CONFIG_FILE];\n}\n\nexport function resolveDevHostsPath(options: ReadDevHostsOptions = {}): ResolvedDevHostsPath {\n const cwd = options.cwd ?? process.cwd();\n const searchedFiles = getConfigFileCandidates(options);\n\n for (const fileName of searchedFiles) {\n const path = resolve(cwd, fileName);\n if (existsSync(path)) {\n return {\n path,\n fileName: basename(fileName),\n exists: true,\n searchedFiles,\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n }\n }\n\n const fileName = searchedFiles[0] ?? LOCALGHOST_CONFIG_FILE;\n\n return {\n path: resolve(cwd, fileName),\n fileName: basename(fileName),\n exists: false,\n searchedFiles,\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n}\n\nexport function getDevHostsPath(options: ReadDevHostsOptions = {}) {\n return resolveDevHostsPath(options).path;\n}\n\nfunction formatSearchedFiles(files: string[], pattern?: ConfigPattern) {\n if (files.length > 0) return files.map((file) => `\\`${file}\\``).join(\", \");\n if (pattern) return `files matching ${pattern.toString()}`;\n return `\\`${LOCALGHOST_CONFIG_FILE}\\``;\n}\n\nexport function readDevHosts(options: ReadDevHostsOptions | string = {}) {\n const resolvedOptions = typeof options === \"string\" ? { cwd: options } : options;\n const resolvedPath = resolveDevHostsPath(resolvedOptions);\n\n if (!resolvedPath.exists) {\n const cwd = resolvedOptions.cwd ?? process.cwd();\n throw new Error(\n `Missing Localghost config in ${cwd}. Looked for ${formatSearchedFiles(resolvedPath.searchedFiles, resolvedPath.configPattern)}. Run \\`localghost init\\` or pass --config/--config-pattern.`\n );\n }\n\n return parseDevHosts(readFileSync(resolvedPath.path, \"utf8\"), resolvedPath.fileName);\n}\n\nexport function getProjectName(cwd = process.cwd()) {\n try {\n const pkg = JSON.parse(readFileSync(join(cwd, \"package.json\"), \"utf8\")) as { name?: unknown };\n const name = typeof pkg.name === \"string\" && pkg.name ? pkg.name : \"app\";\n return sanitizeProjectName(name.replace(/^@/, \"\"));\n } catch {\n return \"app\";\n }\n}\n\nexport function sanitizeProjectName(value: string) {\n const projectName = value.replace(/[^\\w.-]+/g, \"-\").replace(/^-+|-+$/g, \"\");\n return projectName || \"app\";\n}\n","export type DevHostEntry = {\n host: string;\n port: number;\n target: string;\n};\n\nconst HOST_PATTERN = /^(?=.{1,253}$)(?!-)[a-z0-9-]+(\\.[a-z0-9-]+)*\\.?$/i;\n\nexport function parseDevHosts(input: string, fileName = \".localghost\"): DevHostEntry[] {\n const entries: DevHostEntry[] = [];\n\n input.split(/\\r?\\n/).forEach((rawLine, index) => {\n const line = rawLine.replace(/#.*/, \"\").trim();\n\n if (!line) {\n return;\n }\n\n const parts = line.split(/\\s+/);\n const host = parts[0];\n const portRaw = parts[1];\n\n if (!host || !portRaw || parts.length > 2) {\n throw new Error(`Invalid ${fileName} line ${index + 1}: \"${rawLine}\"`);\n }\n\n if (!HOST_PATTERN.test(host)) {\n throw new Error(`Invalid host on line ${index + 1}: \"${host}\"`);\n }\n\n const port = Number(portRaw);\n\n if (!Number.isInteger(port) || port < 1 || port > 65535) {\n throw new Error(`Invalid port on line ${index + 1}: \"${portRaw}\"`);\n }\n\n entries.push({\n host: host.toLowerCase().replace(/\\.$/, \"\"),\n port,\n target: `127.0.0.1:${port}`\n });\n });\n\n return entries;\n}\n\nexport function findLocalMdnsHosts(entries: DevHostEntry[]): string[] {\n return [...new Set(entries.map((entry) => entry.host).filter((host) => host.endsWith(\".local\")))];\n}\n","import { createServer } from \"node:net\";\n\nexport type FindAvailablePortOptions = {\n host?: string;\n maxAttempts?: number;\n};\n\nexport async function isPortAvailable(port: number, host = \"127.0.0.1\") {\n return new Promise<boolean>((resolve) => {\n const server = createServer();\n\n server.once(\"error\", () => {\n resolve(false);\n });\n\n server.once(\"listening\", () => {\n server.close(() => resolve(true));\n });\n\n server.listen(port, host);\n });\n}\n\nexport async function findAvailablePort(startPort: number, options: FindAvailablePortOptions = {}) {\n const host = options.host ?? \"127.0.0.1\";\n const maxAttempts = options.maxAttempts ?? 50;\n\n for (let offset = 0; offset < maxAttempts; offset += 1) {\n const port = startPort + offset;\n if (await isPortAvailable(port, host)) {\n return port;\n }\n }\n\n throw new Error(`No available port found from ${startPort} to ${startPort + maxAttempts - 1}.`);\n}\n","import {\n getProjectName,\n readDevHosts,\n resolveDevHostsPath,\n sanitizeProjectName,\n type ConfigPattern,\n type ReadDevHostsOptions\n} from \"./config.js\";\nimport { findAvailablePort } from \"./port.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type LocalghostContextOptions = {\n cwd?: string;\n project?: string;\n fileName?: string;\n configFiles?: string[];\n configPattern?: ConfigPattern;\n port?: number;\n https?: boolean;\n bindHost?: string | boolean;\n primaryHost?: string;\n dynamicPort?: boolean;\n};\n\nexport type LocalghostContext = {\n cwd: string;\n projectName: string;\n readOptions: ReadDevHostsOptions;\n configPath: string;\n configFileName: string;\n configEntries: DevHostEntry[];\n entries: DevHostEntry[];\n hosts: string[];\n requestedPort: number;\n port: number;\n dynamicPort: boolean;\n bindHost: string | boolean;\n primaryHost: string;\n https: boolean;\n};\n\nfunction parsePort(value: string | undefined) {\n if (!value) return undefined;\n const port = Number.parseInt(value, 10);\n return Number.isInteger(port) && port > 0 && port <= 65535 ? port : undefined;\n}\n\nfunction envPort() {\n return parsePort(process.env.LOCALGHOST_PORT) ?? parsePort(process.env.VITE_PORT);\n}\n\nfunction envDynamicPort() {\n const value = process.env.LOCALGHOST_DYNAMIC_PORT;\n if (!value) return undefined;\n return [\"1\", \"true\", \"yes\", \"on\"].includes(value.toLowerCase());\n}\n\nfunction readOptionsFromContext(options: LocalghostContextOptions): ReadDevHostsOptions {\n return {\n cwd: options.cwd ?? process.cwd(),\n ...(options.fileName ? { fileName: options.fileName } : {}),\n ...(options.configFiles ? { configFiles: options.configFiles } : {}),\n ...(options.configPattern ? { configPattern: options.configPattern } : {})\n };\n}\n\nfunction withRuntimePort(entries: DevHostEntry[], requestedPort: number, port: number) {\n if (requestedPort === port) return entries;\n\n const hasRequestedPort = entries.some((entry) => entry.port === requestedPort);\n if (!hasRequestedPort) return entries;\n\n return entries.map((entry) => (entry.port === requestedPort ? { ...entry, port } : entry));\n}\n\nfunction uniqueHosts(entries: DevHostEntry[]) {\n return [...new Set(entries.map((entry) => entry.host))];\n}\n\nexport function defineLocalghostConfig<T extends LocalghostContextOptions>(config: T) {\n return config;\n}\n\nexport async function resolveLocalghostContext(options: LocalghostContextOptions = {}): Promise<LocalghostContext> {\n const cwd = options.cwd ?? process.cwd();\n const readOptions = readOptionsFromContext({ ...options, cwd });\n const resolvedPath = resolveDevHostsPath(readOptions);\n const configEntries = readDevHosts(readOptions);\n const requestedPort = options.port ?? envPort() ?? configEntries[0]?.port ?? 5173;\n const dynamicPort = options.dynamicPort ?? envDynamicPort() ?? false;\n const bindHost = options.bindHost ?? \"127.0.0.1\";\n const probeHost = typeof bindHost === \"string\" ? bindHost : \"127.0.0.1\";\n const port = dynamicPort ? await findAvailablePort(requestedPort, { host: probeHost }) : requestedPort;\n const entries = withRuntimePort(configEntries, requestedPort, port);\n const hosts = uniqueHosts(entries);\n const primaryHost =\n options.primaryHost ??\n entries.find((entry) => entry.port === port)?.host ??\n hosts[0] ??\n `${sanitizeProjectName(getProjectName(cwd))}.localhost`;\n\n return {\n cwd,\n projectName: sanitizeProjectName(options.project ?? getProjectName(cwd)),\n readOptions,\n configPath: resolvedPath.path,\n configFileName: resolvedPath.fileName,\n configEntries,\n entries,\n hosts,\n requestedPort,\n port,\n dynamicPort,\n bindHost,\n primaryHost,\n https: options.https === true\n };\n}\n","import { execa } from \"execa\";\n\nexport type DoctorResult = {\n ok: boolean;\n caddy: {\n found: boolean;\n version?: string;\n installHint: string;\n };\n};\n\nexport async function checkCaddy(): Promise<DoctorResult[\"caddy\"]> {\n try {\n const result = await execa(\"caddy\", [\"version\"], { reject: false });\n const version = [result.stdout, result.stderr].filter(Boolean).join(\"\\n\").trim();\n\n return {\n found: result.exitCode === 0,\n ...(version ? { version } : {}),\n installHint: \"brew install caddy\"\n };\n } catch {\n return {\n found: false,\n installHint: \"brew install caddy\"\n };\n }\n}\n\nexport async function runDoctor(): Promise<DoctorResult> {\n const caddy = await checkCaddy();\n return {\n ok: caddy.found,\n caddy\n };\n}\n","export type LocalghostEnvironment = NodeJS.ProcessEnv;\n\nconst PRODUCTION_ENV_KEYS = [\"NODE_ENV\", \"VERCEL_ENV\", \"NETLIFY\", \"CF_PAGES_BRANCH\", \"LOCALGHOST_ENV\"] as const;\n\nexport function getProductionReason(env: LocalghostEnvironment = process.env) {\n if (env.LOCALGHOST_ENV === \"production\") return \"LOCALGHOST_ENV=production\";\n if (env.NODE_ENV === \"production\") return \"NODE_ENV=production\";\n if (env.VERCEL_ENV === \"production\") return \"VERCEL_ENV=production\";\n if (env.NETLIFY === \"true\" && env.CONTEXT === \"production\") return \"NETLIFY=true and CONTEXT=production\";\n if (env.CF_PAGES_BRANCH && env.CF_PAGES_BRANCH === env.CF_PAGES_PRODUCTION_BRANCH) {\n return \"CF_PAGES_BRANCH matches CF_PAGES_PRODUCTION_BRANCH\";\n }\n\n return null;\n}\n\nexport function isProductionLike(env: LocalghostEnvironment = process.env) {\n return getProductionReason(env) !== null;\n}\n\nexport function assertLocalDevelopment(command: string, env: LocalghostEnvironment = process.env) {\n const reason = getProductionReason(env);\n if (!reason) return;\n\n throw new Error(`Localghost only runs in local development. Refusing \\`${command}\\` because ${reason}.`);\n}\n\nexport function getProductionEnvKeys() {\n return PRODUCTION_ENV_KEYS;\n}\n","import { mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport function readTextFile(path: string) {\n return readFileSync(path, \"utf8\");\n}\n\nexport function writeTextFile(path: string, value: string) {\n mkdirSync(dirname(path), { recursive: true });\n writeFileSync(path, value, \"utf8\");\n return path;\n}\n","import { writeFileSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { execa } from \"execa\";\nimport { sanitizeProjectName } from \"./config.js\";\nimport { readTextFile } from \"./fs.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type UpdateSystemHostsResult = {\n changed: boolean;\n hostsPath: string;\n tempPath?: string;\n};\n\nexport type RemoveSystemHostsResult = UpdateSystemHostsResult & {\n removed: boolean;\n};\n\nfunction escapeRegExp(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction getManagedBlockPattern(projectName: string) {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const start = `# localghost:start ${sanitizedProjectName}`;\n const end = `# localghost:end ${sanitizedProjectName}`;\n return new RegExp(`${escapeRegExp(start)}[\\\\s\\\\S]*?${escapeRegExp(end)}\\\\n?`, \"m\");\n}\n\nexport function getSystemHostsPath() {\n return process.platform === \"win32\" ? \"C:\\\\Windows\\\\System32\\\\drivers\\\\etc\\\\hosts\" : \"/etc/hosts\";\n}\n\nexport function renderHostsBlock(projectName: string, entries: DevHostEntry[]) {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const hosts = [...new Set(entries.map((entry) => entry.host))].sort();\n\n return [\n `# localghost:start ${sanitizedProjectName}`,\n ...hosts.map((host) => `127.0.0.1 ${host}`),\n `# localghost:end ${sanitizedProjectName}`,\n \"\"\n ].join(\"\\n\");\n}\n\nexport function upsertManagedBlock(existing: string, projectName: string, block: string) {\n const pattern = getManagedBlockPattern(projectName);\n\n if (pattern.test(existing)) {\n return existing.replace(pattern, block);\n }\n\n return `${existing.trimEnd()}\\n\\n${block}`;\n}\n\nexport function removeManagedBlock(existing: string, projectName: string) {\n const pattern = getManagedBlockPattern(projectName);\n\n if (!pattern.test(existing)) {\n return existing;\n }\n\n return existing.replace(pattern, \"\").replace(/\\n{3,}/g, \"\\n\\n\").trimEnd() + \"\\n\";\n}\n\nasync function writeSystemHostsFile(hostsPath: string, next: string, projectName: string) {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const tempPath = join(tmpdir(), `localghost-${sanitizedProjectName}-hosts`);\n writeFileSync(tempPath, next, \"utf8\");\n\n if (process.platform === \"win32\") {\n throw new Error(`Windows support: run as administrator and copy ${tempPath} to ${hostsPath}.`);\n }\n\n await execa(\"sudo\", [\"cp\", tempPath, hostsPath], { stdio: \"inherit\" });\n\n return tempPath;\n}\n\nexport async function updateSystemHosts(projectName: string, entries: DevHostEntry[]): Promise<UpdateSystemHostsResult> {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const hostsPath = getSystemHostsPath();\n const existing = readTextFile(hostsPath);\n const block = renderHostsBlock(sanitizedProjectName, entries);\n const next = upsertManagedBlock(existing, sanitizedProjectName, block);\n\n if (next === existing) {\n return { changed: false, hostsPath };\n }\n\n const tempPath = await writeSystemHostsFile(hostsPath, next, sanitizedProjectName);\n\n return { changed: true, hostsPath, tempPath };\n}\n\nexport async function removeSystemHosts(projectName: string): Promise<RemoveSystemHostsResult> {\n const sanitizedProjectName = sanitizeProjectName(projectName);\n const hostsPath = getSystemHostsPath();\n const existing = readTextFile(hostsPath);\n const next = removeManagedBlock(existing, sanitizedProjectName);\n\n if (next === existing) {\n return { changed: false, removed: false, hostsPath };\n }\n\n const tempPath = await writeSystemHostsFile(hostsPath, next, sanitizedProjectName);\n\n return { changed: true, removed: true, hostsPath, tempPath };\n}\n","import { stdin as input, stdout as output } from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\n\nexport function canPrompt() {\n return Boolean(input.isTTY && output.isTTY);\n}\n\nexport async function withPrompt<T>(run: (prompt: (question: string) => Promise<string>) => Promise<T>) {\n const rl = createInterface({ input, output });\n try {\n return await run((question) => rl.question(question));\n } finally {\n rl.close();\n }\n}\n\nexport async function confirm(question: string, defaultValue = true) {\n return withPrompt(async (prompt) => {\n const suffix = defaultValue ? \" [Y/n] \" : \" [y/N] \";\n const answer = (await prompt(`${question}${suffix}`)).trim().toLowerCase();\n if (!answer) return defaultValue;\n return answer === \"y\" || answer === \"yes\";\n });\n}\n\nexport async function ask(question: string, defaultValue?: string) {\n return withPrompt(async (prompt) => {\n const suffix = defaultValue ? ` (${defaultValue}) ` : \" \";\n const answer = (await prompt(`${question}${suffix}`)).trim();\n return answer || defaultValue || \"\";\n });\n}\n","import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { readTextFile, writeTextFile } from \"./fs.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport const LOCALGHOST_STATE_FILE = \"ops/local/localghost-state.json\";\n\nexport type LocalghostStateAction = \"setup\" | \"teardown\";\n\nexport type LocalghostState = {\n version: 1;\n action: LocalghostStateAction;\n updatedAt: string;\n projectName: string;\n cwd: string;\n configPath?: string;\n hostsPath?: string;\n hostsChanged?: boolean;\n hostsTempPath?: string;\n caddyfilePath?: string;\n caddyfileRemoved?: boolean;\n caddyHttps?: boolean;\n entries?: DevHostEntry[];\n};\n\nexport type WriteLocalghostStateInput = Omit<LocalghostState, \"version\" | \"updatedAt\">;\n\nexport function getLocalghostStatePath(cwd = process.cwd()) {\n return join(cwd, LOCALGHOST_STATE_FILE);\n}\n\nexport function readLocalghostState(cwd = process.cwd()): LocalghostState | null {\n const path = getLocalghostStatePath(cwd);\n if (!existsSync(path)) return null;\n return JSON.parse(readTextFile(path)) as LocalghostState;\n}\n\nexport function writeLocalghostState(cwd: string, state: WriteLocalghostStateInput) {\n const path = getLocalghostStatePath(cwd);\n writeTextFile(path, `${JSON.stringify({ version: 1, updatedAt: new Date().toISOString(), ...state }, null, 2)}\\n`);\n return path;\n}\n","import { dirname, join } from \"node:path\";\nimport { execa } from \"execa\";\nimport { writeTextFile } from \"./fs.js\";\nimport type { DevHostEntry } from \"./parse.js\";\n\nexport type CaddyModeOptions = {\n https?: boolean;\n};\n\nfunction groupByPort(entries: DevHostEntry[]) {\n const groups = new Map<number, DevHostEntry[]>();\n\n for (const entry of entries) {\n const group = groups.get(entry.port) ?? [];\n group.push(entry);\n groups.set(entry.port, group);\n }\n\n return groups;\n}\n\nexport function getCaddyfilePath(cwd = process.cwd()) {\n return join(cwd, \"ops/local/Caddyfile\");\n}\n\nexport function renderCaddyfile(entries: DevHostEntry[], options: CaddyModeOptions = {}) {\n const groups = groupByPort(entries);\n const https = options.https === true;\n const blocks = [...groups.entries()]\n .sort(([leftPort], [rightPort]) => leftPort - rightPort)\n .map(([port, group]) => {\n const hosts = group\n .map((entry) => (https ? entry.host : `http://${entry.host}`))\n .sort()\n .join(\", \");\n\n return `${hosts} {\n reverse_proxy 127.0.0.1:${port}\n}`;\n });\n\n const globalOptions = https\n ? `{\n local_certs\n}\n\n`\n : \"\";\n\n return `${globalOptions}${blocks.join(\"\\n\\n\")}\n`;\n}\n\nexport async function writeCaddyfile(entries: DevHostEntry[], cwd = process.cwd(), options: CaddyModeOptions = {}) {\n const path = getCaddyfilePath(cwd);\n writeTextFile(path, renderCaddyfile(entries, options));\n return path;\n}\n\nexport async function validateCaddyfile(path: string) {\n await execa(\"caddy\", [\"validate\", \"--config\", path], {\n cwd: dirname(path),\n stdio: \"inherit\"\n });\n}\n\nexport async function runCaddy(path: string) {\n await execa(\"caddy\", [\"run\", \"--config\", path], {\n cwd: dirname(path),\n stdio: \"inherit\"\n });\n}\n\nexport function startCaddy(path: string) {\n return execa(\"caddy\", [\"run\", \"--config\", path], {\n cwd: dirname(path),\n stdio: \"inherit\"\n });\n}\n"],"mappings":"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","names":["existsSync","readFileSync","resolve","input","fileName","resolve","readFileSync","writeFileSync","join","execa","join","writeFileSync","execa","existsSync","join","join","existsSync","dirname","join","execa","join","execa","dirname","resolve","readFileSync","existsSync"]}
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 HTTPS proxy from the same config file.
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
- https://app.localhost/ -> http://127.0.0.1:5173
92
- https://api.app.localhost/ -> http://127.0.0.1:8787
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` and print the browser-facing HTTPS URLs.
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, https: true })]
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 HTTPS hostnames for app repos. A tiny CLI for `.localghost` configs, `/etc/hosts` blocks, Caddy reverse proxies, and Vite `allowedHosts`.
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 HTTPS hostnames for app repos, powered by `.localghost`, Caddy, `/etc/hosts`, and Vite.
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 HTTPS hostnames for Vite
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 HTTPS hostnames for app repos. A tiny CLI for .localghost configs, /etc/hosts blocks, Caddy reverse proxies, and Vite allowedHosts." \
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 HTTPS domains in app repos. It gives each project one small contract for `.localhost` hostnames, Caddy reverse proxies, Vite `allowedHosts`, and the system hosts file.
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.
@@ -120,10 +120,48 @@ npm run release:check
120
120
 
121
121
  That command typechecks, builds the package, builds the static site, and runs `npm pack --dry-run`.
122
122
 
123
- `.github/workflows/publish-npm.yml` publishes to npm from a published GitHub release or manual workflow dispatch. The workflow reruns `npm run release:check` before publishing and uses:
123
+ `.github/workflows/publish-npm.yml` publishes to npm when a `v*` tag is pushed, or from manual workflow dispatch. The workflow checks that tag names match `package.json` versions, reruns `npm run release:check`, and then uses:
124
124
 
125
125
  ```sh
126
- npm publish --access public --provenance
126
+ npm run publish:public
127
127
  ```
128
128
 
129
- Configure npm trusted publishing for `hamedb89/localghost` before relying on the release workflow. Local manual publishes are guarded by the `prepublishOnly` package hook, which runs the same release check.
129
+ That package script runs `npm publish --access public --provenance`.
130
+
131
+ Configure npm trusted publishing for `hamedb89/localghost` before relying on the release workflow. On npmjs.com, open the package settings and add a trusted publisher with:
132
+
133
+ ```txt
134
+ Provider: GitHub Actions
135
+ Organization or user: hamedb89
136
+ Repository: localghost
137
+ Workflow filename: publish-npm.yml
138
+ Environment name: npm
139
+ Allowed actions: npm publish
140
+ ```
141
+
142
+ The workflow must keep `id-token: write`, run on a GitHub-hosted runner, and use a recent npm CLI. Trusted publishing does not need an `NPM_TOKEN` secret. npm automatically generates provenance for public packages published from public GitHub repositories through trusted publishing.
143
+
144
+ If the workflow reaches `npm publish` and npm returns `404 Not Found` or a permission-flavored 404 for `@hamedb89/localghost`, recheck the trusted publisher fields above. The package, repository, workflow filename, environment name, and allowed action must match exactly.
145
+
146
+ Local manual publishes are guarded by the `prepublishOnly` package hook, which runs the same release check.
147
+
148
+ ## Patch Release Workflow
149
+
150
+ Use patch releases for docs, packaging metadata, small fixes, and backwards-compatible CLI/API changes. Do not overwrite a published npm version; npm package versions are immutable, and git tags should continue to identify the source that produced that published package.
151
+
152
+ For a normal patch release:
153
+
154
+ ```sh
155
+ git status --short
156
+ npm run release:check
157
+ npm version patch
158
+ git push origin main --tags
159
+ ```
160
+
161
+ Pushing the `v*` tag starts the npm publish workflow automatically. The workflow reruns `npm run release:check` before publishing. Create the GitHub release afterwards if you want release notes on GitHub.
162
+
163
+ If publishing from a local terminal instead of GitHub Actions, omit provenance and provide the npm two-factor code:
164
+
165
+ ```sh
166
+ npm publish --access public --otp=123456
167
+ ```
@@ -9,20 +9,25 @@ 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 reset [--project name]
13
14
  localghost teardown [--project name] [--remove-caddyfile]
14
- localghost status [--json]
15
+ localghost status [--ready] [--json]
16
+ localghost ps [--json]
15
17
  localghost update [--json]
16
- localghost dev [--config file] [--config-pattern regex]
18
+ localghost dev [--config file] [--config-pattern regex] [--https|--ssl] [--setup]
19
+ localghost run [--config file] [--config-pattern regex] [--https|--ssl] [--setup] [--dynamic-port] -- command
17
20
  localghost print [--config file] [--config-pattern regex]
18
21
  ```
19
22
 
20
23
  ## Description
21
24
 
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 proxy. It is intentionally small and explicit: no hidden installs, no full hosts-file rewrites, and no broad Vite `allowedHosts: true` shortcut.
25
+ Localghost reads `.localghost`, writes a managed `/etc/hosts` block, records `ops/local/localghost-state.json`, generates `ops/local/Caddyfile`, and runs a Caddy local proxy. HTTP is the default; local HTTPS is explicit with `--https` or `--ssl`. 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
26
 
24
27
  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
28
 
29
+ `setup`, `dev`, and `teardown` refuse to run in production-like environments such as `NODE_ENV=production`, `VERCEL_ENV=production`, or `LOCALGHOST_ENV=production`.
30
+
26
31
  ## Commands
27
32
 
28
33
  ### init
@@ -56,7 +61,7 @@ Currently checks Caddy and prints `brew install caddy` when missing.
56
61
 
57
62
  ### setup
58
63
 
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.
64
+ 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
65
 
61
66
  ```sh
62
67
  localghost setup --project app
@@ -70,12 +75,30 @@ Removes the managed Localghost block from `/etc/hosts` for the selected project
70
75
  localghost teardown --remove-caddyfile
71
76
  ```
72
77
 
78
+ ### reset
79
+
80
+ Removes the managed Localghost hosts block, generated Caddyfile, and setup state, but keeps `.localghost` in place so setup can be tested again.
81
+
82
+ ```sh
83
+ localghost reset
84
+ localghost setup
85
+ ```
86
+
73
87
  ### status
74
88
 
75
- Prints Localghost's project-local state file. Pass `--json` for scripts and agents.
89
+ 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.
90
+
91
+ ```sh
92
+ localghost status --ready
93
+ ```
94
+
95
+ ### ps
96
+
97
+ 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.
76
98
 
77
99
  ```sh
78
- localghost status --json
100
+ localghost ps
101
+ localghost ps --json
79
102
  ```
80
103
 
81
104
  ### update
@@ -88,7 +111,7 @@ localghost update
88
111
 
89
112
  ### routes
90
113
 
91
- Prints the local domain layer as `domain -> upstream` routes. Pass `--http` if the browser-facing domain should be shown as plain HTTP.
114
+ 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
115
 
93
116
  ```sh
94
117
  localghost routes
@@ -96,12 +119,25 @@ localghost routes
96
119
 
97
120
  ### dev
98
121
 
99
- Writes `ops/local/Caddyfile`, validates it, and runs Caddy. Supports `--config` and `--config-pattern`.
122
+ Requires setup to be ready, writes `ops/local/Caddyfile`, validates it, and runs Caddy. Supports `--config` and `--config-pattern`. HTTP is the default. Pass `--https` or `--ssl` to run a local HTTPS proxy. Pass `--setup` to explicitly allow `dev` to run setup first when setup is missing or stale.
100
123
 
101
124
  ```sh
102
125
  localghost dev
103
126
  ```
104
127
 
128
+ ### run
129
+
130
+ Resolves one Localghost context, ensures setup is ready, writes the runtime Caddyfile, starts Caddy, and runs a child dev command. The selected port is passed to the child as `LOCALGHOST_PORT` and `VITE_PORT`.
131
+
132
+ ```sh
133
+ localghost run -- vite
134
+ localghost run --dynamic-port -- turbo dev
135
+ ```
136
+
137
+ Pass `--dynamic-port` or `--dynamic-port=yes` to start at the configured port and walk upward until `127.0.0.1:<port>` is free. Pass `--setup` to explicitly allow setup when the hosts block is missing or stale.
138
+
139
+ `dev` and `run` register active sessions in a user-local activity file so `localghost ps` can show what is running across projects.
140
+
105
141
  ### print
106
142
 
107
143
  Prints parsed Localghost config entries as JSON. Supports `--config` and `--config-pattern`.
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@hamedb89/localghost",
3
- "version": "0.1.0",
4
- "description": "Friendly local HTTPS hostnames for app repos with .localghost, Caddy, /etc/hosts, and Vite.",
3
+ "version": "0.1.6",
4
+ "description": "Friendly local hostnames for app repos with .localghost, Caddy, /etc/hosts, and Vite.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "localghost": "./dist/cli.js"
7
+ "localghost": "dist/cli.js"
8
8
  },
9
9
  "exports": {
10
10
  ".": {
@@ -46,8 +46,11 @@
46
46
  "prepack": "npm run build",
47
47
  "prepublishOnly": "npm run release:check",
48
48
  "pack:dry": "npm pack --dry-run",
49
- "release:check": "npm run typecheck && npm run build && npm run site:build && npm pack --dry-run",
49
+ "release:check": "npm run version:check && npm run typecheck && npm run build && npm run site:build && npm pack --dry-run",
50
50
  "site:build": "node scripts/build-site.mjs",
51
+ "sync:version": "node scripts/sync-readme-version.mjs",
52
+ "version:check": "node scripts/sync-readme-version.mjs --check",
53
+ "version": "npm run sync:version && git add README.md src/update-check.ts",
51
54
  "publish:public": "npm publish --access public --provenance"
52
55
  },
53
56
  "dependencies": {