/ src / utils / cliHighlight.ts
cliHighlight.ts
 1  // highlight.js's type defs carry `/// <reference lib="dom" />`. SSETransport,
 2  // mcp/client, ssh, dumpPrompts use DOM types (TextDecodeOptions, RequestInfo)
 3  // that only typecheck because this file's `typeof import('highlight.js')` pulls
 4  // lib.dom in. tsconfig has lib: ["ESNext"] only — fixing the actual DOM-type
 5  // deps is a separate sweep; this ref preserves the status quo.
 6  /// <reference lib="dom" />
 7  
 8  import { extname } from 'path'
 9  
10  export type CliHighlight = {
11    highlight: typeof import('cli-highlight').highlight
12    supportsLanguage: typeof import('cli-highlight').supportsLanguage
13  }
14  
15  // One promise shared by Fallback.tsx, markdown.ts, events.ts, getLanguageName.
16  // The highlight.js import piggybacks: cli-highlight has already pulled it into
17  // the module cache, so the second import() is a cache hit — no extra bytes
18  // faulted in.
19  let cliHighlightPromise: Promise<CliHighlight | null> | undefined
20  
21  let loadedGetLanguage: typeof import('highlight.js').getLanguage | undefined
22  
23  async function loadCliHighlight(): Promise<CliHighlight | null> {
24    try {
25      const cliHighlight = await import('cli-highlight')
26      // cache hit — cli-highlight already loaded highlight.js
27      const highlightJs = await import('highlight.js')
28      loadedGetLanguage = highlightJs.getLanguage
29      return {
30        highlight: cliHighlight.highlight,
31        supportsLanguage: cliHighlight.supportsLanguage,
32      }
33    } catch {
34      return null
35    }
36  }
37  
38  export function getCliHighlightPromise(): Promise<CliHighlight | null> {
39    cliHighlightPromise ??= loadCliHighlight()
40    return cliHighlightPromise
41  }
42  
43  /**
44   * eg. "foo/bar.ts" → "TypeScript". Awaits the shared cli-highlight load,
45   * then reads highlight.js's language registry. All callers are telemetry
46   * (OTel counter attributes, permission-dialog unary events) — none block
47   * on this, they fire-and-forget or the consumer already handles Promise<string>.
48   */
49  export async function getLanguageName(file_path: string): Promise<string> {
50    await getCliHighlightPromise()
51    const ext = extname(file_path).slice(1)
52    if (!ext) return 'unknown'
53    return loadedGetLanguage?.(ext)?.name ?? 'unknown'
54  }