104 lines · 3.3 KB · TypeScript
RawDownload
import * as OS from "node:os";import { Effect, FileSystem, Layer, Path } from "effect"; import {  WorkspacePaths,  WorkspacePathOutsideRootError,  WorkspaceRootCreateFailedError,  WorkspaceRootNotDirectoryError,  WorkspaceRootNotExistsError,  type WorkspacePathsShape,} from "../Services/WorkspacePaths.ts"; function toPosixRelativePath(input: string): string {  return input.replaceAll("\\", "/");} function expandHomePath(input: string, path: Path.Path): string {  if (input === "~") {    return OS.homedir();  }  if (input.startsWith("~/") || input.startsWith("~\\")) {    return path.join(OS.homedir(), input.slice(2));  }  return input;} export const makeWorkspacePaths = Effect.gen(function* () {  const fileSystem = yield* FileSystem.FileSystem;  const path = yield* Path.Path;   const normalizeWorkspaceRoot: WorkspacePathsShape["normalizeWorkspaceRoot"] = Effect.fn(    "WorkspacePaths.normalizeWorkspaceRoot",  )(function* (workspaceRoot, options) {    const normalizedWorkspaceRoot = path.resolve(expandHomePath(workspaceRoot.trim(), path));    let workspaceStat = yield* fileSystem      .stat(normalizedWorkspaceRoot)      .pipe(Effect.catch(() => Effect.succeed(null)));    if (!workspaceStat && options?.createIfMissing) {      yield* fileSystem.makeDirectory(normalizedWorkspaceRoot, { recursive: true }).pipe(        Effect.mapError(          () =>            new WorkspaceRootCreateFailedError({              workspaceRoot,              normalizedWorkspaceRoot,            }),        ),      );      workspaceStat = yield* fileSystem        .stat(normalizedWorkspaceRoot)        .pipe(Effect.catch(() => Effect.succeed(null)));    }    if (!workspaceStat) {      return yield* new WorkspaceRootNotExistsError({        workspaceRoot,        normalizedWorkspaceRoot,      });    }    if (workspaceStat.type !== "Directory") {      return yield* new WorkspaceRootNotDirectoryError({        workspaceRoot,        normalizedWorkspaceRoot,      });    }    return normalizedWorkspaceRoot;  });   const resolveRelativePathWithinRoot: WorkspacePathsShape["resolveRelativePathWithinRoot"] =    Effect.fn("WorkspacePaths.resolveRelativePathWithinRoot")(function* (input) {      const normalizedInputPath = input.relativePath.trim();      if (path.isAbsolute(normalizedInputPath)) {        return yield* new WorkspacePathOutsideRootError({          workspaceRoot: input.workspaceRoot,          relativePath: input.relativePath,        });      }       const absolutePath = path.resolve(input.workspaceRoot, normalizedInputPath);      const relativeToRoot = toPosixRelativePath(path.relative(input.workspaceRoot, absolutePath));      if (        relativeToRoot.length === 0 ||        relativeToRoot === "." ||        relativeToRoot.startsWith("../") ||        relativeToRoot === ".." ||        path.isAbsolute(relativeToRoot)      ) {        return yield* new WorkspacePathOutsideRootError({          workspaceRoot: input.workspaceRoot,          relativePath: input.relativePath,        });      }       return {        absolutePath,        relativePath: relativeToRoot,      };    });   return {    normalizeWorkspaceRoot,    resolveRelativePathWithinRoot,  } satisfies WorkspacePathsShape;}); export const WorkspacePathsLive = Layer.effect(WorkspacePaths, makeWorkspacePaths);