@hamedb89/localghost 0.3.0 → 0.4.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/README.md CHANGED
@@ -45,6 +45,23 @@ Then keep using the command your repo already expects:
45
45
  npm exec localghost
46
46
  ```
47
47
 
48
+ If Localghost is not installed in the repository yet, use an explicit package-manager launcher:
49
+
50
+ ```sh
51
+ npm exec --yes --package=@hamedb89/localghost -- localghost
52
+ pnpm dlx @hamedb89/localghost
53
+ yarn dlx @hamedb89/localghost
54
+ bunx --package @hamedb89/localghost localghost
55
+ ```
56
+
57
+ Test a real consumer repository against the local checkout without changing its manifest or working tree:
58
+
59
+ ```sh
60
+ ./bin/ghost consumer test faaast --repo /path/to/faaast-landing -- pnpm run bench:noop
61
+ ```
62
+
63
+ The command creates a detached worktree, installs its dependencies, builds and links the local Localghost checkout, runs the command inside the worktree, and removes the worktree afterward. Add `--keep` before `--` when inspecting the isolated checkout after a failure.
64
+
48
65
  On the first interactive run, Localghost can create `.localghost`, explain the `/etc/hosts` change, write `ops/local/Caddyfile`, and print the browser-facing URL:
49
66
 
50
67
  ```txt
@@ -87,6 +104,14 @@ Create the repo-local hostname contract:
87
104
  localghost init --write-scripts
88
105
  ```
89
106
 
107
+ Install or update Localghost in the current repository at any time:
108
+
109
+ ```sh
110
+ npm exec --yes --package=@hamedb89/localghost -- localghost upgrade
111
+ ```
112
+
113
+ Once the package is installed locally, use `localghost upgrade` or the package manager's normal update command.
114
+
90
115
  Check whether the machine is ready:
91
116
 
92
117
  ```sh
@@ -603,7 +628,8 @@ localghost teardown [--project name] [--remove-caddyfile]
603
628
  localghost status [--ready] [--json]
604
629
  localghost ps [--json]
605
630
  localghost update [--json]
606
- localghost release <patch|minor|major>
631
+ localghost upgrade [--cwd path]
632
+ localghost release [patch|minor|major]
607
633
  localghost dev [--config file] [--config-pattern regex] [--https|--ssl] [--auto-repair yes|no] [--trust]
608
634
  localghost run [--config file] [--config-pattern regex] [--https|--ssl] [--auto-repair yes|no] [--trust] [--dynamic-port] -- command
609
635
  localghost routes [--https|--ssl]
package/dist/cli.js CHANGED
@@ -1821,30 +1821,44 @@ function shellQuote(value) {
1821
1821
  function getConfigFlag(configFile) {
1822
1822
  return configFile === LOCALGHOST_CONFIG_FILE ? "" : ` --config ${shellQuote(configFile)}`;
1823
1823
  }
1824
- function updatePackageScripts(packageJsonPath, configFile) {
1824
+ function hasLocalghostDependency(pkg) {
1825
+ return ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"].some((field) => {
1826
+ const dependencies = pkg[field];
1827
+ return typeof dependencies === "object" && dependencies !== null && "@hamedb89/localghost" in dependencies;
1828
+ });
1829
+ }
1830
+ function localghostCommand(packageManager, installed) {
1831
+ if (installed) return "localghost";
1832
+ if (packageManager === "pnpm") return "pnpm dlx @hamedb89/localghost";
1833
+ if (packageManager === "yarn") return "yarn dlx @hamedb89/localghost";
1834
+ if (packageManager === "bun") return "bunx --package @hamedb89/localghost localghost";
1835
+ return "npm exec --yes --package=@hamedb89/localghost -- localghost";
1836
+ }
1837
+ function updatePackageScripts(packageJsonPath, configFile, packageManager) {
1825
1838
  const pkg = readPackageJson2(packageJsonPath);
1826
1839
  if (!pkg) return false;
1827
1840
  const scripts = typeof pkg.scripts === "object" && pkg.scripts ? pkg.scripts : {};
1828
1841
  const configFlag = getConfigFlag(configFile);
1842
+ const command = localghostCommand(packageManager, hasLocalghostDependency(pkg));
1829
1843
  const nextScripts = {
1830
1844
  ...scripts,
1831
- "localghost:setup": scripts["localghost:setup"] ?? `localghost setup${configFlag}`,
1832
- "localghost:proxy": scripts["localghost:proxy"] ?? `localghost dev${configFlag}`,
1833
- "localghost:proxy:https": scripts["localghost:proxy:https"] ?? `localghost dev${configFlag} --https`,
1834
- "localghost:run": scripts["localghost:run"] ?? `localghost run${configFlag} --`,
1835
- "localghost:ready": scripts["localghost:ready"] ?? `localghost status${configFlag} --ready`,
1836
- "localghost:repair": scripts["localghost:repair"] ?? `localghost repair${configFlag}`,
1837
- "localghost:trust": scripts["localghost:trust"] ?? `localghost trust${configFlag}`,
1838
- "localghost:ps": scripts["localghost:ps"] ?? "localghost ps",
1839
- "localghost:print": scripts["localghost:print"] ?? `localghost print${configFlag}`,
1840
- "localghost:routes": scripts["localghost:routes"] ?? `localghost routes${configFlag}`,
1841
- "localghost:status": scripts["localghost:status"] ?? "localghost status",
1842
- "localghost:reset": scripts["localghost:reset"] ?? "localghost reset",
1843
- "localghost:teardown": scripts["localghost:teardown"] ?? "localghost teardown",
1844
- "localghost:doctor": scripts["localghost:doctor"] ?? "localghost doctor",
1845
- "localghost:update": scripts["localghost:update"] ?? "localghost update",
1846
- "caddy:setup": scripts["caddy:setup"] ?? `localghost setup${configFlag}`,
1847
- "caddy:dev": scripts["caddy:dev"] ?? `localghost dev${configFlag}`
1845
+ "localghost:setup": scripts["localghost:setup"] ?? `${command} setup${configFlag}`,
1846
+ "localghost:proxy": scripts["localghost:proxy"] ?? `${command} dev${configFlag}`,
1847
+ "localghost:proxy:https": scripts["localghost:proxy:https"] ?? `${command} dev${configFlag} --https`,
1848
+ "localghost:run": scripts["localghost:run"] ?? `${command} run${configFlag} --`,
1849
+ "localghost:ready": scripts["localghost:ready"] ?? `${command} status${configFlag} --ready`,
1850
+ "localghost:repair": scripts["localghost:repair"] ?? `${command} repair${configFlag}`,
1851
+ "localghost:trust": scripts["localghost:trust"] ?? `${command} trust${configFlag}`,
1852
+ "localghost:ps": scripts["localghost:ps"] ?? `${command} ps`,
1853
+ "localghost:print": scripts["localghost:print"] ?? `${command} print${configFlag}`,
1854
+ "localghost:routes": scripts["localghost:routes"] ?? `${command} routes${configFlag}`,
1855
+ "localghost:status": scripts["localghost:status"] ?? `${command} status`,
1856
+ "localghost:reset": scripts["localghost:reset"] ?? `${command} reset`,
1857
+ "localghost:teardown": scripts["localghost:teardown"] ?? `${command} teardown`,
1858
+ "localghost:doctor": scripts["localghost:doctor"] ?? `${command} doctor`,
1859
+ "localghost:update": scripts["localghost:update"] ?? `${command} update`,
1860
+ "caddy:setup": scripts["caddy:setup"] ?? `${command} setup${configFlag}`,
1861
+ "caddy:dev": scripts["caddy:dev"] ?? `${command} dev${configFlag}`
1848
1862
  };
1849
1863
  const changed = JSON.stringify(scripts) !== JSON.stringify(nextScripts);
1850
1864
  if (!changed) return false;
@@ -1880,7 +1894,7 @@ function initLocalghost(options = {}) {
1880
1894
  }
1881
1895
  writeTextFile(configPath, renderConfig({ host, port, apiHost, apiPort }));
1882
1896
  const packageJsonPath = join8(cwd, "package.json");
1883
- const packageJsonChanged = options.writeScripts ? updatePackageScripts(packageJsonPath, configFile) : false;
1897
+ const packageJsonChanged = options.writeScripts ? updatePackageScripts(packageJsonPath, configFile, packageManager) : false;
1884
1898
  return {
1885
1899
  configPath,
1886
1900
  configCreated: true,
@@ -1903,10 +1917,17 @@ Localghost owns the local development proxy and the app process boundary.
1903
1917
 
1904
1918
  ## Preferred repository setup
1905
1919
 
1906
- For a normal repository, use this package script:
1920
+ For a normal repository with Localghost installed as a dev dependency, use this package script:
1907
1921
 
1908
1922
  "dev": "localghost"
1909
1923
 
1924
+ If the repository does not have a local CLI yet, use the package-manager launcher:
1925
+
1926
+ npm exec --yes --package=@hamedb89/localghost -- localghost
1927
+ pnpm dlx @hamedb89/localghost
1928
+ yarn dlx @hamedb89/localghost
1929
+ bunx --package @hamedb89/localghost localghost
1930
+
1910
1931
  For an explicit app command, keep the raw command separate:
1911
1932
 
1912
1933
  "dev": "localghost run -- vite"
@@ -1926,6 +1947,11 @@ Use \`localghost dev\` only when the Caddy proxy should run without starting the
1926
1947
  - \`localghost doctor\`: check machine prerequisites, ports, and registry state.
1927
1948
  - \`localghost repair --reallocate-port\`: move an occupied project port to a stable available port.
1928
1949
 
1950
+ - \`localghost update\`: check npm for a newer Localghost release.
1951
+ - \`localghost upgrade\`: install or update Localghost as a repository dev dependency.
1952
+
1953
+ Use \`localghost\` directly when the package is installed locally. Use \`npm exec --yes --package=@hamedb89/localghost -- localghost <command>\`, \`pnpm dlx @hamedb89/localghost <command>\`, \`yarn dlx @hamedb89/localghost <command>\`, or \`bunx --package @hamedb89/localghost localghost <command>\` when it is not.
1954
+
1929
1955
  ## Configuration
1930
1956
 
1931
1957
  - Commit repository defaults in \`localghost.config.mjs\`.
@@ -1942,6 +1968,14 @@ function formatLocalghostAgentGuide(format = "text") {
1942
1968
  if (format === "json") {
1943
1969
  return JSON.stringify({
1944
1970
  preferredScript: "localghost",
1971
+ packageInstall: "npm install -D @hamedb89/localghost",
1972
+ packageUpgrade: "localghost upgrade",
1973
+ packageLaunchers: {
1974
+ npm: "npm exec --yes --package=@hamedb89/localghost -- localghost",
1975
+ pnpm: "pnpm dlx @hamedb89/localghost",
1976
+ yarn: "yarn dlx @hamedb89/localghost",
1977
+ bun: "bunx --package @hamedb89/localghost localghost"
1978
+ },
1945
1979
  explicitScript: "localghost run -- <command>",
1946
1980
  proxyOnlyCommand: "localghost dev",
1947
1981
  inspectionCommands: ["localghost status --ready", "localghost ps --json", "localghost routes", "localghost doctor"],
@@ -2068,7 +2102,7 @@ import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as rea
2068
2102
  import { homedir as homedir3 } from "os";
2069
2103
  import { dirname as dirname4, join as join10 } from "path";
2070
2104
  var LOCALGHOST_PACKAGE_NAME = "@hamedb89/localghost";
2071
- var LOCALGHOST_VERSION = "0.3.0";
2105
+ var LOCALGHOST_VERSION = "0.4.0";
2072
2106
  var UPDATE_CHECK_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
2073
2107
  var UPDATE_CHECK_NOTIFY_TTL_MS = 24 * 60 * 60 * 1e3;
2074
2108
  var UPDATE_CHECK_TIMEOUT_MS = 900;
@@ -2196,7 +2230,8 @@ function formatUpdateMessage(result) {
2196
2230
  if (!result.updateAvailable || !result.latestVersion) return null;
2197
2231
  return [
2198
2232
  `localghost ${result.latestVersion} is available. Current: ${result.currentVersion}`,
2199
- `Update with: npm i -g ${result.packageName}@latest`
2233
+ "Update a repository with: npm exec --yes --package=@hamedb89/localghost -- localghost upgrade",
2234
+ "If Localghost is already installed locally, run: localghost upgrade"
2200
2235
  ].join("\n");
2201
2236
  }
2202
2237
  function shouldNotifyAboutUpdate(result, cachePath = getUpdateCheckCachePath(), now = Date.now()) {
@@ -2508,6 +2543,21 @@ function registerCleanup(id) {
2508
2543
  process.off("exit", cleanup);
2509
2544
  };
2510
2545
  }
2546
+ function registerSignalShutdown(stop) {
2547
+ let requested = false;
2548
+ const request = () => {
2549
+ if (requested) return;
2550
+ requested = true;
2551
+ stop();
2552
+ };
2553
+ process.once("SIGINT", request);
2554
+ process.once("SIGTERM", request);
2555
+ return () => {
2556
+ process.off("SIGINT", request);
2557
+ process.off("SIGTERM", request);
2558
+ request();
2559
+ };
2560
+ }
2511
2561
  async function resolveServiceRuntimeEntries(services, dynamicPort, projectCwd) {
2512
2562
  const usedPorts = /* @__PURE__ */ new Set();
2513
2563
  const resolved = [];
@@ -2563,6 +2613,24 @@ async function waitForPortsToBeAvailable(entries, timeoutMs = 1e4) {
2563
2613
  }
2564
2614
  return false;
2565
2615
  }
2616
+ async function waitForProcessShutdown(processes, forceStop, timeoutMs = 1e4) {
2617
+ const settled = Promise.allSettled(processes);
2618
+ const completed = await Promise.race([
2619
+ settled.then(() => true),
2620
+ wait2(timeoutMs).then(() => false)
2621
+ ]);
2622
+ if (completed) return true;
2623
+ console.warn("Localghost: timed out waiting for managed processes to stop; escalating termination.");
2624
+ forceStop("SIGTERM");
2625
+ const terminated = await Promise.race([
2626
+ settled.then(() => true),
2627
+ wait2(2e3).then(() => false)
2628
+ ]);
2629
+ if (terminated) return true;
2630
+ forceStop("SIGKILL");
2631
+ await Promise.race([settled, wait2(1e3)]);
2632
+ return false;
2633
+ }
2566
2634
  async function runDetectedServices(options) {
2567
2635
  assertLocalDevelopment("run");
2568
2636
  await assertCaddyReady();
@@ -2628,6 +2696,13 @@ async function runDetectedServices(options) {
2628
2696
  });
2629
2697
  const cleanupRun = registerCleanup(runRecord.id);
2630
2698
  const processExit = Promise.race([caddyExit, ...children]);
2699
+ const stopManaged = (signal) => {
2700
+ for (const child of children) {
2701
+ signalManagedProcess(child, signal);
2702
+ }
2703
+ signalManagedProcess(caddy, signal);
2704
+ };
2705
+ const finishShutdown = registerSignalShutdown(() => stopManaged("SIGINT"));
2631
2706
  try {
2632
2707
  const ready = await Promise.race([
2633
2708
  waitForServicePorts(entries),
@@ -2639,13 +2714,17 @@ async function runDetectedServices(options) {
2639
2714
  }
2640
2715
  await processExit;
2641
2716
  } finally {
2642
- for (const child of children) {
2643
- signalManagedProcess(child, "SIGINT");
2644
- }
2645
- signalManagedProcess(caddy, "SIGINT");
2646
- await Promise.allSettled([caddyExit, ...children]);
2717
+ finishShutdown();
2718
+ await waitForProcessShutdown(
2719
+ [caddyExit, ...children],
2720
+ stopManaged
2721
+ );
2647
2722
  if (!await waitForPortsToBeAvailable(entries)) {
2648
2723
  console.warn("Localghost: timed out waiting for service ports to be released.");
2724
+ stopManaged("SIGTERM");
2725
+ if (!await waitForPortsToBeAvailable(entries, 2e3)) {
2726
+ stopManaged("SIGKILL");
2727
+ }
2649
2728
  }
2650
2729
  cleanupRun();
2651
2730
  }
@@ -2746,7 +2825,7 @@ function formatInstanceViews(instances) {
2746
2825
  var program = new Command();
2747
2826
  program.name("localghost").description("Buh. Friendly local hostnames for app repos.").version(LOCALGHOST_VERSION).option("--no-update-check", "Skip the npm update check for this run");
2748
2827
  program.hook("postAction", async (_thisCommand, actionCommand) => {
2749
- if (actionCommand.name() === "update" || actionCommand.name() === "release") return;
2828
+ if (["update", "upgrade", "release"].includes(actionCommand.name())) return;
2750
2829
  const options = program.opts();
2751
2830
  await maybeNotifyAboutUpdate({ disabled: options.updateCheck === false });
2752
2831
  });
@@ -2825,7 +2904,15 @@ program.command("update").description("Check npm for a newer localghost release"
2825
2904
  }
2826
2905
  console.log(`localghost is up to date. Current: ${result.currentVersion}`);
2827
2906
  });
2828
- program.command("release").description("Dispatch an automated Localghost CLI release").argument("<bump>", "Semantic version increment: patch, minor, or major", parseReleaseBump).action(async (bump) => {
2907
+ program.command("upgrade").description("Install or update Localghost in the current repository").option("--cwd <path>", "Consumer repository", process.cwd()).action(async (options) => {
2908
+ const packageManager = detectPackageManager(options.cwd);
2909
+ const packageName = "@hamedb89/localghost@latest";
2910
+ const args = packageManager === "yarn" ? ["add", "--dev", packageName] : packageManager === "pnpm" ? ["add", "--save-dev", packageName] : packageManager === "bun" ? ["add", "--dev", packageName] : ["install", "--save-dev", packageName];
2911
+ console.log(`Upgrading ${packageName} with ${packageManager}...`);
2912
+ await execa4(packageManager, args, { cwd: options.cwd, stdio: "inherit" });
2913
+ console.log(`Localghost is upgraded in ${options.cwd}.`);
2914
+ });
2915
+ program.command("release").description("Dispatch an automated Localghost CLI release").argument("[bump]", "Semantic version increment: patch, minor, or major (default: patch)", parseReleaseBump, "patch").action(async (bump) => {
2829
2916
  const repository = "hamedb89/localghost";
2830
2917
  try {
2831
2918
  await execa4("gh", [
@@ -3223,20 +3310,25 @@ program.command("run").description("Run Caddy and a dev command from the same Lo
3223
3310
  entries: context.entries
3224
3311
  });
3225
3312
  const cleanupRun = registerCleanup(runRecord.id);
3226
- const stopCaddy = () => {
3227
- signalManagedProcess(caddy, "SIGINT");
3228
- };
3229
- const stopChild = () => {
3230
- signalManagedProcess(child, "SIGINT");
3313
+ const stopManaged = (signal) => {
3314
+ signalManagedProcess(child, signal);
3315
+ signalManagedProcess(caddy, signal);
3231
3316
  };
3317
+ const finishShutdown = registerSignalShutdown(() => stopManaged("SIGINT"));
3232
3318
  try {
3233
3319
  await Promise.race([child, caddyExit]);
3234
3320
  } finally {
3235
- stopChild();
3236
- stopCaddy();
3237
- await Promise.allSettled([child, caddyExit]);
3321
+ finishShutdown();
3322
+ await waitForProcessShutdown(
3323
+ [child, caddyExit],
3324
+ stopManaged
3325
+ );
3238
3326
  if (!await waitForPortsToBeAvailable(context.entries)) {
3239
3327
  console.warn("Localghost: timed out waiting for service ports to be released.");
3328
+ stopManaged("SIGTERM");
3329
+ if (!await waitForPortsToBeAvailable(context.entries, 2e3)) {
3330
+ stopManaged("SIGKILL");
3331
+ }
3240
3332
  }
3241
3333
  cleanupRun();
3242
3334
  await context.releasePort?.();
@@ -3415,9 +3507,11 @@ async function main() {
3415
3507
  ...detected.command
3416
3508
  ]);
3417
3509
  }
3418
- main().catch((error) => {
3510
+ main().then(() => {
3511
+ process.exit(process.exitCode ?? 0);
3512
+ }).catch((error) => {
3419
3513
  const message = error instanceof Error ? error.message : String(error);
3420
3514
  console.error(message);
3421
- process.exitCode = 1;
3515
+ process.exit(1);
3422
3516
  });
3423
3517
  //# sourceMappingURL=cli.js.map