1863 lines · 71.2 KB · TypeScript
RawDownload
import * as Context from "effect/Context";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Result from "effect/Result";import * as Schema from "effect/Schema";import {  resolvePullRequestAuthorFilter,  type PullRequestAction,  type PullRequestActor,  type PullRequestInvolvement,  type PullRequestListFilters,  type PullRequestListState,  type PullRequestMergeMethod,  type PullRequestOmittedFileStat,  type PullRequestReaction,  type PullRequestReactionContent,  type PullRequestReviewCommentDraft,  type PullRequestReviewVerdict,  type PullRequestReviewerCandidateList,  type PullRequestReviewerKind,  type PullRequestThreadCommentsResult,  type PullRequestUpdateMethod,} from "@t3tools/contracts"; import * as GitHubCli from "../sourceControl/GitHubCli.ts";import * as GitHubGraphQlBudget from "../sourceControl/githubGraphQlBudget.ts";import * as SourceControlRateLimit from "../sourceControl/SourceControlRateLimit.ts";import {  ACTOR_AVATARS_GRAPHQL_QUERY,  ADD_REACTION_GRAPHQL_MUTATION,  buildReviewSubmissionJson,  buildReviewerRequestJson,  decodeActorAvatarsJson,  decodePullRequestActivityJson,  decodePullRequestDetailJson,  decodePullRequestFilesJson,  decodePullRequestListJson,  decodePullRequestNodeIdJson,  decodePullRequestSearchJson,  decodePullRequestStatsJson,  decodeReactionSubjectScopeJson,  decodeRepositoryAccessJson,  decodeReviewerCandidatesJson,  decodeReviewDismissalsJson,  decodeReviewThreadCommentsJson,  decodeReviewThreadsJson,  buildPullRequestStatsGraphQlQuery,  encodeGraphQlRequestJson,  pullRequestSearchGraphQlQuery,  PULL_REQUEST_SEARCH_MAX_ROWS,  PULL_REQUEST_ACTIVITY_JSON_FIELDS,  BASE_COMPARISON_GRAPHQL_QUERY,  decodeBaseComparisonJson,  PULL_REQUEST_DETAIL_JSON_FIELDS,  PULL_REQUEST_LIST_JSON_FIELDS,  PULL_REQUEST_NODE_ID_GRAPHQL_QUERY,  REACTION_SUBJECT_PULL_REQUEST_GRAPHQL_QUERY,  REMOVE_REACTION_GRAPHQL_MUTATION,  gitHubReactionContent,  REPOSITORY_ACCESS_JSON_FIELDS,  RESOLVE_REVIEW_THREAD_GRAPHQL_MUTATION,  REVIEWER_CANDIDATES_GRAPHQL_QUERY,  REVIEW_THREAD_COMMENTS_GRAPHQL_QUERY,  REVIEW_DISMISSALS_GRAPHQL_QUERY,  REVIEW_THREAD_REPLY_GRAPHQL_MUTATION,  REVIEW_THREADS_GRAPHQL_QUERY,  reviewThreadConversation,  UNRESOLVE_REVIEW_THREAD_GRAPHQL_MUTATION,  UPDATE_ISSUE_COMMENT_GRAPHQL_MUTATION,  UPDATE_PULL_REQUEST_GRAPHQL_MUTATION,  UPDATE_REVIEW_COMMENT_GRAPHQL_MUTATION,  VIEWER_PERMISSIONS_GRAPHQL_QUERY,  decodeViewerPermissionsJson,  type GitHubBaseComparison,  type GitHubPullRequestDetail,  type GitHubPullRequestActivity,  type GitHubPullRequestListItem,  type GitHubPullRequestSearchItem,  type GitHubReviewThreadComments,  type GitHubRepositoryAccess,  type GitHubReviewThreadEntry,  type GitHubReviewThreadPage,  type GitHubViewerAccess,} from "./gitHubPullRequestJson.ts";import type { ProviderListCursor } from "./PullRequestProvider.ts"; /** * Names the read that produced unusable output, so a failure reports the call it came from * rather than borrowing another operation's message. */export class GitHubPullRequestReadError extends Schema.TaggedErrorClass<GitHubPullRequestReadError>()(  "GitHubPullRequestReadError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,    operation: Schema.String,    cause: Schema.Defect(),  },) {  get detail(): string {    return `GitHub CLI returned an unreadable ${this.operation} response.`;  }   override get message(): string {    return `GitHub CLI failed in ${this.operation}: ${this.detail}`;  }} /** Not a decode failure: gh answered, the account it answered for just has no login. */export class GitHubViewerLoginUnavailableError extends Schema.TaggedErrorClass<GitHubViewerLoginUnavailableError>()(  "GitHubViewerLoginUnavailableError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,  },) {  get detail(): string {    return "GitHub CLI returned no login for the authenticated account.";  }   override get message(): string {    return `GitHub CLI failed in getViewerLogin: ${this.detail}`;  }} /** Not a decode failure: the reader asked to carry on from a cursor this walk never handed out. */export class GitHubDiffCursorError extends Schema.TaggedErrorClass<GitHubDiffCursorError>()(  "GitHubDiffCursorError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,  },) {  get detail(): string {    return "The diff cursor was not one this pull request handed out.";  }   override get message(): string {    return `GitHub CLI failed in getPullRequestDiff: ${this.detail}`;  }} /** Not a decode failure: the reader named a commit that is not a sha this repository could hold. */export class GitHubDiffCommitError extends Schema.TaggedErrorClass<GitHubDiffCommitError>()(  "GitHubDiffCommitError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,  },) {  get detail(): string {    return "The named commit was not a commit sha.";  }   override get message(): string {    return `GitHub CLI failed in getPullRequestDiff: ${this.detail}`;  }} /** The revisions read successfully, but cannot name both sides this file needs. */export class GitHubDiffRevisionsUnavailableError extends Schema.TaggedErrorClass<GitHubDiffRevisionsUnavailableError>()(  "GitHubDiffRevisionsUnavailableError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,    number: Schema.Int,    commit: Schema.optional(Schema.String),  },) {  get detail(): string {    return this.commit === undefined      ? `Pull request #${this.number} reported no usable base and head revisions.`      : `Commit ${this.commit} reported no usable revisions for this file.`;  }   override get message(): string {    return `GitHub CLI failed in getPullRequestDiffFileContents: ${this.detail}`;  }} /** A blob exists, but expanding it would be unsafe or would not produce text. */export class GitHubDiffFileContentsUnavailableError extends Schema.TaggedErrorClass<GitHubDiffFileContentsUnavailableError>()(  "GitHubDiffFileContentsUnavailableError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,    path: Schema.String,    reason: Schema.Literals(["oversized", "binary"]),  },) {  get detail(): string {    return this.reason === "oversized"      ? `The diff file '${this.path}' exceeds the 1 MB expansion limit.`      : `The diff file '${this.path}' is binary.`;  }   override get message(): string {    return `GitHub CLI failed in getPullRequestDiffFileContents: ${this.detail}`;  }} /** * Not a decode failure: a repository was named that cannot go into a search or into a GraphQL * document as itself. Every qualifier and every alias below is composed from `owner/name`, so a * name that is not one is refused here rather than escaped into something GitHub might read as a * qualifier of its own. */export class GitHubRepositorySelectorError extends Schema.TaggedErrorClass<GitHubRepositorySelectorError>()(  "GitHubRepositorySelectorError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,    operation: Schema.String,  },) {  get detail(): string {    return "A repository was named that GitHub cannot address.";  }   override get message(): string {    return `GitHub CLI failed in ${this.operation}: ${this.detail}`;  }} /** Not a decode failure: the reader named a subject this pull request never handed out. */export class GitHubSubjectScopeError extends Schema.TaggedErrorClass<GitHubSubjectScopeError>()(  "GitHubSubjectScopeError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,    operation: Schema.String,  },) {  get detail(): string {    return "The named subject did not belong to the named pull request.";  }   override get message(): string {    return `GitHub CLI failed in ${this.operation}: ${this.detail}`;  }} export type GitHubPullRequestCliError =  | GitHubCli.GitHubCliError  | GitHubPullRequestReadError  | GitHubDiffCursorError  | GitHubDiffCommitError  | GitHubDiffRevisionsUnavailableError  | GitHubDiffFileContentsUnavailableError  | GitHubRepositorySelectorError  | GitHubSubjectScopeError  | SourceControlRateLimit.SourceControlRateLimitPausedError  | GitHubViewerLoginUnavailableError; /** A large pull request can produce a multi-megabyte patch; past this it is truncated. */const DIFF_MAX_OUTPUT_BYTES = 8 * 1024 * 1024;const DIFF_TIMEOUT_MS = 60_000;/** Pierre expansion is for source files, not blobs large enough to stall a review surface. */const DIFF_FILE_MAX_OUTPUT_BYTES = 1024 * 1024; /** A search-free fallback may scan older rows for local filters, but never the whole repository. */const PULL_REQUEST_FALLBACK_MAX_ROWS = 1_000; /** What the files API serves at most in one response, which is what one slice is made of. */const DIFF_FILES_PAGE_SIZE = 100; /** * Pages of review threads to follow before the conversation is reported as truncated. GitHub * serves a hundred threads a page, so this is a thousand threads — past anything a pull request * a person is reading has, and short of walking a repository-sized conversation forever. */const REVIEW_THREAD_PAGES = 10; export interface GitHubPullRequestListBatch {  readonly items: ReadonlyArray<GitHubPullRequestListItem>;  readonly truncated: boolean;  /** False for a page GitHub would not search, which came back in `gh`'s own order instead. */  readonly continues: boolean;} export interface GitHubPullRequestStat {  readonly repository: string;  readonly number: number;  readonly additions: number;  readonly deletions: number;} /** * Aliased lookups per request, and requests at once. Measured over a hundred rows: one request * carrying all hundred takes ~5.2s, four of twenty-five in parallel ~2.1s. */const STAT_ALIASES_PER_REQUEST = 25;const STAT_REQUEST_CONCURRENCY = 4; export interface GitHubPullRequestSearchBatch {  /** Rows across every repository asked for, newest update first, each naming its own. */  readonly items: ReadonlyArray<GitHubPullRequestSearchItem>;  readonly truncated: boolean;} export interface GitHubPullRequestDiffSlice {  readonly patch: string;  /** Files in this slice had their hunks withheld, as opposed to there being more slices. */  readonly truncated: boolean;  /** Where the next slice starts, or null once the patch is whole. */  readonly nextCursor: string | null;  /** GitHub's own counts for the files whose hunks it withheld from this slice. */  readonly omittedFileStats?: ReadonlyArray<PullRequestOmittedFileStat>;} export class GitHubPullRequestCli extends Context.Service<  GitHubPullRequestCli,  {    readonly getViewerLogin: (input: {      readonly cwd: string;    }) => Effect.Effect<string, GitHubPullRequestCliError>;     readonly listPullRequests: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly state: PullRequestListState;      readonly involvement: PullRequestInvolvement;      readonly viewer: string;      readonly limit: number;      /** Free text for `--search`, matched as one literal phrase. */      readonly query?: string | undefined;      /** Where to carry on from, as a `updated:` qualifier on the same search. */      readonly cursor?: ProviderListCursor | undefined;      /** Further narrowings, as qualifiers on the search and as a local pass on the fallback. */      readonly filters?: PullRequestListFilters | undefined;    }) => Effect.Effect<GitHubPullRequestListBatch, GitHubPullRequestCliError>;     /**     * The same listing for a whole host in one search. `limit` is the size of the slice across     * all of the repositories rather than per repository, because that is what a search answers:     * the newest rows of the lot, which is exactly the page.     */    readonly searchPullRequests: (input: {      /** Any checkout on the host; the search names its repositories itself. */      readonly cwd: string;      readonly host: string;      readonly repositories: ReadonlyArray<string>;      readonly state: PullRequestListState;      readonly involvement: PullRequestInvolvement;      readonly viewer: string;      readonly limit: number;      readonly query?: string | undefined;      readonly cursor?: ProviderListCursor | undefined;      readonly filters?: PullRequestListFilters | undefined;    }) => Effect.Effect<GitHubPullRequestSearchBatch, GitHubPullRequestCliError>;     /** The line counts the search leaves out, for rows already on the page. */    readonly listPullRequestStats: (input: {      readonly cwd: string;      readonly host: string;      readonly changeRequests: ReadonlyArray<{        readonly repository: string;        readonly number: number;      }>;    }) => Effect.Effect<ReadonlyArray<GitHubPullRequestStat>, GitHubPullRequestCliError>;     readonly getPullRequestDetail: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;    }) => Effect.Effect<GitHubPullRequestDetail, GitHubPullRequestCliError>;     /**     * How far the branch trails its base, and whether this viewer may update it. Its own read     * because the comparison needs the head ref the detail answers with — a fork's branch is not     * addressable in the base repository by name alone.     */    readonly getPullRequestBaseComparison: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;      /** Qualified `owner:branch`, which is the only form a fork's head resolves under. */      readonly headRef: string;      /** Manual action checks may use the quota held back from automatic reads. */      readonly allowReserve?: boolean | undefined;    }) => Effect.Effect<GitHubBaseComparison, GitHubPullRequestCliError>;     readonly getPullRequestActivity: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;    }) => Effect.Effect<GitHubPullRequestActivity, GitHubPullRequestCliError>;     readonly getPullRequestDiff: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;      /** Absent asks for the first slice; anything else is a cursor a slice handed back. */      readonly cursor?: string | undefined;      /** One commit's own changes, rather than everything the pull request carries. */      readonly commit?: string | undefined;    }) => Effect.Effect<GitHubPullRequestDiffSlice, GitHubPullRequestCliError>;     readonly getPullRequestDiffFileContents: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;      readonly commit?: string | undefined;      readonly changeType: "change" | "rename-pure" | "rename-changed" | "new" | "deleted";      readonly oldPath: string;      readonly newPath: string;    }) => Effect.Effect<      { readonly oldContents: string; readonly newContents: string },      GitHubPullRequestCliError    >;     readonly listReviewThreadComments: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;    }) => Effect.Effect<GitHubReviewThreadComments, GitHubPullRequestCliError>;     /** One request for a listing's authors, since no `gh` JSON field reports an avatar. */    readonly listActorAvatars: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly ids: ReadonlyArray<string>;    }) => Effect.Effect<ReadonlyMap<string, string>, GitHubPullRequestCliError>;     readonly getReviewThreadComments: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;      readonly threadId: string;      readonly cursor: string;    }) => Effect.Effect<PullRequestThreadCommentsResult, GitHubPullRequestCliError>;     /** One `gh repo view`, which answers what the repository allows and where the viewer stands. */    readonly getRepositoryAccess: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;    }) => Effect.Effect<GitHubRepositoryAccess, GitHubPullRequestCliError>;     /** The viewer's standing on its own, for deciding a write without reading the whole detail. */    readonly getViewerAccess: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;      /** Manual action checks may use the quota held back from automatic reads. */      readonly allowReserve?: boolean | undefined;    }) => Effect.Effect<GitHubViewerAccess, GitHubPullRequestCliError>;     /** Who this pull request may be sent to, and who it has already been sent to. */    readonly listReviewerCandidates: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;    }) => Effect.Effect<PullRequestReviewerCandidateList, GitHubPullRequestCliError>;     readonly setReviewerRequest: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;      readonly reviewers: ReadonlyArray<{        readonly id: string;        readonly kind: PullRequestReviewerKind;      }>;      /** False deletes the same collection a request posts to, which takes the request back. */      readonly requested: boolean;    }) => Effect.Effect<void, GitHubPullRequestCliError>;     readonly runPullRequestAction: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;      readonly action: PullRequestAction;      readonly mergeMethod?: PullRequestMergeMethod;      readonly updateMethod?: PullRequestUpdateMethod;    }) => Effect.Effect<void, GitHubPullRequestCliError>;     readonly commentOnPullRequest: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;      readonly body: string;    }) => Effect.Effect<void, GitHubPullRequestCliError>;     readonly submitReview: (input: {      readonly cwd: string;      readonly repository: string;

Lines 1–500 of 1863.