extractFromBunfs.js 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
  1. /* eslint-disable custom-rules/no-sync-fs */
  2. // Extracts a file from Bun's $bunfs virtual filesystem to a real temp directory
  3. // so it can be spawned as a subprocess (child processes cannot access $bunfs).
  4. //
  5. // Separated from embed.js so the logic is testable without Bun-specific
  6. // `import ... with { type: 'file' }` syntax.
  7. import { createHash } from 'crypto'
  8. import {
  9. chmodSync,
  10. mkdirSync,
  11. readFileSync,
  12. renameSync,
  13. writeFileSync,
  14. } from 'fs'
  15. import { tmpdir } from 'os'
  16. import { join } from 'path'
  17. /**
  18. * If `embeddedPath` is inside Bun's $bunfs virtual filesystem, extract it to a
  19. * temp directory and return the real path. Otherwise return the path unchanged.
  20. *
  21. * Uses a content hash for the directory name so that:
  22. * - Same binary version reuses the same extracted file (no accumulation)
  23. * - Different versions get separate directories (no collision)
  24. * - Concurrent instances are safe (atomic write via temp file + rename)
  25. *
  26. * @param {string} embeddedPath — path returned by `import ... with { type: 'file' }`
  27. * @returns {string} — a real filesystem path that can be spawned as a subprocess
  28. */
  29. export function extractFromBunfs(embeddedPath) {
  30. if (!embeddedPath.includes('$bunfs')) {
  31. return embeddedPath
  32. }
  33. try {
  34. const content = readFileSync(embeddedPath)
  35. const hash = createHash('sha256').update(content).digest('hex').slice(0, 16)
  36. const tmpDir = join(tmpdir(), `claude-agent-sdk-${hash}`)
  37. const tmpPath = join(tmpDir, 'cli.js')
  38. mkdirSync(tmpDir, { recursive: true })
  39. // Write to a temp file and atomically rename to avoid truncation races —
  40. // concurrent readers always see either the old complete file or the new one.
  41. const tmpFile = join(tmpDir, `cli.js.tmp.${process.pid}`)
  42. writeFileSync(tmpFile, content)
  43. chmodSync(tmpFile, 0o755)
  44. renameSync(tmpFile, tmpPath)
  45. return tmpPath
  46. } catch (err) {
  47. // biome-ignore lint/suspicious/noConsole: intentional user-facing warning in standalone SDK helper
  48. console.warn(
  49. `[claude-agent-sdk] Failed to extract CLI from $bunfs: ${err.message}. ` +
  50. `Child processes cannot access $bunfs paths — the CLI will likely fail to start.`,
  51. )
  52. return embeddedPath
  53. }
  54. }