All files / src/build build-sockets-bundle.ts

95.12% Statements 39/41
71.42% Branches 10/14
83.33% Functions 5/6
97.5% Lines 39/40

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110                                              1x   1x 1x 1x 1x     1x 1x 1x 1x   1x 1x 1x 1x 1x 1x   1x                   3x   3x 1x     2x 2x 1x     1x 1x 1x   1x 1x       1x   1x   1x     1x       1x 1x               1x   1x 1x 1x 1x                 1x    
/**
 * Builds the sockets bundle for preview/production
 * Scans apiDir for *.socket.ts/js, generates an entry module, bundles with esbuild
 */
 
import * as path from 'path';
import * as fs from 'fs';
import { scanApiDirectory } from '../core/file-system/scan-api-dir.js';
import { patternToRegex } from '../core/normalize/normalize-path.js';
import type { ParsedSocket } from '../core/routing/socket-parser.js';
import { VITEK_SOCKETS_BUNDLE_FILENAME } from '../shared/constants.js';
 
export interface BuildSocketsBundleOptions {
  root: string;
  apiDir: string;
  outDir: string;
}
 
/**
 * Generates the entry file content that imports all socket handlers
 * and exports { sockets } in the shape expected by createSocketHandler
 */
function generateSocketsEntryContent(sockets: ParsedSocket[], entryDir: string): string {
  const lines: string[] = [];
 
  sockets.forEach((parsed, i) => {
    const rel = path.relative(entryDir, parsed.file).replace(/\\/g, '/');
    const importPath = rel.startsWith('.') ? rel : `./${rel}`;
    lines.push(`import handler_${i} from ${JSON.stringify(importPath)};`);
  });
 
  const socketEntries = sockets.map((parsed, i) => {
    const regex = patternToRegex(parsed.pattern);
    const regexSource = regex.source;
    return `  { pattern: ${JSON.stringify(parsed.pattern)}, params: ${JSON.stringify(parsed.params)}, file: ${JSON.stringify(parsed.file)}, regex: new RegExp(${JSON.stringify(regexSource)}), handler: (() => { const m = handler_${i}; return typeof m === 'function' ? m : m.default; })() }`;
  });
  lines.push('');
  lines.push('const sockets = [');
  lines.push(socketEntries.join(',\n'));
  lines.push('];');
  lines.push('');
  lines.push('export { sockets };');
 
  return lines.join('\n');
}
 
/**
 * Builds the sockets bundle and writes it to outDir/vitek-sockets.mjs
 * Returns the path to the written file, or null if skipped (no sockets or error)
 */
export async function buildSocketsBundle(
  options: BuildSocketsBundleOptions
): Promise<string | null> {
  const { apiDir, outDir } = options;
 
  if (!fs.existsSync(apiDir)) {
    return null;
  }
 
  const scanResult = scanApiDirectory(apiDir);
  if (scanResult.sockets.length === 0) {
    return null;
  }
 
  const tmpDir = path.join(outDir, '.vitek-tmp');
  Eif (!fs.existsSync(tmpDir)) {
    fs.mkdirSync(tmpDir, { recursive: true });
  }
  const tmpEntry = path.join(tmpDir, 'vitek-sockets-entry.mts');
  const entryContent = generateSocketsEntryContent(
    scanResult.sockets,
    path.dirname(tmpEntry)
  );
  fs.writeFileSync(tmpEntry, entryContent, 'utf-8');
 
  const outFile = path.join(outDir, VITEK_SOCKETS_BUNDLE_FILENAME);
 
  const esbuild = (await import('esbuild').catch(() => null)) as {
    build: (opts: unknown) => Promise<void>;
  } | null;
  Iif (!esbuild) {
    return null;
  }
 
  try {
    await esbuild.build({
      entryPoints: [tmpEntry],
      bundle: true,
      format: 'esm',
      platform: 'node',
      outfile: outFile,
      external: ['vitek-plugin'],
    });
    return outFile;
  } finally {
    try {
      fs.unlinkSync(tmpEntry);
      Eif (fs.existsSync(tmpDir) && fs.readdirSync(tmpDir).length === 0) {
        fs.rmdirSync(tmpDir);
      }
    } catch {
      // ignore cleanup errors
    }
  }
}
 
export function getSocketsBundleFilename(): string {
  return VITEK_SOCKETS_BUNDLE_FILENAME;
}