Skip to content

Commit 5fa1673

Browse files
authored
refactor: use InstanceState context in File service (#23015)
1 parent daaa1c7 commit 5fa1673

2 files changed

Lines changed: 29 additions & 21 deletions

File tree

packages/opencode/src/file/index.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -356,8 +356,9 @@ export const layer = Layer.effect(
356356
)
357357

358358
const scan = Effect.fn("File.scan")(function* () {
359-
if (Instance.directory === path.parse(Instance.directory).root) return
360-
const isGlobalHome = Instance.directory === Global.Path.home && Instance.project.id === "global"
359+
const ctx = yield* InstanceState.context
360+
if (ctx.directory === path.parse(ctx.directory).root) return
361+
const isGlobalHome = ctx.directory === Global.Path.home && ctx.project.id === "global"
361362
const next: Entry = { files: [], dirs: [] }
362363

363364
if (isGlobalHome) {
@@ -366,14 +367,14 @@ export const layer = Layer.effect(
366367
const ignoreNested = new Set(["node_modules", "dist", "build", "target", "vendor"])
367368
const shouldIgnoreName = (name: string) => name.startsWith(".") || protectedNames.has(name)
368369
const shouldIgnoreNested = (name: string) => name.startsWith(".") || ignoreNested.has(name)
369-
const top = yield* appFs.readDirectoryEntries(Instance.directory).pipe(Effect.orElseSucceed(() => []))
370+
const top = yield* appFs.readDirectoryEntries(ctx.directory).pipe(Effect.orElseSucceed(() => []))
370371

371372
for (const entry of top) {
372373
if (entry.type !== "directory") continue
373374
if (shouldIgnoreName(entry.name)) continue
374375
dirs.add(entry.name + "/")
375376

376-
const base = path.join(Instance.directory, entry.name)
377+
const base = path.join(ctx.directory, entry.name)
377378
const children = yield* appFs.readDirectoryEntries(base).pipe(Effect.orElseSucceed(() => []))
378379
for (const child of children) {
379380
if (child.type !== "directory") continue
@@ -384,7 +385,7 @@ export const layer = Layer.effect(
384385

385386
next.dirs = Array.from(dirs).toSorted()
386387
} else {
387-
const files = yield* rg.files({ cwd: Instance.directory }).pipe(
388+
const files = yield* rg.files({ cwd: ctx.directory }).pipe(
388389
Stream.runCollect,
389390
Effect.map((chunk) => [...chunk]),
390391
)
@@ -416,15 +417,16 @@ export const layer = Layer.effect(
416417
})
417418

418419
const gitText = Effect.fnUntraced(function* (args: string[]) {
419-
return (yield* git.run(args, { cwd: Instance.directory })).text()
420+
return (yield* git.run(args, { cwd: (yield* InstanceState.context).directory })).text()
420421
})
421422

422423
const init = Effect.fn("File.init")(function* () {
423424
yield* ensure().pipe(Effect.forkIn(scope))
424425
})
425426

426427
const status = Effect.fn("File.status")(function* () {
427-
if (Instance.project.vcs !== "git") return []
428+
const ctx = yield* InstanceState.context
429+
if (ctx.project.vcs !== "git") return []
428430

429431
const diffOutput = yield* gitText([
430432
"-c",
@@ -463,7 +465,7 @@ export const layer = Layer.effect(
463465
if (untrackedOutput.trim()) {
464466
for (const file of untrackedOutput.trim().split("\n")) {
465467
const content = yield* appFs
466-
.readFileString(path.join(Instance.directory, file))
468+
.readFileString(path.join(ctx.directory, file))
467469
.pipe(Effect.catch(() => Effect.succeed<string | undefined>(undefined)))
468470
if (content === undefined) continue
469471
changed.push({
@@ -498,19 +500,22 @@ export const layer = Layer.effect(
498500
}
499501

500502
return changed.map((item) => {
501-
const full = path.isAbsolute(item.path) ? item.path : path.join(Instance.directory, item.path)
503+
const full = path.isAbsolute(item.path) ? item.path : path.join(ctx.directory, item.path)
502504
return {
503505
...item,
504-
path: path.relative(Instance.directory, full),
506+
path: path.relative(ctx.directory, full),
505507
}
506508
})
507509
})
508510

509511
const read: Interface["read"] = Effect.fn("File.read")(function* (file: string) {
510512
using _ = log.time("read", { file })
511-
const full = path.join(Instance.directory, file)
513+
const ctx = yield* InstanceState.context
514+
const full = path.join(ctx.directory, file)
512515

513-
if (!Instance.containsPath(full)) throw new Error("Access denied: path escapes project directory")
516+
if (!Instance.containsPath(full, ctx)) {
517+
throw new Error("Access denied: path escapes project directory")
518+
}
514519

515520
if (isImageByExtension(file)) {
516521
const exists = yield* appFs.existsSafe(full)
@@ -553,13 +558,13 @@ export const layer = Layer.effect(
553558
Effect.catch(() => Effect.succeed("")),
554559
)
555560

556-
if (Instance.project.vcs === "git") {
561+
if (ctx.project.vcs === "git") {
557562
let diff = yield* gitText(["-c", "core.fsmonitor=false", "diff", "--", file])
558563
if (!diff.trim()) {
559564
diff = yield* gitText(["-c", "core.fsmonitor=false", "diff", "--staged", "--", file])
560565
}
561566
if (diff.trim()) {
562-
const original = yield* git.show(Instance.directory, "HEAD", file)
567+
const original = yield* git.show(ctx.directory, "HEAD", file)
563568
const patch = structuredPatch(file, file, original, content, "old", "new", {
564569
context: Infinity,
565570
ignoreWhitespace: true,
@@ -573,29 +578,32 @@ export const layer = Layer.effect(
573578
})
574579

575580
const list = Effect.fn("File.list")(function* (dir?: string) {
581+
const ctx = yield* InstanceState.context
576582
const exclude = [".git", ".DS_Store"]
577583
let ignored = (_: string) => false
578-
if (Instance.project.vcs === "git") {
584+
if (ctx.project.vcs === "git") {
579585
const ig = ignore()
580-
const gitignore = path.join(Instance.project.worktree, ".gitignore")
586+
const gitignore = path.join(ctx.worktree, ".gitignore")
581587
const gitignoreText = yield* appFs.readFileString(gitignore).pipe(Effect.catch(() => Effect.succeed("")))
582588
if (gitignoreText) ig.add(gitignoreText)
583-
const ignoreFile = path.join(Instance.project.worktree, ".ignore")
589+
const ignoreFile = path.join(ctx.worktree, ".ignore")
584590
const ignoreText = yield* appFs.readFileString(ignoreFile).pipe(Effect.catch(() => Effect.succeed("")))
585591
if (ignoreText) ig.add(ignoreText)
586592
ignored = ig.ignores.bind(ig)
587593
}
588594

589-
const resolved = dir ? path.join(Instance.directory, dir) : Instance.directory
590-
if (!Instance.containsPath(resolved)) throw new Error("Access denied: path escapes project directory")
595+
const resolved = dir ? path.join(ctx.directory, dir) : ctx.directory
596+
if (!Instance.containsPath(resolved, ctx)) {
597+
throw new Error("Access denied: path escapes project directory")
598+
}
591599

592600
const entries = yield* appFs.readDirectoryEntries(resolved).pipe(Effect.orElseSucceed(() => []))
593601

594602
const nodes: Node[] = []
595603
for (const entry of entries) {
596604
if (exclude.includes(entry.name)) continue
597605
const absolute = path.join(resolved, entry.name)
598-
const file = path.relative(Instance.directory, absolute)
606+
const file = path.relative(ctx.directory, absolute)
599607
const type = entry.type === "directory" ? "directory" : "file"
600608
nodes.push({
601609
name: entry.name,

packages/opencode/src/project/instance.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export const Instance = {
9696
if (AppFileSystem.contains(instance.directory, filepath)) return true
9797
// Non-git projects set worktree to "/" which would match ANY absolute path.
9898
// Skip worktree check in this case to preserve external_directory permissions.
99-
if (Instance.worktree === "/") return false
99+
if (instance.worktree === "/") return false
100100
return AppFileSystem.contains(instance.worktree, filepath)
101101
},
102102
/**

0 commit comments

Comments
 (0)