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

This commit is contained in:
Kit Langton
2026-04-17 22:08:57 -04:00
committed by GitHub
parent daaa1c7e26
commit 5fa1673341
2 changed files with 29 additions and 21 deletions

View File

@@ -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,

View File

@@ -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)
},
/**