@nxuss/lemma 1.21.0 → 1.22.1

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.
@@ -2,6 +2,8 @@
2
2
  export declare function startBackgroundClipboardWatcher(cliOpts?: {
3
3
  clipboard?: boolean;
4
4
  }): void;
5
+ export declare const MAX_WORKSPACE_WATCHERS = 512;
6
+ export declare function isUnsafeWatchRoot(dir: string): boolean;
5
7
  export declare function performAutoHeal(apply: boolean): Promise<{
6
8
  success: boolean;
7
9
  message: string;
@@ -1 +1 @@
1
- {"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";AA0cA,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA4DvF;AA4nED,wBAAsB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8KhL"}
1
+ {"version":3,"file":"lemma-proxy.d.ts","sourceRoot":"","sources":["../../../src/cli/lemma-proxy.ts"],"names":[],"mappings":";AA0cA,wBAAgB,+BAA+B,CAAC,OAAO,CAAC,EAAE;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA4DvF;AAqfD,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAGtD;AAyqDD,wBAAsB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA8KhL"}
@@ -953,6 +953,16 @@ class WriteQueue {
953
953
  }
954
954
  }
955
955
  const usageWriteQueue = new WriteQueue();
956
+ // One fs.watch FD is opened per watched directory. Watching $HOME (the launchd
957
+ // daemon's WorkingDirectory) or a filesystem root exhausts FDs (EMFILE), crashes
958
+ // the process, and a KeepAlive supervisor turns that into an infinite
959
+ // crash/relaunch loop — on macOS each relaunch re-triggers the TCC data-access
960
+ // prompt. Refuse those roots outright.
961
+ export const MAX_WORKSPACE_WATCHERS = 512;
962
+ export function isUnsafeWatchRoot(dir) {
963
+ const resolved = path.resolve(dir);
964
+ return resolved === path.resolve(os.homedir()) || resolved === path.parse(resolved).root;
965
+ }
956
966
  // ── Proxy Server class ─────────────────────────────────────────────────────────
957
967
  class LemmaServer {
958
968
  constructor(port, projectName, openBrowser = true) {
@@ -1641,12 +1651,28 @@ class LemmaServer {
1641
1651
  }
1642
1652
  startWorkspaceTimeWatcher() {
1643
1653
  const cwd = process.cwd();
1654
+ if (isUnsafeWatchRoot(cwd)) {
1655
+ console.log(`⏭️ [TimeTravel] Skipping workspace watcher: refusing to watch ${cwd} recursively (file-descriptor exhaustion risk). Start the proxy from a project directory instead.`);
1656
+ return;
1657
+ }
1644
1658
  const { watch } = require('fs');
1645
1659
  // Debounced capture function
1646
1660
  let debounceTimer = null;
1647
1661
  const pendingChanges = new Set();
1662
+ // One FD per watched directory: cap it so giant trees degrade to a warning
1663
+ // instead of an EMFILE crash (which a KeepAlive supervisor turns into a
1664
+ // crash/relaunch loop).
1665
+ let watchCount = 0;
1666
+ let limitWarned = false;
1648
1667
  const ignoreDirs = ['node_modules', '.git', 'dist', 'chroma_data', '.lemma', 'dashboard', 'bin', 'sdks'];
1649
1668
  const watchDirRecursive = (dir) => {
1669
+ if (watchCount >= MAX_WORKSPACE_WATCHERS) {
1670
+ if (!limitWarned) {
1671
+ limitWarned = true;
1672
+ console.log(`⚠️ [TimeTravel] Watcher limit (${MAX_WORKSPACE_WATCHERS} dirs) reached at ${dir} — deeper directories won't trigger snapshots.`);
1673
+ }
1674
+ return;
1675
+ }
1650
1676
  try {
1651
1677
  const watcher = watch(dir, (eventType, filename) => {
1652
1678
  if (!filename)
@@ -1668,6 +1694,19 @@ class LemmaServer {
1668
1694
  // Prevent watcher from keeping process alive if in background
1669
1695
  if (watcher.unref)
1670
1696
  watcher.unref();
1697
+ watchCount++;
1698
+ // An async EMFILE/ENOSPC on a giant tree must never take down the server:
1699
+ // contain it to a single warning and keep serving.
1700
+ watcher.on('error', (err) => {
1701
+ if (!limitWarned) {
1702
+ limitWarned = true;
1703
+ console.log(`⚠️ [TimeTravel] Watcher error (${(err && err.code) || 'unknown'}) — snapshots degraded, server keeps running.`);
1704
+ }
1705
+ try {
1706
+ watcher.close();
1707
+ }
1708
+ catch { }
1709
+ });
1671
1710
  // Recursively watch subfolders
1672
1711
  const files = fs.readdirSync(dir, { withFileTypes: true });
1673
1712
  for (const file of files) {