| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- import fs from "fs/promises"
- import path from "path"
- import { fileURLToPath } from "url"
- import { describe, expect, test } from "bun:test"
- import { Effect, Exit, Fiber, Layer, Schema } from "effect"
- import { FSUtil } from "@opencode-ai/core/fs-util"
- import { Location } from "@opencode-ai/core/location"
- import { FileSystem } from "@opencode-ai/core/filesystem"
- import { Ripgrep } from "@opencode-ai/core/filesystem/ripgrep"
- import { ProjectReference } from "@opencode-ai/core/project-reference"
- import { Repository } from "@opencode-ai/core/repository"
- import { Global } from "@opencode-ai/core/global"
- import { AbsolutePath, RelativePath } from "@opencode-ai/core/schema"
- import { tmpdir } from "./fixture/tmpdir"
- import { location } from "./fixture/location"
- import { it } from "./lib/effect"
- const inertReferences = ProjectReference.Service.of({
- list: () => Effect.succeed([]),
- get: () => Effect.succeed(undefined),
- resolveMention: () => Effect.succeed(undefined),
- ensurePath: () => Effect.void,
- containsManagedPath: () => Effect.succeed(false),
- })
- function provide(
- directory: string,
- references = inertReferences,
- filesystem = FSUtil.defaultLayer,
- data = Global.Path.data,
- ) {
- return Effect.provide(
- FileSystem.layer.pipe(
- Layer.provide(
- Layer.mergeAll(
- filesystem,
- Ripgrep.defaultLayer,
- Layer.succeed(Location.Service, Location.Service.of(location({ directory: AbsolutePath.make(directory) }))),
- Layer.succeed(ProjectReference.Service, references),
- Global.layerWith({ data }),
- ),
- ),
- ),
- )
- }
- function withTmp<A, E, R>(f: (directory: string) => Effect.Effect<A, E, R>) {
- return Effect.acquireRelease(
- Effect.promise(() => tmpdir()),
- (tmp) => Effect.promise(() => tmp[Symbol.asyncDispose]()),
- ).pipe(Effect.flatMap((tmp) => f(tmp.path)))
- }
- describe("FileSystem", () => {
- it.live("accepts generated managed output paths and rejects other absolute paths", () =>
- withTmp((directory) => {
- const worktree = directory
- const data = path.join(directory, "data")
- return Effect.gen(function* () {
- const managed = path.join(data, "tool-output")
- const output = path.join(managed, "tool_123")
- const unrelated = path.join(directory, "secret.txt")
- yield* Effect.promise(() => fs.mkdir(managed, { recursive: true }))
- yield* Effect.promise(() => fs.writeFile(output, "failure here"))
- yield* Effect.promise(() => fs.writeFile(unrelated, "secret"))
- const service = yield* FileSystem.Service
- expect(yield* service.read({ path: output })).toMatchObject({ type: "text", content: "failure here" })
- expect((yield* service.resolveRoot({ path: output })).real).toBe(output)
- expect(yield* Effect.exit(service.read({ path: unrelated }))).toMatchObject({ _tag: "Failure" })
- expect(yield* Effect.exit(service.read({ path: managed }))).toMatchObject({ _tag: "Failure" })
- }).pipe(provide(worktree, inertReferences, FSUtil.defaultLayer, data))
- }),
- )
- it.live("reads text and binary files", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- yield* Effect.promise(() => fs.writeFile(path.join(directory, "hello.txt"), "hello"))
- yield* Effect.promise(() => fs.writeFile(path.join(directory, "data.bin"), Buffer.from([0, 1, 2])))
- const service = yield* FileSystem.Service
- expect(yield* service.read({ path: RelativePath.make("hello.txt") })).toEqual({
- type: "text",
- content: "hello",
- mime: "text/plain",
- })
- expect(yield* service.read({ path: RelativePath.make("data.bin") })).toEqual({
- type: "binary",
- content: "AAEC",
- encoding: "base64",
- mime: "application/octet-stream",
- })
- const binary = yield* service.resolveRead({ path: RelativePath.make("data.bin") })
- expect(Exit.isFailure(yield* service.readTextPageResolved(binary).pipe(Effect.exit))).toBe(true)
- }).pipe(provide(directory)),
- ),
- )
- it.live("revalidates file identity before sampled classification", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- const file = path.join(directory, "image.png")
- yield* Effect.promise(() => fs.writeFile(file, Buffer.from([0x89, 0x50, 0x4e, 0x47])))
- const service = yield* FileSystem.Service
- const target = yield* service.resolveRead({ path: RelativePath.make("image.png") })
- yield* Effect.promise(() => fs.rename(file, path.join(directory, "original.png")))
- yield* Effect.promise(() => fs.writeFile(file, Buffer.from([0xff, 0xd8, 0xff])))
- expect(
- Exit.isFailure(yield* service.readSampleResolved(target, FileSystem.READ_SAMPLE_BYTES).pipe(Effect.exit)),
- ).toBe(true)
- }).pipe(provide(directory)),
- ),
- )
- it.live("pages large UTF-8 text files by line with continuation", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- const lines = Array.from({ length: 30 }, (_, index) => `line-${index + 1}-é`.padEnd(2_000, "x"))
- yield* Effect.promise(() => fs.writeFile(path.join(directory, "large.txt"), lines.join("\n")))
- const service = yield* FileSystem.Service
- const target = yield* service.resolveRead({ path: RelativePath.make("large.txt") })
- const first = yield* service.readTextPageResolved(target)
- expect(first).toMatchObject({
- type: "text-page",
- offset: 1,
- truncated: true,
- })
- expect(first.next).toBeDefined()
- const next = first.next!
- expect(yield* service.readTextPageResolved(target, { offset: next, limit: 1 })).toEqual({
- type: "text-page",
- content: lines[next - 1],
- mime: "text/plain",
- offset: next,
- truncated: true,
- next: next + 1,
- })
- expect(yield* service.readTextPageResolved(target, { offset: 30 })).toEqual({
- type: "text-page",
- content: lines[29],
- mime: "text/plain",
- offset: 30,
- truncated: false,
- })
- }).pipe(provide(directory)),
- ),
- )
- it.live("rejects paged text when a late NUL appears after the requested page", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- const file = path.join(directory, "late-binary.txt")
- yield* Effect.promise(() =>
- fs.writeFile(
- file,
- Buffer.concat([Buffer.from("first\nsecond\n"), Buffer.alloc(80_000, 0x61), Buffer.from([0])]),
- ),
- )
- const service = yield* FileSystem.Service
- const target = yield* service.resolveRead({ path: RelativePath.make("late-binary.txt") })
- expect(Exit.isFailure(yield* service.readToolResolved(target, { limit: 1 }).pipe(Effect.exit))).toBe(true)
- }).pipe(provide(directory)),
- ),
- )
- it.live("rejects paged text when invalid UTF-8 appears near EOF", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- const file = path.join(directory, "invalid-utf8.txt")
- yield* Effect.promise(() =>
- fs.writeFile(
- file,
- Buffer.concat([Buffer.from("first\nsecond\n"), Buffer.alloc(80_000, 0x61), Buffer.from([0xc3, 0x28])]),
- ),
- )
- const service = yield* FileSystem.Service
- const target = yield* service.resolveRead({ path: RelativePath.make("invalid-utf8.txt") })
- expect(Exit.isFailure(yield* service.readToolResolved(target, { limit: 1 }).pipe(Effect.exit))).toBe(true)
- }).pipe(provide(directory)),
- ),
- )
- it.live("rejects PDFs for direct, large, and paged reads", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- const small = path.join(directory, "small.pdf")
- const large = path.join(directory, "large.pdf")
- yield* Effect.promise(() => fs.writeFile(small, "%PDF-1.7\nsmall"))
- yield* Effect.promise(() =>
- fs.writeFile(large, Buffer.concat([Buffer.from("%PDF-1.7\n"), Buffer.alloc(80_000)])),
- )
- const service = yield* FileSystem.Service
- const smallTarget = yield* service.resolveRead({ path: RelativePath.make("small.pdf") })
- const largeTarget = yield* service.resolveRead({ path: RelativePath.make("large.pdf") })
- expect(Exit.isFailure(yield* service.readToolResolved(smallTarget).pipe(Effect.exit))).toBe(true)
- expect(Exit.isFailure(yield* service.readToolResolved(largeTarget).pipe(Effect.exit))).toBe(true)
- expect(Exit.isFailure(yield* service.readToolResolved(largeTarget, { limit: 1 }).pipe(Effect.exit))).toBe(true)
- }).pipe(provide(directory)),
- ),
- )
- it.live("rejects signature-bearing media beyond the ingestion cap before loading", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- const file = path.join(directory, "huge.png")
- yield* Effect.promise(async () => {
- const handle = await fs.open(file, "w")
- try {
- await handle.write(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), 0, 8, 0)
- await handle.truncate(FileSystem.MAX_MEDIA_INGEST_BYTES + 1)
- } finally {
- await handle.close()
- }
- })
- const service = yield* FileSystem.Service
- const target = yield* service.resolveRead({ path: RelativePath.make("huge.png") })
- const exit = yield* service.readToolResolved(target).pipe(Effect.exit)
- expect(Exit.isFailure(exit)).toBe(true)
- if (Exit.isFailure(exit)) expect(String(exit.cause)).toContain("Media exceeds")
- }).pipe(provide(directory)),
- ),
- )
- it.live("never mixes a sampled image with replacement-path content", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- const file = path.join(directory, "race.png")
- const moved = path.join(directory, "original.png")
- const original = Buffer.concat([
- Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
- Buffer.alloc(4 * 1024 * 1024, 0x11),
- ])
- const replacement = Buffer.concat([Buffer.from([0xff, 0xd8, 0xff]), Buffer.alloc(1024, 0x22)])
- yield* Effect.promise(() => fs.writeFile(file, original))
- const service = yield* FileSystem.Service
- const target = yield* service.resolveRead({ path: RelativePath.make("race.png") })
- const reading = yield* service.readToolResolved(target).pipe(Effect.forkChild)
- yield* Effect.promise(async () => {
- await fs.rename(file, moved)
- await fs.writeFile(file, replacement)
- })
- const exit = yield* Fiber.join(reading).pipe(Effect.exit)
- if (Exit.isSuccess(exit)) {
- expect(exit.value).toMatchObject({ type: "binary", mime: "image/png" })
- if (exit.value.type === "binary") expect(exit.value.content).toBe(original.toString("base64"))
- }
- }).pipe(provide(directory)),
- ),
- )
- it.live("closes validated descriptors after successful and failed reads", () =>
- withTmp((directory) => {
- let active = 0
- const filesystem = Layer.effect(
- FSUtil.Service,
- Effect.gen(function* () {
- const service = yield* FSUtil.Service
- return FSUtil.Service.of({
- ...service,
- open: (target, options) =>
- Effect.acquireRelease(
- service.open(target, options).pipe(Effect.tap(() => Effect.sync(() => active++))),
- () => Effect.sync(() => active--),
- ),
- })
- }),
- ).pipe(Layer.provide(FSUtil.defaultLayer))
- return Effect.gen(function* () {
- const text = path.join(directory, "text.txt")
- const binary = path.join(directory, "binary.pdf")
- yield* Effect.promise(() => fs.writeFile(text, "hello"))
- yield* Effect.promise(() => fs.writeFile(binary, "%PDF-1.7"))
- const service = yield* FileSystem.Service
- const before =
- process.platform === "win32"
- ? undefined
- : yield* Effect.promise(() => fs.readdir("/dev/fd").then((entries) => entries.length))
- for (let index = 0; index < 50; index++) {
- yield* service.readToolResolved(yield* service.resolveRead({ path: RelativePath.make("text.txt") }))
- yield* service
- .readToolResolved(yield* service.resolveRead({ path: RelativePath.make("binary.pdf") }))
- .pipe(Effect.exit)
- }
- expect(active).toBe(0)
- if (before !== undefined) {
- const after = yield* Effect.promise(() => fs.readdir("/dev/fd").then((entries) => entries.length))
- expect(after).toBeLessThanOrEqual(before + 2)
- }
- yield* Effect.promise(() => fs.rename(text, text + ".moved"))
- yield* Effect.promise(() => fs.rename(binary, binary + ".moved"))
- }).pipe(provide(directory, inertReferences, filesystem))
- }),
- )
- it.live("lists direct children with relative paths and resolved URIs", () =>
- 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"))
- const service = yield* FileSystem.Service
- const entries = yield* service.list()
- expect(entries.map(({ uri: _uri, ...entry }) => entry)).toEqual([
- {
- path: RelativePath.make("src"),
- type: "directory",
- mime: "application/x-directory",
- },
- {
- path: RelativePath.make("README.md"),
- type: "file",
- mime: "text/markdown",
- },
- ])
- expect(
- yield* Effect.promise(() => Promise.all(entries.map((entry) => fs.realpath(fileURLToPath(entry.uri))))),
- ).toEqual(
- yield* Effect.promise(() =>
- Promise.all([fs.realpath(path.join(directory, "src")), fs.realpath(path.join(directory, "README.md"))]),
- ),
- )
- }).pipe(provide(directory)),
- ),
- )
- it.live("lists stable bounded pages", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- yield* Effect.promise(async () => {
- await fs.mkdir(path.join(directory, "src"))
- await fs.writeFile(path.join(directory, "README.md"), "# Test")
- })
- const service = yield* FileSystem.Service
- expect(yield* service.listPage({ limit: 1 })).toMatchObject({
- entries: [{ path: "src", type: "directory" }],
- truncated: true,
- next: 2,
- })
- expect(yield* service.listPage({ offset: 2, limit: 1 })).toMatchObject({
- entries: [{ path: "README.md", type: "file" }],
- truncated: false,
- })
- expect((yield* service.resolveList()).resource).toBe(".")
- }).pipe(provide(directory)),
- ),
- )
- it.live("materializes only the selected direct children for a page", () =>
- withTmp((directory) => {
- const realPaths: string[] = []
- const filesystem = Layer.effect(
- FSUtil.Service,
- Effect.gen(function* () {
- const service = yield* FSUtil.Service
- return FSUtil.Service.of({
- ...service,
- realPath: (target) =>
- Effect.sync(() => realPaths.push(target)).pipe(Effect.andThen(service.realPath(target))),
- })
- }),
- ).pipe(Layer.provide(FSUtil.defaultLayer))
- return Effect.gen(function* () {
- yield* Effect.promise(async () => {
- await fs.mkdir(path.join(directory, "src"))
- await fs.writeFile(path.join(directory, "alpha.txt"), "alpha")
- await fs.writeFile(path.join(directory, "beta.txt"), "beta")
- })
- const service = yield* FileSystem.Service
- expect(yield* service.listPage({ offset: 2, limit: 1 })).toMatchObject({
- entries: [{ path: "alpha.txt", type: "file" }],
- truncated: true,
- next: 3,
- })
- expect(realPaths.filter((target) => target !== directory)).toEqual([path.join(directory, "alpha.txt")])
- }).pipe(provide(directory, inertReferences, filesystem))
- }),
- )
- it.live("materializes selected page entries with at most 16 concurrent real path lookups", () =>
- withTmp((directory) => {
- let active = 0
- let maximum = 0
- const filesystem = Layer.effect(
- FSUtil.Service,
- Effect.gen(function* () {
- const service = yield* FSUtil.Service
- return FSUtil.Service.of({
- ...service,
- realPath: (target) =>
- target === directory
- ? service.realPath(target)
- : Effect.acquireUseRelease(
- Effect.sync(() => {
- active++
- maximum = Math.max(maximum, active)
- }),
- () => Effect.sleep("10 millis").pipe(Effect.andThen(service.realPath(target))),
- () => Effect.sync(() => active--),
- ),
- })
- }),
- ).pipe(Layer.provide(FSUtil.defaultLayer))
- return Effect.gen(function* () {
- yield* Effect.promise(() =>
- Promise.all(Array.from({ length: 32 }, (_, index) => fs.writeFile(path.join(directory, `${index}.txt`), ""))),
- )
- const service = yield* FileSystem.Service
- expect((yield* service.listPage({ limit: 32 })).entries).toHaveLength(32)
- expect(maximum).toBe(16)
- }).pipe(provide(directory, inertReferences, filesystem))
- }),
- )
- it.live("caps direct list page service calls at 2000 entries", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- yield* Effect.promise(() =>
- Promise.all(
- Array.from({ length: 2_001 }, (_, index) =>
- fs.writeFile(path.join(directory, `${index.toString().padStart(4, "0")}.txt`), ""),
- ),
- ),
- )
- const service = yield* FileSystem.Service
- const target = yield* service.resolveList()
- expect((yield* service.listPageResolved(target, { limit: 2_001 })).entries).toHaveLength(2_000)
- }).pipe(provide(directory)),
- ),
- )
- test("rejects empty list aliases and page limits over 2000", () => {
- const decode = Schema.decodeUnknownSync(FileSystem.ListPageInput)
- expect(() => decode({ reference: "" })).toThrow()
- expect(() => decode({ limit: 2_001 })).toThrow()
- })
- it.live("rejects escaping list paths and omits escaping symlink children", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- if (process.platform === "win32") return
- const outside = `${directory}-outside`
- yield* Effect.promise(async () => {
- await fs.mkdir(outside)
- await fs.writeFile(path.join(outside, "secret.txt"), "secret")
- await fs.symlink(outside, path.join(directory, "escape"))
- })
- const service = yield* FileSystem.Service
- expect(
- Exit.isFailure(yield* service.listPage({ path: RelativePath.make("../outside") }).pipe(Effect.exit)),
- ).toBe(true)
- expect((yield* service.listPage()).entries).toEqual([])
- yield* Effect.promise(() => fs.rm(outside, { recursive: true, force: true }))
- }).pipe(provide(directory)),
- ),
- )
- it.live("paginates visible entries after omitting escaping symlink children", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- if (process.platform === "win32") return
- const outside = `${directory}-outside`
- yield* Effect.promise(async () => {
- await fs.mkdir(outside)
- await fs.symlink(outside, path.join(directory, "a-escape"))
- await fs.writeFile(path.join(directory, "b-visible.txt"), "visible")
- })
- const service = yield* FileSystem.Service
- expect(yield* service.listPage({ limit: 1 })).toMatchObject({
- entries: [{ path: "b-visible.txt", type: "file" }],
- truncated: false,
- })
- yield* Effect.promise(() => fs.rm(outside, { recursive: true, force: true }))
- }).pipe(provide(directory)),
- ),
- )
- it.live("rejects paths outside the location", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- const service = yield* FileSystem.Service
- expect(
- Exit.isFailure(yield* service.read({ path: RelativePath.make("../outside.txt") }).pipe(Effect.exit)),
- ).toBe(true)
- }).pipe(provide(directory)),
- ),
- )
- it.live("reads and lists paths relative to a local project reference", () =>
- withTmp((directory) => {
- const docs = path.join(directory, "docs")
- return Effect.gen(function* () {
- yield* Effect.promise(async () => {
- await fs.mkdir(docs)
- await fs.writeFile(path.join(docs, "README.md"), "docs")
- })
- const service = yield* FileSystem.Service
- expect(yield* service.read({ reference: "docs", path: RelativePath.make("README.md") })).toMatchObject({
- type: "text",
- content: "docs",
- })
- expect(yield* service.list({ reference: "docs" })).toMatchObject([{ path: "README.md", type: "file" }])
- }).pipe(provide(directory, references({ docs: { name: "docs", kind: "local", path: docs } })))
- }),
- )
- it.live("materializes Git references before filesystem access", () =>
- withTmp((directory) => {
- const docs = path.join(directory, "docs")
- const ensured: string[] = []
- return Effect.gen(function* () {
- yield* Effect.promise(async () => {
- await fs.mkdir(docs)
- await fs.writeFile(path.join(docs, "README.md"), "docs")
- })
- expect(
- yield* (yield* FileSystem.Service).read({ reference: "sdk", path: RelativePath.make("README.md") }),
- ).toMatchObject({ content: "docs" })
- expect(ensured).toEqual([docs])
- }).pipe(
- provide(
- directory,
- references(
- {
- sdk: {
- name: "sdk",
- kind: "git",
- repository: "owner/repo",
- reference: Repository.parseRemote("owner/repo"),
- path: docs,
- },
- },
- (target) => Effect.sync(() => ensured.push(target ?? "")),
- ),
- ),
- )
- }),
- )
- it.live("rejects unknown, invalid, and escaping project reference paths", () =>
- withTmp((directory) => {
- const docs = path.join(directory, "docs")
- return Effect.gen(function* () {
- yield* Effect.promise(() => fs.mkdir(docs))
- const service = yield* FileSystem.Service
- expect(Exit.isFailure(yield* service.list({ reference: "unknown" }).pipe(Effect.exit))).toBe(true)
- expect(Exit.isFailure(yield* service.list({ reference: "invalid" }).pipe(Effect.exit))).toBe(true)
- expect(
- Exit.isFailure(
- yield* service.read({ reference: "docs", path: RelativePath.make("../outside") }).pipe(Effect.exit),
- ),
- ).toBe(true)
- }).pipe(
- provide(
- directory,
- references({
- docs: { name: "docs", kind: "local", path: docs },
- invalid: { name: "invalid", kind: "invalid", message: "invalid reference" },
- }),
- ),
- )
- }),
- )
- it.live("rejects aliases when project references are disabled", () =>
- withTmp((directory) =>
- Effect.gen(function* () {
- expect(Exit.isFailure(yield* (yield* FileSystem.Service).list({ reference: "docs" }).pipe(Effect.exit))).toBe(
- true,
- )
- }).pipe(provide(directory)),
- ),
- )
- it.live("rejects symlink escapes from project references", () =>
- withTmp((directory) => {
- const docs = path.join(directory, "docs")
- const outside = path.join(directory, "outside.txt")
- return Effect.gen(function* () {
- if (process.platform === "win32") return
- yield* Effect.promise(async () => {
- await fs.mkdir(docs)
- await fs.writeFile(outside, "outside")
- await fs.symlink(outside, path.join(docs, "link.txt"))
- })
- expect(
- Exit.isFailure(
- yield* (yield* FileSystem.Service)
- .read({ reference: "docs", path: RelativePath.make("link.txt") })
- .pipe(Effect.exit),
- ),
- ).toBe(true)
- }).pipe(provide(directory, references({ docs: { name: "docs", kind: "local", path: docs } })))
- }),
- )
- })
- function references(
- entries: Record<string, ProjectReference.Resolved>,
- ensurePath: ProjectReference.Interface["ensurePath"] = () => Effect.void,
- ) {
- return ProjectReference.Service.of({
- list: () => Effect.succeed(Object.values(entries)),
- get: (name) => Effect.succeed(entries[name]),
- resolveMention: () => Effect.succeed(undefined),
- ensurePath,
- containsManagedPath: () => Effect.succeed(false),
- })
- }
|