Просмотр исходного кода

refactor: use InstanceState context in File service (#23015)

Kit Langton 20 часов назад
Родитель
Сommit
5fa1673341
2 измененных файлов с 29 добавлено и 21 удалено
  1. 28 20
      packages/opencode/src/file/index.ts
  2. 1 1
      packages/opencode/src/project/instance.ts

+ 28 - 20
packages/opencode/src/file/index.ts

@@ -356,8 +356,9 @@ export const layer = Layer.effect(
     )
 
     const scan = Effect.fn("File.scan")(function* () {
-      if (Instance.directory === path.parse(Instance.directory).root) return
-      const isGlobalHome = Instance.directory === Global.Path.home && Instance.project.id === "global"
+      const ctx = yield* InstanceState.context
+      if (ctx.directory === path.parse(ctx.directory).root) return
+      const isGlobalHome = ctx.directory === Global.Path.home && ctx.project.id === "global"
       const next: Entry = { files: [], dirs: [] }
 
       if (isGlobalHome) {
@@ -366,14 +367,14 @@ export const layer = Layer.effect(
         const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"])
         const shouldIgnoreName = (name: string) => name.startsWith(".") || protectedNames.has(name)
         const shouldIgnoreNested = (name: string) => name.startsWith(".") || ignoreNested.has(name)
-        const top = yield* appFs.readDirectoryEntries(Instance.directory).pipe(Effect.orElseSucceed(() => []))
+        const top = yield* appFs.readDirectoryEntries(ctx.directory).pipe(Effect.orElseSucceed(() => []))
 
         for (const entry of top) {
           if (entry.type !== "directory") continue
           if (shouldIgnoreName(entry.name)) continue
           dirs.add(entry.name + "/")
 
-          const base = path.join(Instance.directory, entry.name)
+          const base = path.join(ctx.directory, entry.name)
           const children = yield* appFs.readDirectoryEntries(base).pipe(Effect.orElseSucceed(() => []))
           for (const child of children) {
             if (child.type !== "directory") continue
@@ -384,7 +385,7 @@ export const layer = Layer.effect(
 
         next.dirs = Array.from(dirs).toSorted()
       } else {
-        const files = yield* rg.files({ cwd: Instance.directory }).pipe(
+        const files = yield* rg.files({ cwd: ctx.directory }).pipe(
           Stream.runCollect,
           Effect.map((chunk) => [...chunk]),
         )
@@ -416,7 +417,7 @@ export const layer = Layer.effect(
     })
 
     const gitText = Effect.fnUntraced(function* (args: string[]) {
-      return (yield* git.run(args, { cwd: Instance.directory })).text()
+      return (yield* git.run(args, { cwd: (yield* InstanceState.context).directory })).text()
     })
 
     const init = Effect.fn("File.init")(function* () {
@@ -424,7 +425,8 @@ export const layer = Layer.effect(
     })
 
     const status = Effect.fn("File.status")(function* () {
-      if (Instance.project.vcs !== "git") return []
+      const ctx = yield* InstanceState.context
+      if (ctx.project.vcs !== "git") return []
 
       const diffOutput = yield* gitText([
         "-c",
@@ -463,7 +465,7 @@ export const layer = Layer.effect(
       if (untrackedOutput.trim()) {
         for (const file of untrackedOutput.trim().split("\n")) {
           const content = yield* appFs
-            .readFileString(path.join(Instance.directory, file))
+            .readFileString(path.join(ctx.directory, file))
             .pipe(Effect.catch(() => Effect.succeed<string | undefined>(undefined)))
           if (content === undefined) continue
           changed.push({
@@ -498,19 +500,22 @@ export const layer = Layer.effect(
       }
 
       return changed.map((item) => {
-        const full = path.isAbsolute(item.path) ? item.path : path.join(Instance.directory, item.path)
+        const full = path.isAbsolute(item.path) ? item.path : path.join(ctx.directory, item.path)
         return {
           ...item,
-          path: path.relative(Instance.directory, full),
+          path: path.relative(ctx.directory, full),
         }
       })
     })
 
     const read: Interface["read"] = Effect.fn("File.read")(function* (file: string) {
       using _ = log.time("read", { file })
-      const full = path.join(Instance.directory, file)
+      const ctx = yield* InstanceState.context
+      const full = path.join(ctx.directory, file)
 
-      if (!Instance.containsPath(full)) throw new Error("Access denied: path escapes project directory")
+      if (!Instance.containsPath(full, ctx)) {
+        throw new Error("Access denied: path escapes project directory")
+      }
 
       if (isImageByExtension(file)) {
         const exists = yield* appFs.existsSafe(full)
@@ -553,13 +558,13 @@ export const layer = Layer.effect(
         Effect.catch(() => Effect.succeed("")),
       )
 
-      if (Instance.project.vcs === "git") {
+      if (ctx.project.vcs === "git") {
         let diff = yield* gitText(["-c", "core.fsmonitor=false", "diff", "--", file])
         if (!diff.trim()) {
           diff = yield* gitText(["-c", "core.fsmonitor=false", "diff", "--staged", "--", file])
         }
         if (diff.trim()) {
-          const original = yield* git.show(Instance.directory, "HEAD", file)
+          const original = yield* git.show(ctx.directory, "HEAD", file)
           const patch = structuredPatch(file, file, original, content, "old", "new", {
             context: Infinity,
             ignoreWhitespace: true,
@@ -573,21 +578,24 @@ export const layer = Layer.effect(
     })
 
     const list = Effect.fn("File.list")(function* (dir?: string) {
+      const ctx = yield* InstanceState.context
       const exclude = [".git", ".DS_Store"]
       let ignored = (_: string) => false
-      if (Instance.project.vcs === "git") {
+      if (ctx.project.vcs === "git") {
         const ig = ignore()
-        const gitignore = path.join(Instance.project.worktree, ".gitignore")
+        const gitignore = path.join(ctx.worktree, ".gitignore")
         const gitignoreText = yield* appFs.readFileString(gitignore).pipe(Effect.catch(() => Effect.succeed("")))
         if (gitignoreText) ig.add(gitignoreText)
-        const ignoreFile = path.join(Instance.project.worktree, ".ignore")
+        const ignoreFile = path.join(ctx.worktree, ".ignore")
         const ignoreText = yield* appFs.readFileString(ignoreFile).pipe(Effect.catch(() => Effect.succeed("")))
         if (ignoreText) ig.add(ignoreText)
         ignored = ig.ignores.bind(ig)
       }
 
-      const resolved = dir ? path.join(Instance.directory, dir) : Instance.directory
-      if (!Instance.containsPath(resolved)) throw new Error("Access denied: path escapes project directory")
+      const resolved = dir ? path.join(ctx.directory, dir) : ctx.directory
+      if (!Instance.containsPath(resolved, ctx)) {
+        throw new Error("Access denied: path escapes project directory")
+      }
 
       const entries = yield* appFs.readDirectoryEntries(resolved).pipe(Effect.orElseSucceed(() => []))
 
@@ -595,7 +603,7 @@ export const layer = Layer.effect(
       for (const entry of entries) {
         if (exclude.includes(entry.name)) continue
         const absolute = path.join(resolved, entry.name)
-        const file = path.relative(Instance.directory, absolute)
+        const file = path.relative(ctx.directory, absolute)
         const type = entry.type === "directory" ? "directory" : "file"
         nodes.push({
           name: entry.name,

+ 1 - 1
packages/opencode/src/project/instance.ts

@@ -96,7 +96,7 @@ export const Instance = {
     if (AppFileSystem.contains(instance.directory, filepath)) return true
     // Non-git projects set worktree to "/" which would match ANY absolute path.
     // Skip worktree check in this case to preserve external_directory permissions.
-    if (Instance.worktree === "/") return false
+    if (instance.worktree === "/") return false
     return AppFileSystem.contains(instance.worktree, filepath)
   },
   /**