431 lines · 15.1 KB · TypeScript
RawDownload
import {  CommandId,  AuthAdministrativeScopes,  EnvironmentHttpApi,  EnvironmentHttpCommonError,  type OrchestrationReadModel,  ProjectId,  type ClientOrchestrationCommand,} from "@t3tools/contracts";import * as Console from "effect/Console";import * as Crypto from "effect/Crypto";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, HttpClientError } from "effect/unstable/http";import * as HttpApiClient from "effect/unstable/httpapi/HttpApiClient"; import * as EnvironmentAuth from "../auth/EnvironmentAuth.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 projectCommandUuid = Crypto.Crypto.pipe(  Effect.flatMap((crypto) => crypto.randomUUIDv4),  Effect.mapError(    () =>      new ProjectCommandError({        message: "Failed to generate a project command identifier.",      }),  ),); const ProjectCliRuntimeLive = Layer.mergeAll(  WorkspacePathsLive,  OrchestrationLayerLive.pipe(    Layer.provideMerge(RepositoryIdentityResolverLive),    Layer.provideMerge(SqlitePersistenceLayerLive),  ),); const PROJECT_CLI_LIVE_SERVER_TIMEOUT = Duration.seconds(1);const isEnvironmentHttpCommonError = Schema.is(EnvironmentHttpCommonError);const withProjectCliSessionToken = <A, E, R>(  environmentAuth: EnvironmentAuth.EnvironmentAuthShape,  run: (token: string) => Effect.Effect<A, E, R>,) =>  Effect.acquireUseRelease(    environmentAuth.issueSession({      scopes: AuthAdministrativeScopes,      label: "t3 project cli",    }),    (issued) => run(issued.token),    (issued) => environmentAuth.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 failLiveServerRequest = (cause: unknown) => {  if (isEnvironmentHttpCommonError(cause)) {    return Effect.fail(      new ProjectCommandError({        message: `Server request failed (${cause.code}, trace ${cause.traceId}).`,      }),    );  }  if (HttpClientError.isHttpClientError(cause) && cause.response !== undefined) {    return Effect.fail(      new ProjectCommandError({        message: `Server request failed with undeclared status ${cause.response.status}.`,      }),    );  }  return Effect.fail(    new ProjectCommandError({      message: `Failed to call running server: ${String(cause)}.`,    }),  );}; const makeLiveServerClient = (origin: string) =>  HttpApiClient.make(EnvironmentHttpApi, {    baseUrl: origin,  }); 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) =>  Effect.gen(function* () {    const client = yield* makeLiveServerClient(origin);    return yield* client.orchestration.snapshot({      headers: { authorization: `Bearer ${bearerToken}` },    });  }).pipe(withProjectCliLiveServerTimeout, Effect.catch(failLiveServerRequest)); const dispatchLiveOrchestrationCommand = (  origin: string,  bearerToken: string,  command: ProjectCliDispatchCommand,) =>  Effect.gen(function* () {    const client = yield* makeLiveServerClient(origin);    yield* client.orchestration.dispatch({      headers: { authorization: `Bearer ${bearerToken}` },      payload: command,    } as Parameters<typeof client.orchestration.dispatch>[0]);  }).pipe(withProjectCliLiveServerTimeout, Effect.catch(failLiveServerRequest)); const getOfflineSnapshot = Effect.fn("getOfflineSnapshot")(function* () {  const projectionSnapshotQuery = yield* ProjectionSnapshotQuery;  return yield* projectionSnapshotQuery.getSnapshot();}); const tryResolveLiveProjectExecutionMode = Effect.fn("tryResolveLiveProjectExecutionMode")(  function* (environmentAuth: EnvironmentAuth.EnvironmentAuthShape, config: ServerConfigShape) {    const runtimeState = yield* readPersistedServerRuntimeState(config.serverRuntimeStatePath);    if (Option.isNone(runtimeState)) {      return Option.none<{ readonly origin: string }>();    }     const attempt = withProjectCliSessionToken(environmentAuth, (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,    Crypto.Crypto | 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 environmentAuth = yield* EnvironmentAuth.EnvironmentAuth;    const liveMode = yield* tryResolveLiveProjectExecutionMode(environmentAuth, config);     if (Option.isSome(liveMode)) {      return yield* withProjectCliSessionToken(environmentAuth, (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(EnvironmentAuth.runtimeLayer, 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(yield* projectCommandUuid);        yield* dispatch({          type: "project.create",          commandId: CommandId.make(yield* projectCommandUuid),          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(yield* projectCommandUuid),          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(yield* projectCommandUuid),          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]),);