Ctrl K
ComunidadGuías…

frontend-modern/vite.config.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { defineConfig } from 'vite';
import tailwindcss from '@tailwindcss/vite';
import solid from 'vite-plugin-solid';
import sri from 'vite-plugin-sri-gen';
import path from 'path';
import { URL } from 'node:url';
import { configDefaults } from 'vitest/config';

const frontendDevHost = process.env.FRONTEND_DEV_HOST ?? '127.0.0.1';
const frontendDevPort = Number(
  process.env.FRONTEND_DEV_PORT ?? process.env.VITE_PORT ?? process.env.PORT ?? 5173,
);

const backendProtocol = process.env.PULSE_DEV_API_PROTOCOL ?? 'http';
const backendHost = process.env.PULSE_DEV_API_HOST ?? '127.0.0.1';
const backendPort = Number(
  process.env.PULSE_DEV_API_PORT ?? process.env.FRONTEND_PORT ?? process.env.PORT ?? 7655,
);

const backendUrl =
  process.env.PULSE_DEV_API_URL ?? `${backendProtocol}://${backendHost}:${backendPort}`;

const backendWsUrl =
  process.env.PULSE_DEV_WS_URL ??
  (() => {
    try {
      const parsed = new URL(backendUrl);
      parsed.protocol = parsed.protocol === 'https:' ? 'wss:' : 'ws:';
      return parsed.toString();
    } catch {
      return backendUrl.replace(/^http:\/\//i, 'ws://').replace(/^https:\/\//i, 'wss://');
    }
  })();

// When the dev server runs on a non-default port (e.g. a preview/tooling
// instance beside the managed 5173 one), the backend's dev origin allowlist
// (http://localhost:5173,http://localhost:7655) rejects WebSocket upgrades.
// Setting PULSE_DEV_PROXY_ORIGIN rewrites the proxied Origin header to an
// allowed one. Unset, nothing changes.
const proxyOriginOverride = process.env.PULSE_DEV_PROXY_ORIGIN ?? '';

const srcAlias = path.resolve(__dirname, './src');

export default defineConfig(({ mode }) => ({
  // preloadDynamicChunks would modulepreload every lazy chunk (~3.1MB raw,
  // the whole app incl. all Settings panels and every locale) at cold start,
  // defeating route-level code splitting on slow devices. Dynamic-import SRI
  // is still enforced through the generated import map integrity block.
  plugins: [tailwindcss(), solid(), sri({ algorithm: 'sha384', preloadDynamicChunks: false })],
  resolve: {
    alias: {
      '@': srcAlias,
    },
    conditions: ['import', 'browser', 'default'],
  },
  server: {
    port: frontendDevPort,
    host: frontendDevHost,
    strictPort: true,
    // Test files are not part of the client app, but once anything requests
    // one through the dev server it enters the client module graph, and every
    // later save of it dead-ends HMR into a full page reload for all connected
    // clients. Parallel agents save test files constantly, so ignore them in
    // the dev watcher. Scoped off test mode so vitest watch re-runs still see
    // file changes.
    ...(mode === 'test'
      ? {}
      : { watch: { ignored: ['**/__tests__/**', '**/*.test.ts', '**/*.test.tsx'] } }),
    proxy: {
      '/ws': {
        target: backendWsUrl,
        ws: true,
        changeOrigin: true,
        // Browser WebSocket connections are long-lived, disable timeouts
        proxyTimeout: 0,
        timeout: 0,
        configure: (proxy, _options) => {
          proxy.options.timeout = 0;
          proxy.options.proxyTimeout = 0;

          proxy.on('proxyReqWs', (proxyReq, req, socket) => {
            if (proxyOriginOverride) proxyReq.setHeader('Origin', proxyOriginOverride);
            socket.setTimeout(0);
            socket.setNoDelay(true);
            socket.setKeepAlive(true, 30000);
          });
          proxy.on('open', (proxySocket) => {
            proxySocket.setTimeout(0);
            proxySocket.setNoDelay(true);
            proxySocket.setKeepAlive(true, 30000);
          });
        },
      },
      // SSE endpoint for AI chat streaming
      '/api/ai/execute/stream': {
        target: backendUrl,
        changeOrigin: true,
        // SSE requires special handling to prevent proxy timeouts
        // Set timeout to 0 to completely disable
        timeout: 0,
        proxyTimeout: 0,
        configure: (proxy, _options) => {
          // Completely disable http-proxy internal timeouts
          proxy.options.timeout = 0;
          proxy.options.proxyTimeout = 0;

          // Set proxy-level timeouts
          proxy.on('proxyReq', (proxyReq, req, res) => {
            // Disable socket timeouts for SSE
            req.socket.setTimeout(0);
            req.socket.setNoDelay(true);
            req.socket.setKeepAlive(true, 30000);
            // Also set on the proxy request
            proxyReq.socket?.setTimeout(0);
          });
          proxy.on('proxyRes', (proxyRes, req, res) => {
            // Disable response socket timeout
            res.socket?.setTimeout(0);
            res.socket?.setNoDelay(true);
            res.socket?.setKeepAlive(true, 30000);
            // Also disable on proxy response socket
            proxyRes.socket?.setTimeout(0);
          });
          proxy.on('error', (err, req, res) => {
            console.error('[SSE Proxy Error]', err.message);
          });
        },
      },
      // SSE endpoint for AI alert investigation (one-click investigate from alerts page)
      '/api/ai/investigate-alert': {
        target: backendUrl,
        changeOrigin: true,
        // SSE requires special handling to prevent proxy timeouts
        timeout: 0,
        proxyTimeout: 0,
        configure: (proxy, _options) => {
          proxy.options.timeout = 0;
          proxy.options.proxyTimeout = 0;

          proxy.on('proxyReq', (proxyReq, req, res) => {
            req.socket.setTimeout(0);
            req.socket.setNoDelay(true);
            req.socket.setKeepAlive(true, 30000);
            proxyReq.socket?.setTimeout(0);
          });
          proxy.on('proxyRes', (proxyRes, req, res) => {
            res.socket?.setTimeout(0);
            res.socket?.setNoDelay(true);
            res.socket?.setKeepAlive(true, 30000);
            proxyRes.socket?.setTimeout(0);
          });
          proxy.on('error', (err, req, res) => {
            console.error('[SSE Proxy Error - Investigate Alert]', err.message);
          });
        },
      },
      // SSE endpoint for AI patrol streaming
      '/api/ai/patrol/stream': {
        target: backendUrl,
        changeOrigin: true,
        timeout: 0,
        proxyTimeout: 0,
        configure: (proxy, _options) => {
          proxy.options.timeout = 0;
          proxy.options.proxyTimeout = 0;

          proxy.on('proxyReq', (proxyReq, req, res) => {
            req.socket.setTimeout(0);
            req.socket.setNoDelay(true);
            req.socket.setKeepAlive(true, 30000);
            proxyReq.socket?.setTimeout(0);
          });
          proxy.on('proxyRes', (proxyRes, req, res) => {
            res.socket?.setTimeout(0);
            res.socket?.setNoDelay(true);
            res.socket?.setKeepAlive(true, 30000);
            proxyRes.socket?.setTimeout(0);
          });
          proxy.on('error', (err, req, res) => {
            console.error('[SSE Proxy Error - Patrol Stream]', err.message);
          });
        },
      },
      '/api/agent/ws': {
        target: backendWsUrl,
        ws: true,
        changeOrigin: true,
        // Agent WebSocket connections are long-lived, disable timeouts
        // proxyTimeout: 0 disables the proxy-to-target timeout
        // timeout: 0 disables the client-to-proxy timeout
        proxyTimeout: 0,
        timeout: 0,
        configure: (proxy, _options) => {
          // Disable http-proxy's internal timeout (default is 2 minutes but seems to be 10s)
          proxy.options.timeout = 0;
          proxy.options.proxyTimeout = 0;

          proxy.on('proxyReqWs', (proxyReq, req, socket) => {
            // Disable socket timeouts for WebSocket connections
            socket.setTimeout(0);
            socket.setNoDelay(true);
            socket.setKeepAlive(true, 30000);
          });
          proxy.on('open', (proxySocket) => {
            // Also disable timeout on the proxy socket
            proxySocket.setTimeout(0);
            proxySocket.setNoDelay(true);
            proxySocket.setKeepAlive(true, 30000);
          });
          proxy.on('error', (err, req, res) => {
            console.error('[Agent WS Proxy Error]', err.message);
          });
        },
      },
      '/api': {
        target: backendUrl,
        changeOrigin: true,
        cookieDomainRewrite: '',
      },
      '/auth/license-purchase-start': {
        target: backendUrl,
        changeOrigin: true,
      },
      '/auth/license-purchase-activate': {
        target: backendUrl,
        changeOrigin: true,
      },
      '/install-container-agent.sh': {
        target: backendUrl,
        changeOrigin: true,
      },
      '/install.sh': {
        target: backendUrl,
        changeOrigin: true,
      },
      '/install.ps1': {
        target: backendUrl,
        changeOrigin: true,
      },
      '/download': {
        target: backendUrl,
        changeOrigin: true,
      },
    },
  },
  build: {
    target: 'esnext',
    rollupOptions: {
      output: {
        // Dynamic-import facades named index.tsx/index.ts all produce chunks
        // called "index", colliding with the real entry chunk in dist/assets
        // and polluting the entry's bundle-size budget line. Name those
        // chunks after the facade's directory instead (Chat, SetupWizard).
        chunkFileNames(chunkInfo) {
          if (chunkInfo.name === 'index' && chunkInfo.facadeModuleId) {
            const dir = chunkInfo.facadeModuleId.split('/').slice(-2, -1)[0];
            if (dir) return `assets/${dir}-[hash].js`;
          }
          return 'assets/[name]-[hash].js';
        },
        manualChunks(id) {
          if (id.includes('node_modules')) {
            if (id.includes('solid-js') || id.includes('@solidjs/router')) return 'vendor-solid';
            if (id.includes('lucide-solid')) return 'vendor-icons';
            if (id.includes('marked') || id.includes('dompurify')) return 'vendor-ai';
            // Only ever dynamically imported (Assistant code-block
            // highlighting on settled turns); folding it into the eager
            // vendor chunk would bill it to first load.
            if (id.includes('highlight.js')) return 'vendor-highlight';
            return 'vendor';
          }

          return undefined;
        },
      },
    },
  },
  optimizeDeps: {
    esbuildOptions: {
      target: 'esnext',
    },
  },
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./src/test/setup.ts'],
    alias: {
      '@': srcAlias,
    },
    exclude: [...configDefaults.exclude, 'tests/integration/**', '**/tests/integration/**'],
  },
}));