From 743ed639e56a037af10385eec01571d966f31205 Mon Sep 17 00:00:00 2001 From: monwiss <547445+monwiss@users.noreply.github.com> Date: Sat, 17 Oct 2026 15:18:32 -0400 Subject: [PATCH] fix(dev): stop the dev server watching .gev-cache and .gev-logs (#761) Providers write cache files and logs into .gev-cache and .gev-logs while the app runs. A serve-only standalone plugin adds both to server.watch.ignored, so the dev watcher no longer stats them; source changes still reload. --- changelog.d/761-dev-unwatched-runtime-dirs.md | 5 ++++ server/standalone/unwatched-cache.js | 28 +++++++++++++++++++ server/standalone/vite.config.js | 2 ++ src/tooling/viteBuild.test.mjs | 25 ++++++++++++++--- 4 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 changelog.d/761-dev-unwatched-runtime-dirs.md create mode 100644 server/standalone/unwatched-cache.js diff --git a/changelog.d/761-dev-unwatched-runtime-dirs.md b/changelog.d/761-dev-unwatched-runtime-dirs.md new file mode 100644 index 000000000..d3aaf0a91 --- /dev/null +++ b/changelog.d/761-dev-unwatched-runtime-dirs.md @@ -0,0 +1,5 @@ +- The dev server no longer watches `.gev-cache` and `.gev-logs`. Providers write + cache files and logs there while the app runs, and none of it is source, so + the watcher now skips it; this guards against the DNS-lookup delays heavy + cache writes have caused, though no difference was measured on current main + (monwiss, #761). diff --git a/server/standalone/unwatched-cache.js b/server/standalone/unwatched-cache.js new file mode 100644 index 000000000..130d0ece4 --- /dev/null +++ b/server/standalone/unwatched-cache.js @@ -0,0 +1,28 @@ +/** + * Directories the app writes to while it runs. None is source, and none + * should reload anything. + */ +const RUNTIME_WRITE_GLOBS = Object.freeze([ + '**/.gev-cache/**', + '**/.gev-logs/**', +]); + +/** + * Keep the directories the app writes to out of the dev server's file watcher. + * + * Disk-cached providers write a file per response into `.gev-cache`, and the + * voice debug log appends to `.gev-logs`. Left watched, each write costs the + * watcher stat calls on libuv's thread pool, which `getaddrinfo` shares; heavy + * cache writes have been seen to delay upstream DNS lookups. + * + * @returns {import('vite').Plugin} Config-only plugin; no server hooks. + */ +export function unwatchedRuntimeDirsPlugin() { + return { + name: 'gev-unwatched-runtime-dirs', + apply: 'serve', + config: () => ({ + server: { watch: { ignored: [...RUNTIME_WRITE_GLOBS] } }, + }), + }; +} diff --git a/server/standalone/vite.config.js b/server/standalone/vite.config.js index a14292597..03a935092 100644 --- a/server/standalone/vite.config.js +++ b/server/standalone/vite.config.js @@ -6,6 +6,7 @@ import { localProviderPlugins } from '../providers/local.js'; import { localMcpPlugin } from '../mcp/plugin.js'; import { apiNotFoundPlugin } from './api-not-found.js'; import { standaloneVoiceTools } from './voiceTools.js'; +import { unwatchedRuntimeDirsPlugin } from './unwatched-cache.js'; const root = fileURLToPath(new URL('../../', import.meta.url)); @@ -20,6 +21,7 @@ export default defineConfig(({ command, mode }) => { ...localProviderPlugins({ realtime: { tools: standaloneVoiceTools() } }), localMcpPlugin(), apiNotFoundPlugin(), + unwatchedRuntimeDirsPlugin(), ], googleApiKey: process.env.GOOGLE_MAPS_API_KEY, cesiumToken: process.env.CESIUM_ION_TOKEN, diff --git a/src/tooling/viteBuild.test.mjs b/src/tooling/viteBuild.test.mjs index 86a9afb63..9e38a0295 100644 --- a/src/tooling/viteBuild.test.mjs +++ b/src/tooling/viteBuild.test.mjs @@ -89,17 +89,34 @@ test('root config retains existing named exports and standalone provider order', assert.equal(compatibility[name], value, name); const config = standaloneConfig({ mode: 'test' }); assert.deepEqual( - config.plugins.slice(3, -4).map((plugin) => plugin.name), + config.plugins.slice(3, -5).map((plugin) => plugin.name), providers.localProviderPlugins().map((plugin) => plugin.name), ); - assert.equal(config.plugins.at(-5).name, 'gev-key-setup'); + assert.equal(config.plugins.at(-6).name, 'gev-key-setup'); // The local MCP route follows every provider and precedes the API fallback. - assert.equal(config.plugins.at(-4).name, 'local-mcp'); - assert.equal(config.plugins.at(-3).name, 'api-not-found'); + assert.equal(config.plugins.at(-5).name, 'local-mcp'); + assert.equal(config.plugins.at(-4).name, 'api-not-found'); + // Config-only, so it sits after the provider slice rather than in it. + assert.equal(config.plugins.at(-3).name, 'gev-unwatched-runtime-dirs'); assert.equal(config.plugins.at(-2).name, 'embed-framing'); assert.equal(config.plugins.at(-1).name, 'panel-build'); }); +test('directories the server writes while it runs are kept out of the dev watcher', () => { + // Providers write cache files and logs here while the app runs; none of it is + // source, so the dev watcher has no reason to stat it. + const config = standaloneConfig({ mode: 'test' }); + const plugin = config.plugins.find( + ({ name }) => name === 'gev-unwatched-runtime-dirs', + ); + // Dev only: preview and build have no watcher. + assert.equal(plugin.apply, 'serve'); + assert.deepEqual(plugin.config().server.watch.ignored, [ + '**/.gev-cache/**', + '**/.gev-logs/**', + ]); +}); + test('build export resolves in Node and has no browser fallback', async () => { const exported = await import('gods-eye-view/build/vite'); assert.equal(exported.createBrowserViteConfig, createBrowserViteConfig);