summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-17 22:08:57 -0400
committerGitHub <[email protected]>2026-04-17 22:08:57 -0400
commit5fa1673341f2ebde860cb06799626c6dfbcde2c3 (patch)
treead4d113c3ec3dd887580829fc96489ce9d2440a9 /packages
parentdaaa1c7e26a06d2239133c857d7f8863b60fb97a (diff)
downloadopencode-5fa1673341f2ebde860cb06799626c6dfbcde2c3.tar.gz
opencode-5fa1673341f2ebde860cb06799626c6dfbcde2c3.zip
refactor: use InstanceState context in File service (#23015)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/file/index.ts48
-rw-r--r--packages/opencode/src/project/instance.ts2
2 files changed, 29 insertions, 21 deletions
diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts
index 2f30b5400..af4fbf76c 100644
--- a/packages/opencode/src/file/index.ts
+++ b/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,
diff --git a/packages/opencode/src/project/instance.ts b/packages/opencode/src/project/instance.ts
index 056eede01..1c5109620 100644
--- a/packages/opencode/src/project/instance.ts
+++ b/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)
},
/**