440 lines · 15.5 KB · TypeScript
RawDownload
import {  CommandId,  OrchestrationReadModel,  ProjectId,  type ClientOrchestrationCommand,} from "@t3tools/contracts";import * as Console from "effect/Console";import * as Data from "effect/Data";import * as DateTime from "effect/DateTime";import * as Duration from "effect/Duration";import * as Effect from "effect/Effect";import * as Exit from "effect/Exit";import * as FileSystem from "effect/FileSystem";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as Path from "effect/Path";import * as References from "effect/References";import * as Schema from "effect/Schema";import { Argument, Command, Flag, GlobalFlag } from "effect/unstable/cli";import {  FetchHttpClient,  HttpClient,  HttpClientRequest,  HttpClientResponse,} from "effect/unstable/http"; import { AuthControlPlaneRuntimeLive } from "../auth/Layers/AuthControlPlane.ts";import { AuthControlPlane } from "../auth/Services/AuthControlPlane.ts";import type { AuthControlPlaneShape } from "../auth/Services/AuthControlPlane.ts";import { ServerConfig, type ServerConfigShape } from "../config.ts";import { OrchestrationEngineService } from "../orchestration/Services/OrchestrationEngine.ts";import { ProjectionSnapshotQuery } from "../orchestration/Services/ProjectionSnapshotQuery.ts";import { OrchestrationLayerLive } from "../orchestration/runtimeLayer.ts";import { layerConfig as SqlitePersistenceLayerLive } from "../persistence/Layers/Sqlite.ts";import { RepositoryIdentityResolverLive } from "../project/Layers/RepositoryIdentityResolver.ts";import { getAutoBootstrapDefaultModelSelection } from "../serverRuntimeStartup.ts";import {  clearPersistedServerRuntimeState,  readPersistedServerRuntimeState,} from "../serverRuntimeState.ts";import { WorkspacePathsLive } from "../workspace/Layers/WorkspacePaths.ts";import { WorkspacePaths } from "../workspace/Services/WorkspacePaths.ts";import { type CliAuthLocationFlags, projectLocationFlags, resolveCliAuthConfig } from "./config.ts"; type ProjectMutationTarget = {  readonly id: ProjectId;  readonly title: string;  readonly workspaceRoot: string;}; type ProjectCommandExecutionMode = "live" | "offline";type ProjectCliDispatchCommand = Extract<  ClientOrchestrationCommand,  { type: "project.create" | "project.meta.update" | "project.delete" }>; class ProjectCommandError extends Data.TaggedError("ProjectCommandError")<{  readonly message: string;}> {} const ProjectCliRuntimeLive = Layer.mergeAll(  WorkspacePathsLive,  OrchestrationLayerLive.pipe(    Layer.provideMerge(RepositoryIdentityResolverLive),    Layer.provideMerge(SqlitePersistenceLayerLive),  ),); const PROJECT_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(1);const OrchestrationHttpErrorResponse = Schema.Struct({  error: Schema.String,}); const withProjectCliSessionToken = <A, E, R>(  authControlPlane: AuthControlPlaneShape,  run: (token: string) => Effect.Effect<A, E, R>,) =>  Effect.acquireUseRelease(    authControlPlane.issueSession({      role: "owner",      label: "t3 project cli",    }),    (issued) => run(issued.token),    (issued) => authControlPlane.revokeSession(issued.sessionId).pipe(Effect.ignore({ log: true })),  ); const withProjectCliLiveServerTimeout = <A, E, R>(effect: Effect.Effect<A, E, R>) =>  effect.pipe(Effect.timeout(PROJECT_CLI_LIVE_SERVER_TIMEOUT)); const runLiveServerRequest = <A, E extends Error, R>(  request: HttpClientRequest.HttpClientRequest,  handle: (response: HttpClientResponse.HttpClientResponse) => Effect.Effect<A, E, R>,) =>  Effect.gen(function* () {    const httpClient = yield* HttpClient.HttpClient;    const response = yield* httpClient.execute(request);    return yield* handle(response);  }).pipe(withProjectCliLiveServerTimeout); const decodeOrchestrationReadModelResponse = (response: HttpClientResponse.HttpClientResponse) =>  HttpClientResponse.schemaBodyJson(OrchestrationReadModel)(response); const readErrorMessageFromResponse = (response: HttpClientResponse.HttpClientResponse) =>  HttpClientResponse.schemaBodyJson(OrchestrationHttpErrorResponse)(response).pipe(    Effect.map((body) => body.error),    Effect.catch(() => Effect.succeed(null)),    Effect.map((body) => {      if (typeof body === "string" && body.trim().length > 0) {        return body;      }      return `Server request failed with status ${response.status}.`;    }),  ); const normalizeWorkspaceRootForProjectCommand = Effect.fn(  "normalizeWorkspaceRootForProjectCommand",)(function* (workspaceRoot: string) {  const workspacePaths = yield* WorkspacePaths;  return yield* workspacePaths.normalizeWorkspaceRoot(workspaceRoot);}); const resolveProjectTitle = Effect.fn("resolveProjectTitle")(function* (  workspaceRoot: string,  explicitTitle?: string,) {  if (explicitTitle !== undefined) {    const trimmed = explicitTitle.trim();    if (trimmed.length > 0) {      return trimmed;    }    return yield* new ProjectCommandError({ message: "Project title cannot be empty." });  }   const path = yield* Path.Path;  const basename = path.basename(workspaceRoot).trim();  return basename.length > 0 ? basename : "project";}); const findActiveProjectTarget = Effect.fn("findActiveProjectTarget")(function* (input: {  readonly snapshot: OrchestrationReadModel;  readonly identifier: string;}) {  const trimmedIdentifier = input.identifier.trim();  if (trimmedIdentifier.length === 0) {    return yield* new ProjectCommandError({ message: "Project identifier cannot be empty." });  }   const activeProjects = input.snapshot.projects.filter((project) => project.deletedAt === null);  const exactIdMatch = activeProjects.find((project) => project.id === trimmedIdentifier);  if (exactIdMatch) {    return {      id: exactIdMatch.id,      title: exactIdMatch.title,      workspaceRoot: exactIdMatch.workspaceRoot,    } satisfies ProjectMutationTarget;  }   const normalizedWorkspaceRootResult = yield* Effect.exit(    normalizeWorkspaceRootForProjectCommand(trimmedIdentifier),  );  const normalizedWorkspaceRoot = Exit.isSuccess(normalizedWorkspaceRootResult)    ? normalizedWorkspaceRootResult.value    : null;   const exactWorkspaceMatch =    normalizedWorkspaceRoot === null      ? undefined      : activeProjects.find((project) => project.workspaceRoot === normalizedWorkspaceRoot);   const resolved = exactWorkspaceMatch;  if (!resolved) {    return yield* new ProjectCommandError({      message: `No active project found for '${trimmedIdentifier}'.`,    });  }   return {    id: resolved.id,    title: resolved.title,    workspaceRoot: resolved.workspaceRoot,  } satisfies ProjectMutationTarget;}); const fetchLiveOrchestrationSnapshot = (origin: string, bearerToken: string) =>  runLiveServerRequest(    HttpClientRequest.get(`${origin}/api/orchestration/snapshot`).pipe(      HttpClientRequest.acceptJson,      HttpClientRequest.bearerToken(bearerToken),    ),    HttpClientResponse.matchStatus({      "2xx": decodeOrchestrationReadModelResponse,      orElse: (response) =>        readErrorMessageFromResponse(response).pipe(          Effect.flatMap((message) => Effect.fail(new ProjectCommandError({ message }))),        ),    }),  ); const dispatchLiveOrchestrationCommand = (  origin: string,  bearerToken: string,  command: ProjectCliDispatchCommand,) =>  HttpClientRequest.post(`${origin}/api/orchestration/dispatch`).pipe(    HttpClientRequest.acceptJson,    HttpClientRequest.bearerToken(bearerToken),    HttpClientRequest.bodyJson(command),    Effect.flatMap((request) =>      runLiveServerRequest(        request,        HttpClientResponse.matchStatus({          "2xx": () => Effect.void,          orElse: (response) =>            readErrorMessageFromResponse(response).pipe(              Effect.flatMap((message) => Effect.fail(new ProjectCommandError({ message }))),            ),        }),      ),    ),  ); const getOfflineSnapshot = Effect.fn("getOfflineSnapshot")(function* () {  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;  return yield* projectionSnapshotQuery.getSnapshot();}); const tryResolveLiveProjectExecutionMode = Effect.fn("tryResolveLiveProjectExecutionMode")(  function* (authControlPlane: AuthControlPlaneShape, config: ServerConfigShape) {    const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath);    if (Option.isNone(runtimeState)) {      return Option.none<{ readonly origin: string }>();    }     const attempt = withProjectCliSessionToken(authControlPlane, (token) =>      fetchLiveOrchestrationSnapshot(runtimeState.value.origin, token).pipe(        Effect.as({          origin: runtimeState.value.origin,        }),      ),    );     const attempted = yield* Effect.exit(attempt);    if (Exit.isSuccess(attempted)) {      return Option.some(attempted.value);    }     yield* clearPersistedServerRuntimeState(config.serverRuntimeStatePath);    return Option.none<{ readonly origin: string }>();  },); const runProjectMutation = Effect.fn("runProjectMutation")(function* (  flags: CliAuthLocationFlags,  run: (input: {    readonly snapshot: OrchestrationReadModel;    readonly dispatch: (      command: ProjectCliDispatchCommand,    ) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>;    readonly mode: ProjectCommandExecutionMode;  }) => Effect.Effect<    string,    Error,    FileSystem.FileSystem | HttpClient.HttpClient | Path.Path | WorkspacePaths  >,) {  const logLevel = yield* GlobalFlag.LogLevel;  const config = yield* resolveCliAuthConfig(flags, logLevel);  const minimumLogLevel = config.logLevel;   return yield* Effect.gen(function* () {    const authControlPlane = yield* AuthControlPlane;    const liveMode = yield* tryResolveLiveProjectExecutionMode(authControlPlane, config);     if (Option.isSome(liveMode)) {      return yield* withProjectCliSessionToken(authControlPlane, (token) =>        Effect.gen(function* () {          const snapshot = yield* fetchLiveOrchestrationSnapshot(liveMode.value.origin, token);          const output = yield* run({            snapshot,            dispatch: (command) =>              dispatchLiveOrchestrationCommand(liveMode.value.origin, token, command),            mode: "live",          });          yield* Console.log(output);        }),      );    }     const offlineRuntimeLayer = ProjectCliRuntimeLive.pipe(      Layer.provide(Layer.succeed(ServerConfig, config)),      Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),    );     return yield* Effect.gen(function* () {      const snapshot = yield* getOfflineSnapshot();      const orchestrationEngine = yield* OrchestrationEngineService;      const output = yield* run({        snapshot,        dispatch: (command) => orchestrationEngine.dispatch(command),        mode: "offline",      });      yield* Console.log(output);    }).pipe(Effect.provide(offlineRuntimeLayer));  }).pipe(    Effect.provide(      Layer.mergeAll(AuthControlPlaneRuntimeLive, WorkspacePathsLive).pipe(        Layer.provideMerge(FetchHttpClient.layer),        Layer.provide(Layer.succeed(ServerConfig, config)),        Layer.provide(Layer.succeed(References.MinimumLogLevel, minimumLogLevel)),      ),    ),  );}); const projectAddCommand = Command.make("add", {  ...projectLocationFlags,  workspaceRoot: Argument.string("path").pipe(    Argument.withDescription("Workspace root to add as a project."),  ),  title: Flag.string("title").pipe(Flag.withDescription("Optional project title."), Flag.optional),}).pipe(  Command.withDescription("Add a project."),  Command.withHandler((flags) =>    runProjectMutation(      flags,      Effect.fn("projectAddMutation")(function* ({        snapshot,        dispatch,      }: {        readonly snapshot: OrchestrationReadModel;        readonly dispatch: (          command: ProjectCliDispatchCommand,        ) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>;      }) {        const workspaceRoot = yield* normalizeWorkspaceRootForProjectCommand(flags.workspaceRoot);        const existingProject = snapshot.projects.find(          (project) => project.deletedAt === null && project.workspaceRoot === workspaceRoot,        );        if (existingProject) {          return yield* new ProjectCommandError({            message: `An active project already exists for '${workspaceRoot}'.`,          });        }         const title = yield* resolveProjectTitle(workspaceRoot, Option.getOrUndefined(flags.title));        const projectId = ProjectId.make(crypto.randomUUID());        yield* dispatch({          type: "project.create",          commandId: CommandId.make(crypto.randomUUID()),          projectId,          title,          workspaceRoot,          defaultModelSelection: getAutoBootstrapDefaultModelSelection(),          createdAt: DateTime.formatIso(yield* DateTime.now),        });        return `Added project ${projectId} (${title}) at ${workspaceRoot}.`;      }),    ),  ),); const projectRemoveCommand = Command.make("remove", {  ...projectLocationFlags,  project: Argument.string("project").pipe(    Argument.withDescription("Project id or workspace root to remove."),  ),}).pipe(  Command.withDescription("Remove a project."),  Command.withHandler((flags) =>    runProjectMutation(      flags,      Effect.fn("projectRemoveMutation")(function* ({        snapshot,        dispatch,      }: {        readonly snapshot: OrchestrationReadModel;        readonly dispatch: (          command: ProjectCliDispatchCommand,        ) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>;      }) {        const project = yield* findActiveProjectTarget({          snapshot,          identifier: flags.project,        });        yield* dispatch({          type: "project.delete",          commandId: CommandId.make(crypto.randomUUID()),          projectId: project.id,        });        return `Removed project ${project.id} (${project.title}).`;      }),    ),  ),); const projectRenameCommand = Command.make("rename", {  ...projectLocationFlags,  project: Argument.string("project").pipe(    Argument.withDescription("Project id or workspace root to rename."),  ),  title: Argument.string("title").pipe(Argument.withDescription("New project title.")),}).pipe(  Command.withDescription("Rename a project."),  Command.withHandler((flags) =>    runProjectMutation(      flags,      Effect.fn("projectRenameMutation")(function* ({        snapshot,        dispatch,      }: {        readonly snapshot: OrchestrationReadModel;        readonly dispatch: (          command: ProjectCliDispatchCommand,        ) => Effect.Effect<void, Error, FileSystem.FileSystem | HttpClient.HttpClient | Path.Path>;      }) {        const project = yield* findActiveProjectTarget({          snapshot,          identifier: flags.project,        });        const nextTitle = yield* resolveProjectTitle(project.workspaceRoot, flags.title);        if (nextTitle === project.title) {          return `Project ${project.id} is already named ${nextTitle}.`;        }         yield* dispatch({          type: "project.meta.update",          commandId: CommandId.make(crypto.randomUUID()),          projectId: project.id,          title: nextTitle,        });        return `Renamed project ${project.id} to ${nextTitle}.`;      }),    ),  ),); export const projectCommand = Command.make("project").pipe(  Command.withDescription("Manage projects."),  Command.withSubcommands([projectAddCommand, projectRemoveCommand, projectRenameCommand]),);