204 lines · 6.2 KB · TypeScript
RawDownload
import { ProjectId } from "@t3tools/contracts";import {  projectScriptRuntimeEnv,  resolveProjectScripts,  setupProjectScript,} from "@t3tools/shared/projectScripts";import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Schema from "effect/Schema"; import * as ProjectionSnapshotQuery from "../orchestration/Services/ProjectionSnapshotQuery.ts";import * as ServerSettings from "../serverSettings.ts";import * as TerminalManager from "../terminal/Manager.ts"; export interface ProjectSetupScriptRunnerResultNoScript {  readonly status: "no-script";} export interface ProjectSetupScriptRunnerResultStarted {  readonly status: "started";  readonly scriptId: string;  readonly scriptName: string;  readonly terminalId: string;  readonly cwd: string;} export type ProjectSetupScriptRunnerResult =  | ProjectSetupScriptRunnerResultNoScript  | ProjectSetupScriptRunnerResultStarted; export interface ProjectSetupScriptRunnerInput {  readonly threadId: string;  readonly projectId?: string;  readonly projectCwd?: string;  readonly worktreePath: string;  readonly preferredTerminalId?: string;} export class ProjectSetupScriptOperationError extends Schema.TaggedErrorClass<ProjectSetupScriptOperationError>()(  "ProjectSetupScriptOperationError",  {    threadId: Schema.String,    projectId: Schema.optional(Schema.String),    projectCwd: Schema.optional(Schema.String),    worktreePath: Schema.String,    operation: Schema.Literals(["resolveProject", "readSettings", "openTerminal", "writeCommand"]),    cause: Schema.Defect(),  },) {  override get message(): string {    return `Project setup script operation '${this.operation}' failed for thread '${this.threadId}' in '${this.worktreePath}'.`;  }} export class ProjectSetupScriptProjectNotFoundError extends Schema.TaggedErrorClass<ProjectSetupScriptProjectNotFoundError>()(  "ProjectSetupScriptProjectNotFoundError",  {    threadId: Schema.String,    projectId: Schema.optional(Schema.String),    projectCwd: Schema.optional(Schema.String),    worktreePath: Schema.String,  },) {  override get message(): string {    return `Project was not found for setup script execution for thread '${this.threadId}' in '${this.worktreePath}'.`;  }} export const ProjectSetupScriptRunnerError = Schema.Union([  ProjectSetupScriptOperationError,  ProjectSetupScriptProjectNotFoundError,]);export type ProjectSetupScriptRunnerError = typeof ProjectSetupScriptRunnerError.Type; export class ProjectSetupScriptRunner extends Context.Service<  ProjectSetupScriptRunner,  {    readonly runForThread: (      input: ProjectSetupScriptRunnerInput,    ) => Effect.Effect<ProjectSetupScriptRunnerResult, ProjectSetupScriptRunnerError>;  }>()("t3/project/ProjectSetupScriptRunner") {} export const make = Effect.gen(function* () {  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery;  const terminalManager = yield* TerminalManager.TerminalManager;  const serverSettings = yield* ServerSettings.ServerSettingsService;   const runForThread: ProjectSetupScriptRunner["Service"]["runForThread"] = Effect.fn(    "ProjectSetupScriptRunner.runForThread",  )(function* (input) {    const errorContext = {      threadId: input.threadId,      worktreePath: input.worktreePath,      ...(input.projectId === undefined ? {} : { projectId: input.projectId }),      ...(input.projectCwd === undefined ? {} : { projectCwd: input.projectCwd }),    };    const projectById = input.projectId      ? yield* projectionSnapshotQuery.getProjectShellById(ProjectId.make(input.projectId)).pipe(          Effect.map(Option.getOrUndefined),          Effect.mapError(            (cause) =>              new ProjectSetupScriptOperationError({                ...errorContext,                operation: "resolveProject",                cause,              }),          ),        )      : null;    const project =      projectById ??      (input.projectCwd        ? yield* projectionSnapshotQuery.getActiveProjectByWorkspaceRoot(input.projectCwd).pipe(            Effect.map(Option.getOrUndefined),            Effect.mapError(              (cause) =>                new ProjectSetupScriptOperationError({                  ...errorContext,                  operation: "resolveProject",                  cause,                }),            ),          )        : null);     if (!project) {      return yield* new ProjectSetupScriptProjectNotFoundError(errorContext);    }     const settings = yield* serverSettings.getSettings.pipe(      Effect.mapError(        (cause) =>          new ProjectSetupScriptOperationError({            ...errorContext,            operation: "readSettings",            cause,          }),      ),    );    const script = setupProjectScript(resolveProjectScripts(settings, project));    if (!script) {      return {        status: "no-script",      } as const;    }     const terminalId = input.preferredTerminalId ?? `setup-${script.id}`;    const cwd = input.worktreePath;    const env = projectScriptRuntimeEnv({      project: { cwd: project.workspaceRoot },      worktreePath: input.worktreePath,    });     yield* terminalManager      .open({        threadId: input.threadId,        terminalId,        cwd,        worktreePath: input.worktreePath,        env,      })      .pipe(        Effect.mapError(          (cause) =>            new ProjectSetupScriptOperationError({              ...errorContext,              operation: "openTerminal",              cause,            }),        ),      );    yield* terminalManager      .write({        threadId: input.threadId,        terminalId,        data: `${script.command}\r`,      })      .pipe(        Effect.mapError(          (cause) =>            new ProjectSetupScriptOperationError({              ...errorContext,              operation: "writeCommand",              cause,            }),        ),      );     return {      status: "started",      scriptId: script.id,      scriptName: script.name,      terminalId,      cwd,    } as const;  });   return ProjectSetupScriptRunner.of({ runForThread });}); export const layer = Layer.effect(ProjectSetupScriptRunner, make);