import-boundaries.test.ts 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import { describe, expect, test } from "bun:test"
  2. import { realpathSync } from "node:fs"
  3. import { mkdtemp, rm } from "node:fs/promises"
  4. import { join, resolve, sep } from "node:path"
  5. const directory = resolve(import.meta.dir, "..")
  6. const effect = realpathSync(resolve(import.meta.dir, "../node_modules/effect"))
  7. const schema = resolve(import.meta.dir, "../../schema")
  8. const protocol = resolve(import.meta.dir, "../../protocol")
  9. const core = resolve(import.meta.dir, "../../core")
  10. const server = resolve(import.meta.dir, "../../server")
  11. describe("public import boundaries", () => {
  12. test("isolates each public entrypoint", async () => {
  13. const root = await bundleInputs("@opencode-ai/client", "browser")
  14. expect(within(root, effect)).toEqual([])
  15. expect(within(root, schema)).toEqual([])
  16. expect(within(root, protocol)).toEqual([])
  17. expect(within(root, core)).toEqual([])
  18. expect(within(root, server)).toEqual([])
  19. const network = await bundleInputs("@opencode-ai/client/effect", "browser")
  20. expect(within(network, effect).length).toBeGreaterThan(0)
  21. expect(within(network, schema).length).toBeGreaterThan(0)
  22. expect(within(network, protocol).length).toBeGreaterThan(0)
  23. expect(within(network, core)).toEqual([])
  24. expect(within(network, server)).toEqual([])
  25. const promiseService = await bundleInputs("@opencode-ai/client/service", "bun")
  26. expect(within(promiseService, effect)).toEqual([])
  27. expect(within(promiseService, schema)).toEqual([])
  28. expect(within(promiseService, protocol)).toEqual([])
  29. expect(within(promiseService, core)).toEqual([])
  30. expect(within(promiseService, server)).toEqual([])
  31. const effectService = await bundleInputs("@opencode-ai/client/effect/service", "bun")
  32. expect(within(effectService, effect).length).toBeGreaterThan(0)
  33. expect(within(effectService, protocol).length).toBeGreaterThan(0)
  34. expect(within(effectService, core)).toEqual([])
  35. expect(within(effectService, server)).toEqual([])
  36. })
  37. })
  38. async function bundleInputs(specifier: string, target: "browser" | "bun") {
  39. const temporary = await mkdtemp(join(import.meta.dir, ".import-boundary-"))
  40. const entrypoint = join(temporary, "index.ts")
  41. const metafile = join(temporary, "meta.json")
  42. try {
  43. await Bun.write(entrypoint, `export * from ${JSON.stringify(specifier)}`)
  44. const child = Bun.spawn(
  45. [
  46. process.execPath,
  47. "build",
  48. entrypoint,
  49. `--target=${target}`,
  50. "--format=esm",
  51. "--packages=bundle",
  52. `--metafile=${metafile}`,
  53. `--outdir=${join(temporary, "out")}`,
  54. ],
  55. { cwd: directory, stdout: "pipe", stderr: "pipe" },
  56. )
  57. const [exitCode, stdout, stderr] = await Promise.all([
  58. child.exited,
  59. new Response(child.stdout).text(),
  60. new Response(child.stderr).text(),
  61. ])
  62. if (exitCode !== 0) throw new Error(stdout + stderr)
  63. const metadata = await Bun.file(metafile).json()
  64. return Object.keys(metadata.inputs).map((input) => resolve(directory, input))
  65. } finally {
  66. await rm(temporary, { recursive: true, force: true })
  67. }
  68. }
  69. function within(inputs: ReadonlyArray<string>, directory: string) {
  70. const prefix = directory.endsWith(sep) ? directory : directory + sep
  71. return inputs.filter((input) => input === directory || input.startsWith(prefix))
  72. }