From 29c1fee0b4008fa408681ec2338aa5629f1dbcf4 Mon Sep 17 00:00:00 2001 From: bryce <50299051+bryce-seifert@users.noreply.github.com> Date: Sat, 13 Jun 2026 17:47:47 -0700 Subject: [PATCH 1/2] chore: move module watcher from chokidar to fs This prevents EMFILE errors after chokidar version update --- launcher/main.js | 39 ++++++++---------------- tools/dev.mts | 79 +++++++++++++++++++----------------------------- 2 files changed, 43 insertions(+), 75 deletions(-) diff --git a/launcher/main.js b/launcher/main.js index 428d97d6a9..1004053688 100644 --- a/launcher/main.js +++ b/launcher/main.js @@ -3,7 +3,6 @@ import os from 'node:os' import path from 'node:path' import url, { fileURLToPath } from 'node:url' import { getCurrentScope, init } from '@sentry/electron/main' -import chokidar from 'chokidar' import debounceFn from 'debounce-fn' import electron, { app, BrowserWindow, dialog, ipcMain } from 'electron' import Store from 'electron-store' @@ -285,7 +284,7 @@ if (!lock) { let restartCounter = 0 - /** @type {ReturnType | null} */ + /** @type {import('fs').FSWatcher | null} */ let watcher = null /** @type {boolean} */ let pendingWatcher = false @@ -307,34 +306,16 @@ if (!lock) { const newPath0 = uiConfig.get('dev_modules_path') if (newPath0 && (await fs.pathExists(newPath0))) { - // Watch for changes in the modules + // Watch for changes in the module dev folder. const devModulesPath = path.resolve(newPath0) - watcher = chokidar.watch('.', { - ignoreInitial: true, - cwd: devModulesPath, - ignored: (path, stats) => { - if ( - stats?.isFile() && - !path.endsWith('.mjs') && - !path.endsWith('.js') && - !path.endsWith('.cjs') && - !path.endsWith('.json') - ) { - return true - } - if (path.includes('node_modules')) { - return true - } - return false - }, - }) + const allowedExtensions = ['.mjs', '.js', '.cjs', '.json'] + watcher = fs.watch(devModulesPath, { recursive: true, persistent: true }, (_event, filename) => { + if (!filename) return + if (filename.includes('node_modules')) return + if (!allowedExtensions.some((ext) => filename.endsWith(ext))) return - watcher.on('error', (error) => { - customLog(`Watcher error: ${error}`, 'Application') - }) - - watcher.on('all', (event, filename) => { const moduleDirName = filename.split(path.sep)[0] + if (!moduleDirName) return let fn = cachedDebounces[moduleDirName] if (!fn) { @@ -359,6 +340,10 @@ if (!lock) { } fn() }) + + watcher.on('error', (error) => { + customLog(`Watcher error: ${error}`, 'Application') + }) } } catch (e) { customLog(`Failed to restart watcher: ${e}`, 'Application') diff --git a/tools/dev.mts b/tools/dev.mts index b53ccb135a..23bfe8d4b2 100644 --- a/tools/dev.mts +++ b/tools/dev.mts @@ -143,58 +143,41 @@ const mainWatcher = chokidar }) if (devModulesPath) { - // Stagger module watcher startup to avoid FD spike during initialization - mainWatcher.on('ready', () => { - chokidar - .watch('.', { - cwd: devModulesPath, - ignoreInitial: true, - ignored: (filePath, stats) => { - if ( - stats?.isFile() && - !filePath.endsWith('.mjs') && - !filePath.endsWith('.js') && - !filePath.endsWith('.cjs') && - !filePath.endsWith('.json') - ) { - return true + const allowedExtensions = ['.mjs', '.js', '.cjs', '.json'] + const moduleWatcher = fs.watch(devModulesPath, { recursive: true, persistent: true }, (_event, filename) => { + if (!filename) return + if (filename.includes('node_modules')) return + if (!allowedExtensions.some((ext) => filename.endsWith(ext))) return + + const moduleDirName = filename.split(path.sep)[0] + if (!moduleDirName) return + // Module changed + + let fn = cachedDebounces[moduleDirName] + if (!fn) { + fn = debounceFn( + () => { + console.log('Sending reload for module:', moduleDirName) + if (node) { + node.send({ + messageType: 'reload-extra-module', + fullpath: path.join(devModulesPath, moduleDirName), + }) } - if (filePath.includes('node_modules')) { - return true - } - return false }, - }) - .on('all', (event, filename) => { - const moduleDirName = filename.split(path.sep)[0] - // Module changed - - let fn = cachedDebounces[moduleDirName] - if (!fn) { - fn = debounceFn( - () => { - console.log('Sending reload for module:', moduleDirName) - if (node) { - node.send({ - messageType: 'reload-extra-module', - fullpath: path.join(devModulesPath, moduleDirName), - }) - } - }, - { - after: true, - before: false, - wait: 1000, - } - ) - cachedDebounces[moduleDirName] = fn + { + after: true, + before: false, + wait: 1000, } + ) + cachedDebounces[moduleDirName] = fn + } - fn() - }) - .on('error', (error) => { - console.warn(`Module watcher error: ${error}`) - }) + fn() + }) + moduleWatcher.on('error', (error) => { + console.warn(`Module watcher error: ${error}`) }) } From 5f9ff1e976ca9d87c6005b219feee228bcb20667 Mon Sep 17 00:00:00 2001 From: bryce <50299051+bryce-seifert@users.noreply.github.com> Date: Sun, 14 Jun 2026 10:06:05 -0700 Subject: [PATCH 2/2] chore: check path before accessing --- tools/dev.mts | 72 +++++++++++++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 34 deletions(-) diff --git a/tools/dev.mts b/tools/dev.mts index 23bfe8d4b2..9b3da578e9 100644 --- a/tools/dev.mts +++ b/tools/dev.mts @@ -143,42 +143,46 @@ const mainWatcher = chokidar }) if (devModulesPath) { - const allowedExtensions = ['.mjs', '.js', '.cjs', '.json'] - const moduleWatcher = fs.watch(devModulesPath, { recursive: true, persistent: true }, (_event, filename) => { - if (!filename) return - if (filename.includes('node_modules')) return - if (!allowedExtensions.some((ext) => filename.endsWith(ext))) return - - const moduleDirName = filename.split(path.sep)[0] - if (!moduleDirName) return - // Module changed - - let fn = cachedDebounces[moduleDirName] - if (!fn) { - fn = debounceFn( - () => { - console.log('Sending reload for module:', moduleDirName) - if (node) { - node.send({ - messageType: 'reload-extra-module', - fullpath: path.join(devModulesPath, moduleDirName), - }) + if (!fs.existsSync(devModulesPath)) { + console.warn(`Module watcher path does not exist: ${devModulesPath}`) + } else { + const allowedExtensions = ['.mjs', '.js', '.cjs', '.json'] + const moduleWatcher = fs.watch(devModulesPath, { recursive: true, persistent: true }, (_event, filename) => { + if (!filename) return + if (filename.includes('node_modules')) return + if (!allowedExtensions.some((ext) => filename.endsWith(ext))) return + + const moduleDirName = filename.split(path.sep)[0] + if (!moduleDirName) return + // Module changed + + let fn = cachedDebounces[moduleDirName] + if (!fn) { + fn = debounceFn( + () => { + console.log('Sending reload for module:', moduleDirName) + if (node) { + node.send({ + messageType: 'reload-extra-module', + fullpath: path.join(devModulesPath, moduleDirName), + }) + } + }, + { + after: true, + before: false, + wait: 1000, } - }, - { - after: true, - before: false, - wait: 1000, - } - ) - cachedDebounces[moduleDirName] = fn - } + ) + cachedDebounces[moduleDirName] = fn + } - fn() - }) - moduleWatcher.on('error', (error) => { - console.warn(`Module watcher error: ${error}`) - }) + fn() + }) + moduleWatcher.on('error', (error) => { + console.warn(`Module watcher error: ${error}`) + }) + } } async function start() {