From 71c1bdcac3b22edf8df24ad9a3eb999c5f44f36b Mon Sep 17 00:00:00 2001 From: "bing.han" Date: Tue, 15 Sep 2026 13:00:25 +0800 Subject: [PATCH] fix(core): break circular import between filesystem and filesystem/search `filesystem.ts` reads `FileSystemSearch.node` while the module initializes, but `filesystem/search.ts` imported `FileSystem` back from `../filesystem` at runtime for the `Entry`/`Match` constructors. Bundlers may emit either module first; when `filesystem.ts` wins, `FileSystemSearch.node` is still undefined and is baked into its layer dependencies, so `LayerNode.hoist` throws `TypeError: undefined is not an object (evaluating 'a.name')` on every boot. `Entry` and `Match` come from `@opencode-ai/schema/filesystem`, which `filesystem.ts` merely re-exports, so importing them from there removes the runtime edge. The remaining `../filesystem` import is type-only. Co-Authored-By: Claude --- packages/core/src/filesystem/search.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/core/src/filesystem/search.ts b/packages/core/src/filesystem/search.ts index c7738388bcc0..77b2f6a9a157 100644 --- a/packages/core/src/filesystem/search.ts +++ b/packages/core/src/filesystem/search.ts @@ -5,7 +5,12 @@ import path from "path" import { Context, Effect, Layer, Scope } from "effect" import { Fff } from "#fff" import fuzzysort from "fuzzysort" -import { FileSystem } from "../filesystem" +// Imported as a value from the schema package rather than through `../filesystem`: +// `filesystem.ts` reads `FileSystemSearch.node` while it initializes, so a runtime +// import back into it would make the two modules circular and let a bundler emit +// them in an order where `FileSystemSearch.node` is still undefined there. +import { Entry, Match } from "@opencode-ai/schema/filesystem" +import type { FileSystem } from "../filesystem" import { FSUtil } from "../fs-util" import { Location } from "../location" import { Ripgrep } from "../ripgrep" @@ -61,7 +66,7 @@ export const ripgrepLayer = Layer.effect( .pipe( Effect.map((result) => result.map((entry) => - FileSystem.Entry.make({ + Entry.make({ ...entry, path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, entry.path))), }), @@ -86,9 +91,9 @@ export const ripgrepLayer = Layer.effect( .pipe( Effect.map((result) => result.map((match) => - FileSystem.Match.make({ + Match.make({ ...match, - entry: FileSystem.Entry.make({ + entry: Entry.make({ ...match.entry, path: RelativePath.make(path.relative(location.directory, path.resolve(cwd, match.entry.path))), }), @@ -109,7 +114,7 @@ export const ripgrepLayer = Layer.effect( return fuzzysort.go(input.query, items, { limit: input.limit ?? 50 }).map((item) => { const relative = item.target const type = relative.endsWith(path.sep) ? ("directory" as const) : ("file" as const) - return FileSystem.Entry.make({ + return Entry.make({ path: RelativePath.make(relative), type, }) @@ -154,7 +159,7 @@ export const fffLayer = Layer.effect( }) if (!found.ok) throw found.error return found.value.items.map((item) => - FileSystem.Entry.make({ + Entry.make({ path: RelativePath.make(item.relativePath.replaceAll("\\", "/")), type: "file", }), @@ -172,8 +177,8 @@ export const fffLayer = Layer.effect( if (!found.ok) throw found.error return found.value.items.map((match) => { const bytes = Buffer.from(match.lineContent) - return FileSystem.Match.make({ - entry: FileSystem.Entry.make({ + return Match.make({ + entry: Entry.make({ path: RelativePath.make(match.relativePath.replaceAll("\\", "/")), type: "file", }), @@ -222,7 +227,7 @@ export const fffLayer = Layer.effect( .sort((a, b) => b.score - a.score || a.path.length - b.path.length) .map((item) => { const relative = item.path.replaceAll("\\", "/").replace(/\/$/, "") - return FileSystem.Entry.make({ + return Entry.make({ path: RelativePath.make(relative + (item.type === "directory" ? path.sep : "")), type: item.type, })