145 lines · 4.8 KB · TypeScript
RawDownload
import { Effect, Layer, Option, Result, Schema } from "effect";import {  SourceControlProviderError,  type ChangeRequest,  type ChangeRequestState,  type GitHubCliError,} from "@t3tools/contracts"; import { GitHubCli, type GitHubPullRequestSummary } from "./GitHubCli.ts";import { decodeGitHubPullRequestListJson } from "../git/githubPullRequests.ts";import { SourceControlProvider, type SourceControlProviderShape } from "./SourceControlProvider.ts"; function providerError(operation: string, cause: GitHubCliError): SourceControlProviderError {  return new SourceControlProviderError({    provider: "github",    operation,    detail: cause.detail,    cause,  });} function toChangeRequest(summary: GitHubPullRequestSummary): ChangeRequest {  return {    provider: "github",    number: summary.number,    title: summary.title,    url: summary.url,    baseRefName: summary.baseRefName,    headRefName: summary.headRefName,    state: summary.state ?? "open",    updatedAt: Option.none(),    ...(summary.isCrossRepository !== undefined      ? { isCrossRepository: summary.isCrossRepository }      : {}),    ...(summary.headRepositoryNameWithOwner !== undefined      ? { headRepositoryNameWithOwner: summary.headRepositoryNameWithOwner }      : {}),    ...(summary.headRepositoryOwnerLogin !== undefined      ? { headRepositoryOwnerLogin: summary.headRepositoryOwnerLogin }      : {}),  };} export const make = Effect.fn("makeGitHubSourceControlProvider")(function* () {  const github = yield* GitHubCli;   const listChangeRequests: SourceControlProviderShape["listChangeRequests"] = (input) => {    if (input.state === "open") {      return github        .listOpenPullRequests({          cwd: input.cwd,          headSelector: input.headSelector,          ...(input.limit !== undefined ? { limit: input.limit } : {}),        })        .pipe(          Effect.map((items) => items.map(toChangeRequest)),          Effect.mapError((error) => providerError("listChangeRequests", error)),        );    }     const stateArg: ChangeRequestState | "all" = input.state;    return github      .execute({        cwd: input.cwd,        args: [          "pr",          "list",          "--head",          input.headSelector,          "--state",          stateArg,          "--limit",          String(input.limit ?? 20),          "--json",          "number,title,url,baseRefName,headRefName,state,mergedAt,updatedAt,isCrossRepository,headRepository,headRepositoryOwner",        ],      })      .pipe(        Effect.flatMap((result) => {          const raw = result.stdout.trim();          if (raw.length === 0) {            return Effect.succeed([]);          }          return Effect.sync(() => decodeGitHubPullRequestListJson(raw)).pipe(            Effect.flatMap((decoded) =>              Result.isSuccess(decoded)                ? Effect.succeed(                    decoded.success.map((item) => ({                      ...toChangeRequest(item),                      updatedAt: item.updatedAt,                    })),                  )                : Effect.fail(                    new SourceControlProviderError({                      provider: "github",                      operation: "listChangeRequests",                      detail: "GitHub CLI returned invalid change request JSON.",                      cause: decoded.failure,                    }),                  ),            ),          );        }),        Effect.mapError((error) =>          Schema.is(SourceControlProviderError)(error)            ? error            : providerError("listChangeRequests", error),        ),      );  };   return SourceControlProvider.of({    kind: "github",    listChangeRequests,    getChangeRequest: (input) =>      github.getPullRequest(input).pipe(        Effect.map(toChangeRequest),        Effect.mapError((error) => providerError("getChangeRequest", error)),      ),    createChangeRequest: (input) =>      github        .createPullRequest({          cwd: input.cwd,          baseBranch: input.baseRefName,          headSelector: input.headSelector,          title: input.title,          bodyFile: input.bodyFile,        })        .pipe(Effect.mapError((error) => providerError("createChangeRequest", error))),    getRepositoryCloneUrls: (input) =>      github        .getRepositoryCloneUrls(input)        .pipe(Effect.mapError((error) => providerError("getRepositoryCloneUrls", error))),    getDefaultBranch: (input) =>      github        .getDefaultBranch(input)        .pipe(Effect.mapError((error) => providerError("getDefaultBranch", error))),    checkoutChangeRequest: (input) =>      github        .checkoutPullRequest(input)        .pipe(Effect.mapError((error) => providerError("checkoutChangeRequest", error))),  });}); export const layer = Layer.effect(SourceControlProvider, make());