| export * as LocationMutation from "./location-mutation" |
|
|
| import { makeLocationNode } from "./effect/app-node" |
| import path from "path" |
| import { Context, Effect, Layer, Schema } from "effect" |
| import { FSUtil } from "./fs-util" |
| import { Location } from "./location" |
|
|
| export const Kind = Schema.Literals(["file", "directory"]) |
| export type Kind = typeof Kind.Type |
|
|
| |
| |
| |
| |
| |
| export const ResolveInput = Schema.Struct({ |
| path: Schema.String, |
| |
| kind: Kind.pipe(Schema.optional), |
| }) |
| export type ResolveInput = typeof ResolveInput.Type |
|
|
| export class PathError extends Schema.TaggedErrorClass<PathError>()("LocationMutation.PathError", { |
| path: Schema.String, |
| reason: Schema.Literals(["relative_escape", "location_escape", "non_directory_ancestor"]), |
| }) {} |
|
|
| export interface ExternalDirectoryAuthorization { |
| readonly action: "external_directory" |
| |
| readonly directory: string |
| |
| readonly resource: string |
| readonly save: string |
| } |
|
|
| export const externalDirectoryPermission = (input: ExternalDirectoryAuthorization) => ({ |
| action: input.action, |
| resources: [input.resource], |
| save: [input.save], |
| }) |
|
|
| export interface Target { |
| |
| readonly canonical: string |
| |
| readonly resource: string |
| readonly externalDirectory?: ExternalDirectoryAuthorization |
| } |
|
|
| export interface Interface { |
| |
| |
| |
| |
| |
| readonly resolve: (input: ResolveInput) => Effect.Effect<Target, PathError | FSUtil.Error> |
| } |
|
|
| export class Service extends Context.Service<Service, Interface>()("@opencode/v2/LocationMutation") {} |
|
|
| interface ResolvedPath { |
| readonly canonical: string |
| readonly type?: |
| | "File" |
| | "Directory" |
| | "SymbolicLink" |
| | "BlockDevice" |
| | "CharacterDevice" |
| | "FIFO" |
| | "Socket" |
| | "Unknown" |
| readonly directory: string |
| } |
|
|
| const slash = (value: string) => value.replaceAll("\\", "/") |
|
|
| const layer = Layer.effect( |
| Service, |
| Effect.gen(function* () { |
| const fs = yield* FSUtil.Service |
| const location = yield* Location.Service |
| const locationRoot = yield* fs.realPath(location.directory) |
|
|
| function notFound<A>(effect: Effect.Effect<A, FSUtil.Error>) { |
| return effect.pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(undefined))) |
| } |
|
|
| const resolvePath = Effect.fnUntraced(function* (absolute: string) { |
| const existing = yield* notFound(fs.realPath(absolute)) |
| if (existing !== undefined) { |
| const info = yield* fs.stat(existing) |
| return { |
| canonical: existing, |
| type: info.type, |
| directory: info.type === "Directory" ? existing : path.dirname(existing), |
| } satisfies ResolvedPath |
| } |
|
|
| let anchor = path.dirname(absolute) |
| while (true) { |
| const canonical = yield* notFound(fs.realPath(anchor)) |
| if (canonical !== undefined) { |
| const info = yield* fs.stat(canonical) |
| if (info.type !== "Directory") { |
| return yield* new PathError({ path: absolute, reason: "non_directory_ancestor" }) |
| } |
| return { |
| canonical: path.resolve(canonical, path.relative(anchor, absolute)), |
| directory: canonical, |
| } satisfies ResolvedPath |
| } |
| const parent = path.dirname(anchor) |
| if (parent === anchor) return yield* new PathError({ path: absolute, reason: "non_directory_ancestor" }) |
| anchor = parent |
| } |
| }) |
|
|
| const resolve = Effect.fn("LocationMutation.resolve")(function* (input: ResolveInput) { |
| const relative = !path.isAbsolute(input.path) |
| const absolute = path.resolve(location.directory, input.path) |
| const lexicallyInternal = FSUtil.contains(location.directory, absolute) |
| if (relative && !lexicallyInternal) return yield* new PathError({ path: input.path, reason: "relative_escape" }) |
|
|
| const resolved = yield* resolvePath(absolute) |
| if (lexicallyInternal && !FSUtil.contains(locationRoot, resolved.canonical)) { |
| return yield* new PathError({ path: input.path, reason: "location_escape" }) |
| } |
|
|
| const external = !lexicallyInternal |
| const resource = external |
| ? slash(resolved.canonical) |
| : slash(path.relative(locationRoot, resolved.canonical) || ".") |
| const externalDirectory = |
| input.kind === "directory" && resolved.type === "Directory" ? resolved.canonical : resolved.directory |
| const externalResource = slash(path.join(externalDirectory, "*")) |
| return { |
| canonical: resolved.canonical, |
| resource, |
| externalDirectory: external |
| ? { |
| action: "external_directory", |
| directory: externalDirectory, |
| resource: externalResource, |
| save: externalResource, |
| } |
| : undefined, |
| } satisfies Target |
| }) |
|
|
| return Service.of({ resolve }) |
| }), |
| ) |
|
|
| export const locationLayer = layer |
|
|
| export const node = makeLocationNode({ |
| service: Service, |
| layer: layer.pipe(Layer.orDie), |
| deps: [FSUtil.node, Location.node], |
| }) |
|
|