@hamedb89/localghost 0.1.15 → 0.2.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/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { D as DevHostEntry, R as ReadDevHostsOptions, a as ResolvedDevHostsPath, b as ConstructGhostTunnelUrlInput, c as GhostTunnelTransportOptions, d as GhostTunnelTransportConfig, e as GhostTunnelRoute, f as GhostTunnelConfig, C as ConfigPattern, G as GhostTunnelOptions } from './tunnel-BA52DD9e.js';
2
2
  export { g as GhostTunnelAdapterOptions, h as GhostTunnelAdapterProvider, i as GhostTunnelAdapterStrategy, j as GhostTunnelAdapterTransport, k as GhostTunnelDisplayDefaults, l as GhostTunnelDomainOptions, m as GhostTunnelMode, n as GhostTunnelNamespaceConfig, o as GhostTunnelNamespaceOptions, p as GhostTunnelNamespaceTag, q as GhostTunnelNamespaceValues, r as GhostTunnelPreviewOptions, s as GhostTunnelTransportKind, t as GhostTunnelTunnelStoreEnv, u as GhostTunnelTunnelStoreOptions, v as GhostTunnelTunnelStoreProvider, L as LOCALGHOST_CONFIG_FILE, w as assertSecureGhostTunnelRequest, x as constructGhostTunnelHost, y as constructGhostTunnelURL, z as constructGhostTunnelUrl, A as findLocalMdnsHosts, B as getConfigFileCandidates, E as getDevHostsPath, F as getGhostTunnelDefaultDisplayUrl, H as getGhostTunnelDisplayUrl, I as getGhostTunnelDisplayUrls, J as getGhostTunnelEntryHost, K as getGhostTunnelPreviewUrl, M as getGhostTunnelWildcardHost, N as getProjectName, O as parseDevHosts, P as parseGhostTunnelHost, Q as readDevHosts, S as resolveDevHostsPath, T as resolveGhostTunnelConfig, U as sanitizeProjectName } from './tunnel-BA52DD9e.js';
3
3
  import * as execa from 'execa';
4
+ import { ChildProcess } from 'node:child_process';
4
5
 
5
6
  declare const LOCALGHOST_ACTIVITY_VERSION = 1;
6
7
  type LocalghostRunMode = "dev" | "run" | "vite";
@@ -15,6 +16,7 @@ type LocalghostRunRecord = {
15
16
  configPath?: string;
16
17
  caddyfilePath?: string;
17
18
  caddyPid?: number;
19
+ caddyPgid?: number;
18
20
  childPid?: number;
19
21
  childCommand?: string[];
20
22
  https?: boolean;
@@ -70,7 +72,7 @@ declare function unregisterLocalghostSetup(options: {
70
72
  declare function renderLocalghostBanner(): string;
71
73
  declare function renderCompactLocalghostBanner(): string;
72
74
 
73
- declare const LOCALGHOST_AGENT_GUIDE = "# Localghost agent guide\n\nLocalghost owns the local development proxy and the app process boundary.\n\n## Preferred repository setup\n\nFor a normal repository, use this package script:\n\n \"dev\": \"localghost\"\n\nFor an explicit app command, keep the raw command separate:\n\n \"dev\": \"localghost run -- vite\"\n \"dev:raw\": \"vite\"\n\nUse `localghost dev` only when the Caddy proxy should run without starting the app.\n\n## Useful commands\n\n- `localghost`: detect and run the repository development command.\n- `localghost run -- <command>`: wrap an explicit app command.\n- `localghost dev`: run only the local Caddy proxy.\n- `localghost status --ready`: check project setup.\n- `localghost repair`: repair managed hosts and Caddy setup.\n- `localghost ps --json`: inspect Localghost-managed repositories, instances, and ports.\n- `localghost routes`: inspect hostname-to-port routing.\n- `localghost doctor`: check machine prerequisites.\n\n## Configuration\n\n- Commit repository defaults in `localghost.config.mjs`.\n- Keep hostname and requested-port routes in `.localghost`.\n- CLI flags override repository configuration for one invocation.\n- Localghost remembers active project and instance port assignments in user state under `~/.localghost`.\n- Do not edit the registry manually and do not start Caddy separately.\n\n## Port behavior\n\nLocalghost remembers ports by canonical repository path and instance key. Concurrent Localghost instances receive distinct ports. The operating-system bind check remains authoritative when another tool already owns a port.\n";
75
+ declare const LOCALGHOST_AGENT_GUIDE = "# Localghost agent guide\n\nLocalghost owns the local development proxy and the app process boundary.\n\n## Preferred repository setup\n\nFor a normal repository, use this package script:\n\n \"dev\": \"localghost\"\n\nFor an explicit app command, keep the raw command separate:\n\n \"dev\": \"localghost run -- vite\"\n \"dev:raw\": \"vite\"\n\nUse `localghost dev` only when the Caddy proxy should run without starting the app.\n\n## Useful commands\n\n- `localghost`: detect and run the repository development command.\n- `localghost run -- <command>`: wrap an explicit app command.\n- `localghost dev`: run only the local Caddy proxy.\n- `localghost status --ready`: check project setup.\n- `localghost repair`: repair managed hosts and Caddy setup.\n- `localghost ps --json`: inspect Localghost-managed repositories, instances, and ports.\n- `localghost routes`: inspect hostname-to-port routing.\n- `localghost doctor`: check machine prerequisites, ports, and registry state.\n- `localghost repair --reallocate-port`: move an occupied project port to a stable available port.\n\n## Configuration\n\n- Commit repository defaults in `localghost.config.mjs`.\n- Keep hostname and requested-port routes in `.localghost`.\n- CLI flags override repository configuration for one invocation.\n- Localghost remembers active project and instance port assignments in user state under `~/.localghost`.\n- Do not edit the registry manually and do not start Caddy separately.\n\n## Port behavior\n\nLocalghost remembers ports by canonical repository path and instance key. Concurrent Localghost instances receive distinct ports. The operating-system bind check remains authoritative when another tool already owns a port.\n";
74
76
  declare function formatLocalghostAgentGuide(format?: "text" | "json"): string;
75
77
 
76
78
  declare const LOCALGHOST_GHOST_TUNNEL_FILE = ".ghosttunnel";
@@ -338,6 +340,7 @@ declare function runCaddy(path: string): Promise<void>;
338
340
  declare function startCaddy(path: string): execa.ResultPromise<{
339
341
  cwd: string;
340
342
  stdio: "inherit" | "pipe";
343
+ detached: boolean;
341
344
  }>;
342
345
  declare function stopCaddyProcesses(pids: Iterable<number>, killProcess?: CaddyProcessKiller): CaddyProcessStopResult;
343
346
  declare function trustCaddy(path: string): Promise<void>;
@@ -465,9 +468,34 @@ type DoctorResult = {
465
468
  version?: string;
466
469
  installHint: string;
467
470
  };
471
+ ports: {
472
+ configured?: number;
473
+ available?: boolean;
474
+ registryPath: string;
475
+ staleLeases: Array<{
476
+ projectCwd: string;
477
+ instanceKey: string;
478
+ port: number;
479
+ pid: number;
480
+ }>;
481
+ duplicateAllocations: Array<{
482
+ port: number;
483
+ projects: string[];
484
+ }>;
485
+ currentAllocation?: {
486
+ projectCwd: string;
487
+ instanceKey: string;
488
+ port: number;
489
+ };
490
+ };
491
+ };
492
+ type DoctorOptions = {
493
+ cwd?: string;
494
+ configFiles?: string[];
495
+ configPattern?: ConfigPattern;
468
496
  };
469
497
  declare function checkCaddy(): Promise<DoctorResult["caddy"]>;
470
- declare function runDoctor(): Promise<DoctorResult>;
498
+ declare function runDoctor(options?: DoctorOptions): Promise<DoctorResult>;
471
499
 
472
500
  type LocalghostEnvironment = NodeJS.ProcessEnv;
473
501
  declare function getProductionReason(env?: LocalghostEnvironment): "LOCALGHOST_ENV=production" | "NODE_ENV=production" | "VERCEL_ENV=production" | "NETLIFY=true and CONTEXT=production" | "CF_PAGES_BRANCH matches CF_PAGES_PRODUCTION_BRANCH" | null;
@@ -522,6 +550,11 @@ type FindAvailablePortOptions = {
522
550
  declare function isPortAvailable(port: number, host?: string): Promise<boolean>;
523
551
  declare function findAvailablePort(startPort: number, options?: FindAvailablePortOptions): Promise<number>;
524
552
 
553
+ type ProcessSignal = NodeJS.Signals;
554
+ type ProcessKiller = (pid: number, signal: ProcessSignal) => void;
555
+ declare function signalManagedProcessPid(pid: number | undefined, signal: ProcessSignal, killProcess?: ProcessKiller): boolean;
556
+ declare function signalManagedProcess(child: Pick<ChildProcess, "pid" | "kill" | "killed">, signal: ProcessSignal): boolean;
557
+
525
558
  declare const LOCALGHOST_REGISTRY_FILE = "registry.json";
526
559
  declare const LOCALGHOST_REGISTRY_LOCK_FILE = "registry.lock";
527
560
  type PortAvailabilityCheck = (port: number, host?: string) => boolean | Promise<boolean>;
@@ -577,6 +610,9 @@ type LocalghostRegistry = {
577
610
  instanceKey: string;
578
611
  }): Promise<boolean>;
579
612
  read(): Promise<LocalghostRegistryData>;
613
+ prune(): Promise<{
614
+ removedLeases: number;
615
+ }>;
580
616
  };
581
617
  declare function getLocalghostRegistryRoot(env?: NodeJS.ProcessEnv): string;
582
618
  declare function canonicalizeLocalghostProjectCwd(cwd?: string): string;
@@ -649,7 +685,7 @@ type CreateVercelGhostTunnelHandlerOptions = {
649
685
  declare function createVercelGhostTunnelHandler(options: CreateVercelGhostTunnelHandlerOptions): (request: VercelGhostTunnelRequestLike, response: VercelGhostTunnelResponseLike) => Promise<void>;
650
686
 
651
687
  declare const LOCALGHOST_PACKAGE_NAME = "@hamedb89/localghost";
652
- declare const LOCALGHOST_VERSION = "0.1.15";
688
+ declare const LOCALGHOST_VERSION = "0.2.0";
653
689
  declare const UPDATE_CHECK_CACHE_TTL_MS: number;
654
690
  declare const UPDATE_CHECK_NOTIFY_TTL_MS: number;
655
691
  declare const UPDATE_CHECK_TIMEOUT_MS = 900;
@@ -686,4 +722,4 @@ declare function maybeNotifyAboutUpdate(options?: {
686
722
  disabled?: boolean;
687
723
  }): Promise<void>;
688
724
 
689
- export { type AcquireLocalghostPortOptions, type ActiveRelayRoute, type CaddyModeOptions, type CaddyProcessKiller, type CaddyProcessStopResult, ConfigPattern, type ConstructGhostTunnelIpUrlInput, ConstructGhostTunnelUrlInput, type CreateVercelGhostTunnelHandlerOptions, DEFAULT_GHOST_TUNNEL_IP_TRANSPORT_TTL_SECONDS, DEFAULT_GHOST_TUNNEL_RESPONSE_TTL_SECONDS, DEFAULT_GHOST_TUNNEL_TRANSPORT_QUERY_PARAM, DEFAULT_RELAY_ALLOWED_TARGET_HOSTS, DEFAULT_RELAY_BLOCKED_PORTS, DEFAULT_RELAY_LIMITS, DEFAULT_RELAY_TARGET_POLICY, type DetectedDevCommand, type DetectedDevService, DevHostEntry, type DoctorResult, type DomainRoute, type DomainRouteOptions, type FindAvailablePortOptions, type GhostTunnelAgent, type GhostTunnelAgentOptions, GhostTunnelConfig, type GhostTunnelHttpResponse, type GhostTunnelIpTransportClaim, GhostTunnelOptions, type GhostTunnelQueuedRequest, type GhostTunnelQueuedResponse, GhostTunnelRoute, type GhostTunnelRouteHeartbeat, type GhostTunnelStore, type GhostTunnelStoreEnv, GhostTunnelTransportConfig, GhostTunnelTransportOptions, type InitOptions, type InitResult, LOCALGHOST_ACTIVITY_VERSION, LOCALGHOST_AGENT_GUIDE, LOCALGHOST_GHOST_TUNNEL_FILE, LOCALGHOST_PACKAGE_NAME, LOCALGHOST_REGISTRY_FILE, LOCALGHOST_REGISTRY_LOCK_FILE, LOCALGHOST_STATE_FILE, LOCALGHOST_VERSION, type LocalghostActivity, type LocalghostContext, type LocalghostContextOptions, type LocalghostEnvironment, type LocalghostLease, type LocalghostPackageManager, type LocalghostProjectConfig, type LocalghostProjectConfigResult, type LocalghostRegistry, type LocalghostRegistryData, type LocalghostRegistryEntry, type LocalghostRegistryOptions, type LocalghostRunMode, type LocalghostRunRecord, type LocalghostServiceOptions, type LocalghostSetupRecord, type LocalghostState, type LocalghostStateAction, type PackageManager, type PortAvailabilityCheck, ReadDevHostsOptions, type ReadGhostTunnelOptions, type RedisGhostTunnelEnvResolution, type RedisGhostTunnelStoreOptions, type RegisterLocalghostRunInput, type RegisterLocalghostSetupInput, type RelayAccessMode, type RelayLimits, type RelayLocalTarget, type RelayOfflineResponse, type RelayProtocol, type RelayRouteClaim, type RelayRouteRegistrationInput, type RelayTargetPolicy, type RemoveSystemHostsResult, type ResolveGhostTunnelIpRedirectInput, type ResolveGhostTunnelRequestInput, ResolvedDevHostsPath, type ResolvedGhostTunnelIpRedirect, type ResolvedGhostTunnelRequest, type ServeGhostTunnelLocalRequestInput, type SignedGhostTunnelIpTransportClaim, type SignedRelayRouteClaim, UPDATE_CHECK_CACHE_TTL_MS, UPDATE_CHECK_NOTIFY_TTL_MS, UPDATE_CHECK_TIMEOUT_MS, type UpdateCheckCache, type UpdateCheckResult, type UpdateSystemHostsResult, type VercelGhostTunnelRequestLike, type VercelGhostTunnelResponseLike, type WriteLocalghostStateInput, assertExactRelayHost, assertLocalDevelopment, assertRelayLocalTarget, authenticateRelayAgentToken, canonicalizeLocalghostProjectCwd, checkCaddy, checkForUpdate, compareVersions, constructGhostTunnelIpUrl, createGhostTunnelQueuedRequest, createGhostTunnelRouteHeartbeat, createLocalghostRegistry, createMemoryGhostTunnelStore, createRedisGhostTunnelStore, createRedisGhostTunnelStoreFromEnv, createRelayRouteRegistration, createVercelGhostTunnelHandler, decodeGhostTunnelBody, defineLocalghostConfig, detectDevCommand, detectDevPackageManager, detectDevServices, detectPackageManager, encodeGhostTunnelBody, findAvailablePort, findGhostTunnelEntry, formatDetectedDevCommand, formatDetectedDevServices, formatDomainRoutes, formatGhostTunnel, formatLocalghostAgentGuide, formatUpdateMessage, getCaddyfilePath, getDomainRoutes, getGhostTunnelPath, getLocalghostActivityPath, getLocalghostRegistryRoot, getLocalghostStatePath, getProductionEnvKeys, getProductionReason, getSystemHostsPath, getUpdateCheckCachePath, initLocalghost, isNewerVersion, isPortAvailable, isProcessRunning, isProductionLike, isRelayRouteActive, isUpdateCheckDisabled, listGhostTunnelEntries, listLocalghostRuns, listLocalghostSetups, markUpdateNotified, maybeNotifyAboutUpdate, packageAddCommand, packageRunCommand, patchLocalghostState, pruneLocalghostActivity, readGhostTunnelEntries, readLocalghostActivity, readLocalghostProjectConfig, readLocalghostState, redactRelayHeaders, redactRelayLogUrl, registerLocalghostRun, registerLocalghostSetup, removeManagedBlock, removeSystemHosts, renderCaddyfile, renderCompactLocalghostBanner, renderGhostTunnelRelayOfflineResponse, renderGhostTunnelRouteMissingResponse, renderHostsBlock, renderLocalghostBanner, renderRelayOfflineResponse, resolveGhostTunnelIpRedirect, resolveGhostTunnelPath, resolveGhostTunnelRequest, resolveLocalghostContext, resolveRedisGhostTunnelEnv, runCaddy, runDoctor, serveGhostTunnelLocalRequest, shouldNotifyAboutUpdate, signGhostTunnelIpTransportClaim, signRelayRouteClaim, startCaddy, startGhostTunnelAgent, stopCaddyProcesses, stripRelayForwardHeaders, trustCaddy, unregisterLocalghostRun, unregisterLocalghostSetup, updateSystemHosts, upsertManagedBlock, validateCaddyfile, verifyGhostTunnelIpTransportClaim, verifyRelayRouteClaim, writeCaddyfile, writeLocalghostActivity, writeLocalghostState };
725
+ export { type AcquireLocalghostPortOptions, type ActiveRelayRoute, type CaddyModeOptions, type CaddyProcessKiller, type CaddyProcessStopResult, ConfigPattern, type ConstructGhostTunnelIpUrlInput, ConstructGhostTunnelUrlInput, type CreateVercelGhostTunnelHandlerOptions, DEFAULT_GHOST_TUNNEL_IP_TRANSPORT_TTL_SECONDS, DEFAULT_GHOST_TUNNEL_RESPONSE_TTL_SECONDS, DEFAULT_GHOST_TUNNEL_TRANSPORT_QUERY_PARAM, DEFAULT_RELAY_ALLOWED_TARGET_HOSTS, DEFAULT_RELAY_BLOCKED_PORTS, DEFAULT_RELAY_LIMITS, DEFAULT_RELAY_TARGET_POLICY, type DetectedDevCommand, type DetectedDevService, DevHostEntry, type DoctorOptions, type DoctorResult, type DomainRoute, type DomainRouteOptions, type FindAvailablePortOptions, type GhostTunnelAgent, type GhostTunnelAgentOptions, GhostTunnelConfig, type GhostTunnelHttpResponse, type GhostTunnelIpTransportClaim, GhostTunnelOptions, type GhostTunnelQueuedRequest, type GhostTunnelQueuedResponse, GhostTunnelRoute, type GhostTunnelRouteHeartbeat, type GhostTunnelStore, type GhostTunnelStoreEnv, GhostTunnelTransportConfig, GhostTunnelTransportOptions, type InitOptions, type InitResult, LOCALGHOST_ACTIVITY_VERSION, LOCALGHOST_AGENT_GUIDE, LOCALGHOST_GHOST_TUNNEL_FILE, LOCALGHOST_PACKAGE_NAME, LOCALGHOST_REGISTRY_FILE, LOCALGHOST_REGISTRY_LOCK_FILE, LOCALGHOST_STATE_FILE, LOCALGHOST_VERSION, type LocalghostActivity, type LocalghostContext, type LocalghostContextOptions, type LocalghostEnvironment, type LocalghostLease, type LocalghostPackageManager, type LocalghostProjectConfig, type LocalghostProjectConfigResult, type LocalghostRegistry, type LocalghostRegistryData, type LocalghostRegistryEntry, type LocalghostRegistryOptions, type LocalghostRunMode, type LocalghostRunRecord, type LocalghostServiceOptions, type LocalghostSetupRecord, type LocalghostState, type LocalghostStateAction, type PackageManager, type PortAvailabilityCheck, ReadDevHostsOptions, type ReadGhostTunnelOptions, type RedisGhostTunnelEnvResolution, type RedisGhostTunnelStoreOptions, type RegisterLocalghostRunInput, type RegisterLocalghostSetupInput, type RelayAccessMode, type RelayLimits, type RelayLocalTarget, type RelayOfflineResponse, type RelayProtocol, type RelayRouteClaim, type RelayRouteRegistrationInput, type RelayTargetPolicy, type RemoveSystemHostsResult, type ResolveGhostTunnelIpRedirectInput, type ResolveGhostTunnelRequestInput, ResolvedDevHostsPath, type ResolvedGhostTunnelIpRedirect, type ResolvedGhostTunnelRequest, type ServeGhostTunnelLocalRequestInput, type SignedGhostTunnelIpTransportClaim, type SignedRelayRouteClaim, UPDATE_CHECK_CACHE_TTL_MS, UPDATE_CHECK_NOTIFY_TTL_MS, UPDATE_CHECK_TIMEOUT_MS, type UpdateCheckCache, type UpdateCheckResult, type UpdateSystemHostsResult, type VercelGhostTunnelRequestLike, type VercelGhostTunnelResponseLike, type WriteLocalghostStateInput, assertExactRelayHost, assertLocalDevelopment, assertRelayLocalTarget, authenticateRelayAgentToken, canonicalizeLocalghostProjectCwd, checkCaddy, checkForUpdate, compareVersions, constructGhostTunnelIpUrl, createGhostTunnelQueuedRequest, createGhostTunnelRouteHeartbeat, createLocalghostRegistry, createMemoryGhostTunnelStore, createRedisGhostTunnelStore, createRedisGhostTunnelStoreFromEnv, createRelayRouteRegistration, createVercelGhostTunnelHandler, decodeGhostTunnelBody, defineLocalghostConfig, detectDevCommand, detectDevPackageManager, detectDevServices, detectPackageManager, encodeGhostTunnelBody, findAvailablePort, findGhostTunnelEntry, formatDetectedDevCommand, formatDetectedDevServices, formatDomainRoutes, formatGhostTunnel, formatLocalghostAgentGuide, formatUpdateMessage, getCaddyfilePath, getDomainRoutes, getGhostTunnelPath, getLocalghostActivityPath, getLocalghostRegistryRoot, getLocalghostStatePath, getProductionEnvKeys, getProductionReason, getSystemHostsPath, getUpdateCheckCachePath, initLocalghost, isNewerVersion, isPortAvailable, isProcessRunning, isProductionLike, isRelayRouteActive, isUpdateCheckDisabled, listGhostTunnelEntries, listLocalghostRuns, listLocalghostSetups, markUpdateNotified, maybeNotifyAboutUpdate, packageAddCommand, packageRunCommand, patchLocalghostState, pruneLocalghostActivity, readGhostTunnelEntries, readLocalghostActivity, readLocalghostProjectConfig, readLocalghostState, redactRelayHeaders, redactRelayLogUrl, registerLocalghostRun, registerLocalghostSetup, removeManagedBlock, removeSystemHosts, renderCaddyfile, renderCompactLocalghostBanner, renderGhostTunnelRelayOfflineResponse, renderGhostTunnelRouteMissingResponse, renderHostsBlock, renderLocalghostBanner, renderRelayOfflineResponse, resolveGhostTunnelIpRedirect, resolveGhostTunnelPath, resolveGhostTunnelRequest, resolveLocalghostContext, resolveRedisGhostTunnelEnv, runCaddy, runDoctor, serveGhostTunnelLocalRequest, shouldNotifyAboutUpdate, signGhostTunnelIpTransportClaim, signRelayRouteClaim, signalManagedProcess, signalManagedProcessPid, startCaddy, startGhostTunnelAgent, stopCaddyProcesses, stripRelayForwardHeaders, trustCaddy, unregisterLocalghostRun, unregisterLocalghostSetup, updateSystemHosts, upsertManagedBlock, validateCaddyfile, verifyGhostTunnelIpTransportClaim, verifyRelayRouteClaim, writeCaddyfile, writeLocalghostActivity, writeLocalghostState };
package/dist/index.js CHANGED
@@ -80,6 +80,7 @@ function registerLocalghostRun(input, path = getLocalghostActivityPath()) {
80
80
  ...input.configPath ? { configPath: input.configPath } : {},
81
81
  ...input.caddyfilePath ? { caddyfilePath: input.caddyfilePath } : {},
82
82
  ...input.caddyPid ? { caddyPid: input.caddyPid } : {},
83
+ ...input.caddyPgid ? { caddyPgid: input.caddyPgid } : {},
83
84
  ...input.childPid ? { childPid: input.childPid } : {},
84
85
  ...input.childCommand ? { childCommand: input.childCommand } : {},
85
86
  ...typeof input.https === "boolean" ? { https: input.https } : {},
@@ -301,7 +302,8 @@ Use \`localghost dev\` only when the Caddy proxy should run without starting the
301
302
  - \`localghost repair\`: repair managed hosts and Caddy setup.
302
303
  - \`localghost ps --json\`: inspect Localghost-managed repositories, instances, and ports.
303
304
  - \`localghost routes\`: inspect hostname-to-port routing.
304
- - \`localghost doctor\`: check machine prerequisites.
305
+ - \`localghost doctor\`: check machine prerequisites, ports, and registry state.
306
+ - \`localghost repair --reallocate-port\`: move an occupied project port to a stable available port.
305
307
 
306
308
  ## Configuration
307
309
 
@@ -1635,7 +1637,8 @@ async function runCaddy(path) {
1635
1637
  function startCaddy(path) {
1636
1638
  return execa("caddy", ["run", "--config", path], {
1637
1639
  cwd: dirname3(path),
1638
- stdio: caddyStdio()
1640
+ stdio: caddyStdio(),
1641
+ detached: process.platform !== "win32"
1639
1642
  });
1640
1643
  }
1641
1644
  function stopCaddyProcesses(pids, killProcess = (pid, signal) => process.kill(pid, signal)) {
@@ -1816,6 +1819,18 @@ function createLocalghostRegistry(options = {}) {
1816
1819
  lockPath,
1817
1820
  ownerToken,
1818
1821
  read: readRegistry,
1822
+ async prune() {
1823
+ const releaseLock = await lock();
1824
+ try {
1825
+ const registry = await readRegistry();
1826
+ const before = registry.leases.length;
1827
+ pruneRegistry(registry, now(), isRunning);
1828
+ await writeRegistry(registry);
1829
+ return { removedLeases: before - registry.leases.length };
1830
+ } finally {
1831
+ await releaseLock();
1832
+ }
1833
+ },
1819
1834
  async acquirePort(acquireOptions) {
1820
1835
  const projectCwd = canonicalizeLocalghostProjectCwd(acquireOptions.projectCwd ?? cwd);
1821
1836
  if (!acquireOptions.instanceKey) throw new Error("instanceKey is required");
@@ -2126,11 +2141,52 @@ async function checkCaddy() {
2126
2141
  };
2127
2142
  }
2128
2143
  }
2129
- async function runDoctor() {
2144
+ async function runDoctor(options = {}) {
2130
2145
  const caddy = await checkCaddy();
2146
+ const cwd = options.cwd ?? process.cwd();
2147
+ const registry = createLocalghostRegistry({ cwd });
2148
+ const data = await registry.read();
2149
+ const now = Date.now();
2150
+ const staleLeases = data.leases.filter((lease) => lease.expiresAt <= now || !isProcessRunning(lease.pid)).map(({ projectCwd, instanceKey, port, pid }) => ({ projectCwd, instanceKey, port, pid }));
2151
+ const allocationsByPort = /* @__PURE__ */ new Map();
2152
+ for (const allocation of data.allocations) {
2153
+ const projects = allocationsByPort.get(allocation.port) ?? [];
2154
+ projects.push(`${allocation.projectCwd}#${allocation.instanceKey}`);
2155
+ allocationsByPort.set(allocation.port, projects);
2156
+ }
2157
+ const duplicateAllocations = [...allocationsByPort.entries()].filter(([, projects]) => projects.length > 1).map(([port, projects]) => ({ port, projects }));
2158
+ let configured;
2159
+ let available;
2160
+ try {
2161
+ const context = await resolveLocalghostContext({
2162
+ cwd,
2163
+ ...options.configFiles ? { configFiles: options.configFiles } : {},
2164
+ ...options.configPattern ? { configPattern: options.configPattern } : {},
2165
+ dynamicPort: false
2166
+ });
2167
+ configured = context.requestedPort;
2168
+ available = await isPortAvailable(configured);
2169
+ } catch {
2170
+ }
2171
+ const currentProjectCwd = canonicalizeLocalghostProjectCwd(cwd);
2172
+ const currentAllocation = data.allocations.find((allocation) => allocation.projectCwd === currentProjectCwd);
2131
2173
  return {
2132
- ok: caddy.found,
2133
- caddy
2174
+ ok: caddy.found && available !== false && staleLeases.length === 0 && duplicateAllocations.length === 0,
2175
+ caddy,
2176
+ ports: {
2177
+ ...configured !== void 0 ? { configured } : {},
2178
+ ...available !== void 0 ? { available } : {},
2179
+ registryPath: registry.registryPath,
2180
+ staleLeases,
2181
+ duplicateAllocations,
2182
+ ...currentAllocation ? {
2183
+ currentAllocation: {
2184
+ projectCwd: currentAllocation.projectCwd,
2185
+ instanceKey: currentAllocation.instanceKey,
2186
+ port: currentAllocation.port
2187
+ }
2188
+ } : {}
2189
+ }
2134
2190
  };
2135
2191
  }
2136
2192
 
@@ -2471,6 +2527,25 @@ function initLocalghost(options = {}) {
2471
2527
  };
2472
2528
  }
2473
2529
 
2530
+ // src/process.ts
2531
+ function signalManagedProcessPid(pid, signal, killProcess = (value, processSignal) => process.kill(value, processSignal)) {
2532
+ if (typeof pid !== "number" || !Number.isInteger(pid) || pid < 1) return false;
2533
+ try {
2534
+ killProcess(process.platform === "win32" ? pid : -pid, signal);
2535
+ return true;
2536
+ } catch (error) {
2537
+ if (error instanceof Error && "code" in error && error.code === "ESRCH") return false;
2538
+ throw error;
2539
+ }
2540
+ }
2541
+ function signalManagedProcess(child, signal) {
2542
+ if (process.platform === "win32") {
2543
+ if (!child.killed) child.kill(signal);
2544
+ return true;
2545
+ }
2546
+ return signalManagedProcessPid(child.pid, signal);
2547
+ }
2548
+
2474
2549
  // src/routes.ts
2475
2550
  var ansi = {
2476
2551
  cyan: "\x1B[36m",
@@ -2790,7 +2865,7 @@ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as rea
2790
2865
  import { homedir as homedir3 } from "os";
2791
2866
  import { dirname as dirname4, join as join10 } from "path";
2792
2867
  var LOCALGHOST_PACKAGE_NAME = "@hamedb89/localghost";
2793
- var LOCALGHOST_VERSION = "0.1.15";
2868
+ var LOCALGHOST_VERSION = "0.2.0";
2794
2869
  var UPDATE_CHECK_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
2795
2870
  var UPDATE_CHECK_NOTIFY_TTL_MS = 24 * 60 * 60 * 1e3;
2796
2871
  var UPDATE_CHECK_TIMEOUT_MS = 900;
@@ -3077,6 +3152,8 @@ export {
3077
3152
  shouldNotifyAboutUpdate,
3078
3153
  signGhostTunnelIpTransportClaim,
3079
3154
  signRelayRouteClaim,
3155
+ signalManagedProcess,
3156
+ signalManagedProcessPid,
3080
3157
  startCaddy,
3081
3158
  startGhostTunnelAgent,
3082
3159
  stopCaddyProcesses,