fixture.ts 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. import { $ } from "bun"
  2. import * as fs from "fs/promises"
  3. import os from "os"
  4. import path from "path"
  5. import type { Config } from "../../src/config/config"
  6. // Strip null bytes from paths (defensive fix for CI environment issues)
  7. function sanitizePath(p: string): string {
  8. return p.replace(/\0/g, "")
  9. }
  10. function exists(dir: string) {
  11. return fs
  12. .stat(dir)
  13. .then(() => true)
  14. .catch(() => false)
  15. }
  16. function clean(dir: string) {
  17. return fs.rm(dir, {
  18. recursive: true,
  19. force: true,
  20. maxRetries: 5,
  21. retryDelay: 100,
  22. })
  23. }
  24. async function stop(dir: string) {
  25. if (!(await exists(dir))) return
  26. await $`git fsmonitor--daemon stop`.cwd(dir).quiet().nothrow()
  27. }
  28. type TmpDirOptions<T> = {
  29. git?: boolean
  30. config?: Partial<Config.Info>
  31. init?: (dir: string) => Promise<T>
  32. dispose?: (dir: string) => Promise<T>
  33. }
  34. export async function tmpdir<T>(options?: TmpDirOptions<T>) {
  35. const dirpath = sanitizePath(path.join(os.tmpdir(), "opencode-test-" + Math.random().toString(36).slice(2)))
  36. await fs.mkdir(dirpath, { recursive: true })
  37. if (options?.git) {
  38. await $`git init`.cwd(dirpath).quiet()
  39. await $`git config core.fsmonitor false`.cwd(dirpath).quiet()
  40. await $`git commit --allow-empty -m "root commit ${dirpath}"`.cwd(dirpath).quiet()
  41. }
  42. if (options?.config) {
  43. await Bun.write(
  44. path.join(dirpath, "opencode.json"),
  45. JSON.stringify({
  46. $schema: "https://opencode.ai/config.json",
  47. ...options.config,
  48. }),
  49. )
  50. }
  51. const realpath = sanitizePath(await fs.realpath(dirpath))
  52. const extra = await options?.init?.(realpath)
  53. const result = {
  54. [Symbol.asyncDispose]: async () => {
  55. try {
  56. await options?.dispose?.(realpath)
  57. } finally {
  58. if (options?.git) await stop(realpath).catch(() => undefined)
  59. await clean(realpath).catch(() => undefined)
  60. }
  61. },
  62. path: realpath,
  63. extra: extra as T,
  64. }
  65. return result
  66. }