import fs from "node:fs/promises"; import { type WorkspaceAvailabilityState } from "@t3tools/contracts"; import { Effect, Schema } from "effect"; const WORKSPACE_PATH_CACHE_TTL_MS = 1_000; type CachedWorkspacePathState = { readonly expiresAt: number; readonly state: WorkspaceAvailabilityState; }; const workspacePathStateCache = new Map(); export class WorkspacePathError extends Schema.TaggedErrorClass()( "WorkspacePathError", { operation: Schema.String, path: Schema.String, state: Schema.Literals(["missing", "not_directory", "inaccessible"]), cause: Schema.optional(Schema.Defect), }, ) { override get message(): string { switch (this.state) { case "missing": return `Workspace folder is missing: ${this.path}`; case "not_directory": return `Workspace path is not a folder: ${this.path}`; case "inaccessible": default: return `Workspace folder is inaccessible: ${this.path}`; } } } export function clearWorkspacePathStateCache(path?: string): void { if (path) { workspacePathStateCache.delete(path); return; } workspacePathStateCache.clear(); } export async function inspectWorkspacePathState(path: string): Promise { const cached = workspacePathStateCache.get(path); const now = Date.now(); if (cached && cached.expiresAt > now) { return cached.state; } let state: WorkspaceAvailabilityState; try { const stat = await fs.stat(path); state = stat.isDirectory() ? "available" : "not_directory"; } catch (error) { const code = (error as NodeJS.ErrnoException).code; state = code === "ENOENT" ? "missing" : "inaccessible"; } workspacePathStateCache.set(path, { expiresAt: now + WORKSPACE_PATH_CACHE_TTL_MS, state, }); return state; } export const getWorkspacePathState = (path: string) => Effect.promise(() => inspectWorkspacePathState(path)); export const assertWorkspaceDirectory = (path: string, operation: string) => getWorkspacePathState(path).pipe( Effect.flatMap((state) => state === "available" ? Effect.succeed(path) : Effect.fail( new WorkspacePathError({ operation, path, state, }), ), ), ); export function resolveWorkspaceUnavailableReason( state: WorkspaceAvailabilityState, ): string | null { switch (state) { case "missing": return "Workspace folder is missing."; case "not_directory": return "Workspace path is not a folder."; case "inaccessible": return "Workspace folder is inaccessible."; case "available": default: return null; } }