2026-06-02 05:54:10 +00:00
|
|
|
import fs from "fs/promises"
|
|
|
|
|
import path from "path"
|
2026-06-09 18:28:45 +00:00
|
|
|
import { describe, expect } from "bun:test"
|
|
|
|
|
import { Effect, Exit, Layer } from "effect"
|
2026-06-27 18:10:07 +00:00
|
|
|
import { LayerNode } from "@opencode-ai/core/effect/layer-node"
|
2026-06-09 18:28:45 +00:00
|
|
|
import { FileSystem } from "@opencode-ai/core/filesystem"
|
2026-06-02 05:54:10 +00:00
|
|
|
import { Location } from "@opencode-ai/core/location"
|
|
|
|
|
import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema"
|
|
|
|
|
import { location } from "./fixture/location"
|
2026-06-09 18:28:45 +00:00
|
|
|
import { tmpdir } from "./fixture/tmpdir"
|
2026-06-02 05:54:10 +00:00
|
|
|
import { it } from "./lib/effect"
|
|
|
|
|
|
2026-06-10 00:38:02 +00:00
|
|
|
const provide = (directory: string) =>
|
|
|
|
|
Effect.provide(
|
2026-06-29 03:49:43 +00:00
|
|
|
LayerNode.compile(FileSystem.node, [
|
2026-06-29 03:48:18 +00:00
|
|
|
[
|
2026-06-29 03:49:43 +00:00
|
|
|
Location.node,
|
|
|
|
|
Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make(directory) }))),
|
2026-06-29 03:48:18 +00:00
|
|
|
],
|
2026-06-29 03:49:43 +00:00
|
|
|
]),
|
2026-06-02 05:54:10 +00:00
|
|
|
)
|
|
|
|
|
|
2026-06-10 00:38:02 +00:00
|
|
|
const withTmp = <A, E, R>(f: (directory: string) => Effect.Effect<A, E, R>) =>
|
|
|
|
|
Effect.acquireRelease(
|
2026-06-02 05:54:10 +00:00
|
|
|
Effect.promise(() => tmpdir()),
|
|
|
|
|
(tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
|
2026-06-02 15:29:07 +00:00
|
|
|
).pipe(Effect.flatMap((tmp) => f(tmp.path)))
|
2026-06-02 05:54:10 +00:00
|
|
|
|
2026-06-02 20:09:26 +00:00
|
|
|
describe("FileSystem", () => {
|
2026-06-10 00:38:02 +00:00
|
|
|
it.live("reads text and binary files", () =>
|
2026-06-02 05:54:10 +00:00
|
|
|
withTmp((directory) =>
|
|
|
|
|
Effect.gen(function* () {
|
2026-06-10 00:38:02 +00:00
|
|
|
yield* Effect.promise(() => fs.writeFile(path.join(directory, "text.txt"), "hello"))
|
2026-06-02 05:54:10 +00:00
|
|
|
yield* Effect.promise(() => fs.writeFile(path.join(directory, "data.bin"), Buffer.from([0, 1, 2])))
|
2026-06-02 20:09:26 +00:00
|
|
|
const service = yield* FileSystem.Service
|
2026-06-10 00:38:02 +00:00
|
|
|
const text = yield* service.read({ path: RelativePath.make("text.txt") })
|
|
|
|
|
const binary = yield* service.read({ path: RelativePath.make("data.bin") })
|
2026-06-11 15:20:11 +00:00
|
|
|
expect(new TextDecoder().decode(text.content)).toBe("hello")
|
|
|
|
|
expect(text.mime).toBe("text/plain")
|
|
|
|
|
expect(binary.content).toEqual(new Uint8Array([0, 1, 2]))
|
2026-06-05 23:48:34 +00:00
|
|
|
}).pipe(provide(directory)),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
2026-06-10 00:38:02 +00:00
|
|
|
it.live("lists direct children", () =>
|
2026-06-02 05:54:10 +00:00
|
|
|
withTmp((directory) =>
|
|
|
|
|
Effect.gen(function* () {
|
|
|
|
|
yield* Effect.promise(() => fs.mkdir(path.join(directory, "src")))
|
|
|
|
|
yield* Effect.promise(() => fs.writeFile(path.join(directory, "README.md"), "# Test"))
|
2026-06-09 18:28:45 +00:00
|
|
|
const entries = yield* (yield* FileSystem.Service).list()
|
2026-06-10 00:38:02 +00:00
|
|
|
expect(entries.map((entry) => ({ path: entry.path, type: entry.type }))).toEqual([
|
|
|
|
|
{ path: RelativePath.make("src" + path.sep), type: "directory" },
|
|
|
|
|
{ path: RelativePath.make("README.md"), type: "file" },
|
2026-06-02 05:54:10 +00:00
|
|
|
])
|
2026-06-02 15:29:07 +00:00
|
|
|
}).pipe(provide(directory)),
|
2026-06-02 05:54:10 +00:00
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
2026-06-10 00:38:02 +00:00
|
|
|
it.live("rejects lexical escapes", () =>
|
2026-06-04 03:02:17 +00:00
|
|
|
withTmp((directory) =>
|
|
|
|
|
Effect.gen(function* () {
|
2026-06-10 00:38:02 +00:00
|
|
|
const result = yield* (yield* FileSystem.Service)
|
|
|
|
|
.read({ path: RelativePath.make("../outside.txt") })
|
|
|
|
|
.pipe(Effect.exit)
|
|
|
|
|
expect(Exit.isFailure(result)).toBe(true)
|
2026-06-02 15:29:07 +00:00
|
|
|
}).pipe(provide(directory)),
|
2026-06-02 05:54:10 +00:00
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
})
|