refactor(core): move path resolve into fs service (#35202)
This commit is contained in:
@@ -38,6 +38,7 @@ export namespace FSUtil {
|
|||||||
readonly ensureDir: (path: string) => Effect.Effect<void, Error>
|
readonly ensureDir: (path: string) => Effect.Effect<void, Error>
|
||||||
readonly writeWithDirs: (path: string, content: string | Uint8Array, mode?: number) => Effect.Effect<void, Error>
|
readonly writeWithDirs: (path: string, content: string | Uint8Array, mode?: number) => Effect.Effect<void, Error>
|
||||||
readonly readDirectoryEntries: (path: string) => Effect.Effect<DirEntry[], Error>
|
readonly readDirectoryEntries: (path: string) => Effect.Effect<DirEntry[], Error>
|
||||||
|
readonly resolve: (path: string) => Effect.Effect<string>
|
||||||
readonly findUp: (target: string, start: string, stop?: string) => Effect.Effect<string[], Error>
|
readonly findUp: (target: string, start: string, stop?: string) => Effect.Effect<string[], Error>
|
||||||
readonly up: (options: { targets: string[]; start: string; stop?: string }) => Effect.Effect<string[], Error>
|
readonly up: (options: { targets: string[]; start: string; stop?: string }) => Effect.Effect<string[], Error>
|
||||||
readonly globUp: (pattern: string, start: string, stop?: string) => Effect.Effect<string[], Error>
|
readonly globUp: (pattern: string, start: string, stop?: string) => Effect.Effect<string[], Error>
|
||||||
@@ -89,6 +90,14 @@ export namespace FSUtil {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const resolve = Effect.fn("FileSystem.resolve")(function* (path: string) {
|
||||||
|
const resolved = pathResolve(windowsPath(path))
|
||||||
|
return yield* fs.realPath(resolved).pipe(
|
||||||
|
Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(resolved)),
|
||||||
|
Effect.orDie,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
const readJson = Effect.fn("FileSystem.readJson")(function* (path: string) {
|
const readJson = Effect.fn("FileSystem.readJson")(function* (path: string) {
|
||||||
const text = yield* fs.readFileString(path)
|
const text = yield* fs.readFileString(path)
|
||||||
return yield* Effect.try({
|
return yield* Effect.try({
|
||||||
@@ -187,6 +196,7 @@ export namespace FSUtil {
|
|||||||
isDir,
|
isDir,
|
||||||
isFile,
|
isFile,
|
||||||
readDirectoryEntries,
|
readDirectoryEntries,
|
||||||
|
resolve,
|
||||||
readJson,
|
readJson,
|
||||||
writeJson,
|
writeJson,
|
||||||
ensureDir,
|
ensureDir,
|
||||||
|
|||||||
@@ -38,22 +38,24 @@ const layer = Layer.effectDiscard(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const observe = Effect.fn("InstructionContext.observe")(function* () {
|
const observe = Effect.fn("InstructionContext.observe")(function* () {
|
||||||
const start = FSUtil.resolve(location.directory)
|
const start = yield* fs.resolve(location.directory)
|
||||||
const stop = FSUtil.resolve(location.project.directory)
|
const stop = yield* fs.resolve(location.project.directory)
|
||||||
const fromProject = relative(stop, start)
|
const fromProject = relative(stop, start)
|
||||||
const insideProject =
|
const insideProject =
|
||||||
fromProject === "" || (fromProject !== ".." && !fromProject.startsWith(`..${sep}`) && !isAbsolute(fromProject))
|
fromProject === "" || (fromProject !== ".." && !fromProject.startsWith(`..${sep}`) && !isAbsolute(fromProject))
|
||||||
const discovered = new Set(
|
const discovered = new Set(
|
||||||
(Flag.OPENCODE_DISABLE_PROJECT_CONFIG || !insideProject
|
yield* Effect.forEach(
|
||||||
? []
|
Flag.OPENCODE_DISABLE_PROJECT_CONFIG || !insideProject
|
||||||
: yield* fs.up({
|
? []
|
||||||
targets: ["AGENTS.md"],
|
: yield* fs.up({
|
||||||
start,
|
targets: ["AGENTS.md"],
|
||||||
stop,
|
start,
|
||||||
})
|
stop,
|
||||||
).map(FSUtil.resolve),
|
}),
|
||||||
|
fs.resolve,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
const paths = Array.dedupe([FSUtil.resolve(join(global.config, "AGENTS.md")), ...discovered])
|
const paths = Array.dedupe([yield* fs.resolve(join(global.config, "AGENTS.md")), ...discovered])
|
||||||
const files = yield* Effect.forEach(
|
const files = yield* Effect.forEach(
|
||||||
paths,
|
paths,
|
||||||
(path) =>
|
(path) =>
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ const layer = Layer.effect(
|
|||||||
})
|
})
|
||||||
|
|
||||||
const canonical = Effect.fnUntraced(function* (input: AbsolutePath) {
|
const canonical = Effect.fnUntraced(function* (input: AbsolutePath) {
|
||||||
const resolved = AbsolutePath.make(FSUtil.resolve(input))
|
const resolved = AbsolutePath.make(yield* fs.resolve(input))
|
||||||
if (!(yield* fs.isDir(resolved))) return yield* new DirectoryUnavailableError({ directory: input })
|
if (!(yield* fs.isDir(resolved))) return yield* new DirectoryUnavailableError({ directory: input })
|
||||||
return resolved
|
return resolved
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -78,17 +78,21 @@ const isTimeout = (error: AppProcess.AppProcessError) =>
|
|||||||
|
|
||||||
const shellTokens = (command: string) => command.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? []
|
const shellTokens = (command: string) => command.match(/(?:[^\s"']+|"[^"]*"|'[^']*')+/g) ?? []
|
||||||
const unquote = (value: string) => value.replace(/^(['"])(.*)\1$/, "$2")
|
const unquote = (value: string) => value.replace(/^(['"])(.*)\1$/, "$2")
|
||||||
const externalCommandDirectories = (command: string, cwd: string) => {
|
const externalCommandDirectories = Effect.fn("BashTool.externalCommandDirectories")(function* (
|
||||||
|
fs: FSUtil.Interface,
|
||||||
|
command: string,
|
||||||
|
cwd: string,
|
||||||
|
) {
|
||||||
const directories = new Set<string>()
|
const directories = new Set<string>()
|
||||||
for (const token of shellTokens(command)) {
|
for (const token of shellTokens(command)) {
|
||||||
const value = unquote(token).replace(/[;,|&]+$/, "")
|
const value = unquote(token).replace(/[;,|&]+$/, "")
|
||||||
if (!path.isAbsolute(value)) continue
|
if (!path.isAbsolute(value)) continue
|
||||||
const resolved = FSUtil.resolve(value)
|
const resolved = yield* fs.resolve(value)
|
||||||
if (FSUtil.contains(cwd, resolved)) continue
|
if (FSUtil.contains(cwd, resolved)) continue
|
||||||
directories.add(FSUtil.resolve(path.dirname(resolved)))
|
directories.add(yield* fs.resolve(path.dirname(resolved)))
|
||||||
}
|
}
|
||||||
return [...directories]
|
return [...directories]
|
||||||
}
|
})
|
||||||
|
|
||||||
const layer = Layer.effectDiscard(
|
const layer = Layer.effectDiscard(
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
@@ -131,7 +135,7 @@ const layer = Layer.effectDiscard(
|
|||||||
agent: context.agent,
|
agent: context.agent,
|
||||||
source,
|
source,
|
||||||
})
|
})
|
||||||
const warnings = externalCommandDirectories(input.command, target.canonical).map(
|
const warnings = (yield* externalCommandDirectories(fs, input.command, target.canonical)).map(
|
||||||
(directory) =>
|
(directory) =>
|
||||||
`Command argument references external directory ${path.join(directory, "*").replaceAll("\\", "/")}. Bash runs with host-user filesystem, process, and network authority; this scan is advisory only.`,
|
`Command argument references external directory ${path.join(directory, "*").replaceAll("\\", "/")}. Bash runs with host-user filesystem, process, and network authority; this scan is advisory only.`,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user