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);
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);