@fluid-app/fluid-cli-theme-dev 0.1.35 → 0.1.36

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.mjs CHANGED
@@ -6,6 +6,7 @@ import { createHash } from "node:crypto";
6
6
  import http from "node:http";
7
7
  import https from "node:https";
8
8
  import chokidar from "chokidar";
9
+ import net from "node:net";
9
10
  import chalk from "chalk";
10
11
  import prompts from "prompts";
11
12
  import ora from "ora";
@@ -1621,6 +1622,46 @@ var Syncer = class {
1621
1622
  }
1622
1623
  };
1623
1624
  //#endregion
1625
+ //#region src/theme/dev-server/port-preflight.ts
1626
+ /**
1627
+ * The dev command does real work before it ever binds a port: it resolves
1628
+ * (or creates) a server-side dev theme and runs a full initial sync, which
1629
+ * can take minutes on a large theme. If the requested port is already taken
1630
+ * — most commonly by another `fluid theme dev` or the Mist Desktop preview,
1631
+ * which both default to 9292 — all of that work is wasted and the process
1632
+ * used to die with a raw `EADDRINUSE` stack trace. Call this before any of
1633
+ * that work starts so we fail fast with a clear message instead.
1634
+ */
1635
+ var PortInUseError = class extends Error {
1636
+ constructor(host, port) {
1637
+ super(formatPortConflictMessage(host, port));
1638
+ this.host = host;
1639
+ this.port = port;
1640
+ this.name = "PortInUseError";
1641
+ }
1642
+ };
1643
+ function formatPortConflictMessage(host, port) {
1644
+ return `Port ${port} on ${host} is already in use — likely another \`fluid theme dev\` or the Mist Desktop preview. Stop the other server or pass --port <number>.`;
1645
+ }
1646
+ /**
1647
+ * Attempt to bind `host:port`, then immediately release it. Resolves if the
1648
+ * port is free; rejects with `PortInUseError` on `EADDRINUSE`/`EACCES`, or
1649
+ * the raw error for anything else unexpected.
1650
+ */
1651
+ function checkPortAvailable(host, port) {
1652
+ return new Promise((resolve, reject) => {
1653
+ const server = net.createServer();
1654
+ server.once("error", (err) => {
1655
+ if (err.code === "EADDRINUSE" || err.code === "EACCES") reject(new PortInUseError(host, port));
1656
+ else reject(err);
1657
+ });
1658
+ server.once("listening", () => {
1659
+ server.close(() => resolve());
1660
+ });
1661
+ server.listen(port, host);
1662
+ });
1663
+ }
1664
+ //#endregion
1624
1665
  //#region src/theme/dev-server/index.ts
1625
1666
  async function startDevServer(api, theme, themeRoot, opts, onReady) {
1626
1667
  const sse = new SSEStream();
@@ -1689,8 +1730,14 @@ async function startDevServer(api, theme, themeRoot, opts, onReady) {
1689
1730
  }
1690
1731
  });
1691
1732
  await new Promise((resolve, reject) => {
1733
+ server.once("error", (err) => {
1734
+ if (err.code === "EADDRINUSE" || err.code === "EACCES") {
1735
+ console.error(formatPortConflictMessage(opts.host, opts.port));
1736
+ process.exit(1);
1737
+ }
1738
+ reject(err);
1739
+ });
1692
1740
  server.listen(opts.port, opts.host, () => resolve());
1693
- server.on("error", reject);
1694
1741
  });
1695
1742
  const address = `http://${opts.host}:${opts.port}`;
1696
1743
  onReady?.(address);
@@ -1905,6 +1952,13 @@ function createDevCommand() {
1905
1952
  console.error(`Invalid port: '${opts.port}'. Must be an integer between 1 and 65535.`);
1906
1953
  process.exit(1);
1907
1954
  }
1955
+ try {
1956
+ await checkPortAvailable(opts.host, port);
1957
+ } catch (e) {
1958
+ if (e instanceof PortInUseError) console.error(e.message);
1959
+ else console.error(`Failed to check port availability: ${e}`);
1960
+ process.exit(1);
1961
+ }
1908
1962
  const reloadMode = opts.liveReload === "off" ? "off" : "full-page";
1909
1963
  const api = createApiClient();
1910
1964
  const config = readThemeConfig(themeRoot.root);