| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- export * as FileSystem from "./filesystem"
- 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"
- import { PositiveInt, RelativePath } from "./schema"
- import { FileSystemSearch } from "./filesystem/search"
- import { Entry, FileSystem, FindInput, Match } from "@kirincode-ai/schema/filesystem"
- export { Entry, Match, Submatch } from "@kirincode-ai/schema/filesystem"
- export const ReadInput = Schema.Struct({
- path: RelativePath,
- })
- export type ReadInput = typeof ReadInput.Type
- export const Content = Schema.Struct({
- uri: Schema.String,
- name: Schema.String.pipe(Schema.optional),
- content: Schema.String,
- encoding: Schema.Literals(["utf8", "base64"]),
- mime: Schema.String,
- }).annotate({ identifier: "FileSystem.Content" })
- export type Content = typeof Content.Type
- export const ListInput = Schema.Struct({
- path: RelativePath.pipe(Schema.optional),
- })
- export type ListInput = typeof ListInput.Type
- export { FindInput }
- export class GlobInput extends Schema.Class<GlobInput>("FileSystem.GlobInput")({
- pattern: Schema.String,
- path: RelativePath.pipe(Schema.optional),
- limit: PositiveInt.pipe(Schema.optional),
- }) {}
- export class GrepInput extends Schema.Class<GrepInput>("FileSystem.GrepInput")({
- pattern: Schema.String,
- path: RelativePath.pipe(Schema.optional),
- include: Schema.String.pipe(Schema.optional),
- limit: PositiveInt.pipe(Schema.optional),
- }) {}
- export const Event = FileSystem.Event
- export interface Interface {
- readonly read: (input: ReadInput) => Effect.Effect<{ readonly content: Uint8Array; readonly mime: string }>
- readonly list: (input?: ListInput) => Effect.Effect<Entry[]>
- readonly find: (input: FindInput) => Effect.Effect<Entry[]>
- readonly glob: (input: GlobInput) => Effect.Effect<readonly Entry[]>
- readonly grep: (input: GrepInput) => Effect.Effect<readonly Match[]>
- }
- export class Service extends Context.Service<Service, Interface>()("@kirincode/v2/FileSystem") {}
- const baseLayer = Layer.effect(
- Service,
- Effect.gen(function* () {
- const fs = yield* FSUtil.Service
- const location = yield* Location.Service
- const search = yield* FileSystemSearch.Service
- const root = yield* fs.realPath(location.directory).pipe(Effect.orDie)
- const resolve = Effect.fnUntraced(function* (input?: RelativePath) {
- const absolute = path.resolve(location.directory, input ?? ".")
- if (!FSUtil.contains(location.directory, absolute))
- return yield* Effect.die(new Error("Path escapes the location"))
- const real = yield* fs.realPath(absolute).pipe(Effect.orDie)
- if (!FSUtil.contains(root, real)) return yield* Effect.die(new Error("Path escapes the location"))
- return { absolute, real, directory: location.directory, root }
- })
- return Service.of({
- find: search.find,
- glob: search.glob,
- grep: search.grep,
- read: Effect.fn("FileSystem.read")(function* (input) {
- const target = yield* resolve(input.path)
- const info = yield* fs.stat(target.real).pipe(Effect.orDie)
- if (info.type !== "File") return yield* Effect.die(new Error("Path is not a file"))
- return {
- content: yield* fs.readFile(target.real).pipe(Effect.orDie),
- mime: FSUtil.mimeType(target.real),
- }
- }),
- list: Effect.fn("FileSystem.list")(function* (input = {}) {
- const target = yield* resolve(input.path)
- const info = yield* fs.stat(target.real).pipe(Effect.orDie)
- if (info.type !== "Directory") return yield* Effect.die(new Error("Path is not a directory"))
- return yield* fs.readDirectoryEntries(target.real).pipe(
- Effect.orDie,
- Effect.map((items) =>
- items
- .flatMap((item) => {
- if (item.type !== "file" && item.type !== "directory") return []
- const absolute = path.join(target.absolute, item.name)
- const relative = path.relative(target.directory, absolute)
- return [
- Entry.make({
- path: RelativePath.make(relative + (item.type === "directory" ? path.sep : "")),
- type: item.type,
- }),
- ]
- })
- .sort((a, b) => (a.type === b.type ? a.path.localeCompare(b.path) : a.type === "directory" ? -1 : 1)),
- ),
- )
- }),
- })
- }),
- )
- export const node = makeLocationNode({
- service: Service,
- layer: baseLayer,
- deps: [FSUtil.node, Location.node, FileSystemSearch.node],
- })
|