2456 lines · 93.5 KB · TypeScript
RawDownload
import { runGitHubStackAction, type GitHubStackActionError } from "./githubStackActions.ts";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 PullRequestStackHead,  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 PullRequestLabelCandidateList,  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,  decodePullRequestHeadsJson,  decodePullRequestListJson,  decodePullRequestNodeIdJson,  decodePullRequestSearchJson,  decodePullRequestStacksJson,  decodePullRequestStatsJson,  decodeReactionSubjectScopeJson,  decodeRepositoryAccessJson,  decodeReviewerCandidatesJson,  decodeLabelCandidatesJson,  buildLabelRequestJson,  LABEL_CANDIDATES_GRAPHQL_QUERY,  decodeReviewDismissalsJson,  decodeReviewThreadCommentsJson,  decodeReviewThreadsJson,  buildPullRequestStatsGraphQlQuery,  buildPullRequestStackMembershipsGraphQlQuery,  decodePullRequestStackMembershipsJson,  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,  REVERT_PULL_REQUEST_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,  decodeWorkflowRunApprovalsJson,  type GitHubBaseComparison,  type GitHubPullRequestDetail,  type GitHubPullRequestActivity,  type GitHubPullRequestHead,  type GitHubPullRequestListItem,  type GitHubPullRequestSearchItem,  type GitHubPullRequestStack,  type GitHubReviewThreadComments,  type GitHubRepositoryAccess,  type GitHubWorkflowRunApproval,  type GitHubReviewThreadEntry,  type GitHubReviewThreadPage,  type GitHubViewerAccess,} from "./gitHubPullRequestJson.ts";import type { ProviderChangeRequestSummary, 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.TaggedError<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.TaggedError<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: gh answered, but the pull request carried no update time. */export class GitHubPullRequestUpdatedAtUnavailableError extends Schema.TaggedError<GitHubPullRequestUpdatedAtUnavailableError>()(  "GitHubPullRequestUpdatedAtUnavailableError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,    repository: Schema.String,    number: Schema.Int,  },) {  get detail(): string {    return `Pull request ${this.repository}#${this.number} reported no update time.`;  }   override get message(): string {    return `GitHub CLI failed in getPullRequestSummary: ${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.TaggedError<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.TaggedError<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.TaggedError<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.TaggedError<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.TaggedError<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.TaggedError<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}`;  }} /** GitHub answered successfully, but approving every returned workflow would be unsafe. */export class GitHubWorkflowApprovalRefusedError extends Schema.TaggedError<GitHubWorkflowApprovalRefusedError>()(  "GitHubWorkflowApprovalRefusedError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,    number: Schema.Int,    reason: Schema.Literals(["head-list-truncated", "head-not-unique", "run-list-truncated"]),    observedCount: Schema.Int,    limit: Schema.Int,  },) {  get detail(): string {    if (this.reason === "head-list-truncated") {      return `GitHub returned more than ${this.limit} pull requests for this head branch.`;    }    if (this.reason === "head-not-unique") {      return `The head revision matched ${this.observedCount} pull requests instead of uniquely matching #${this.number}.`;    }    return `GitHub returned more than ${this.limit} workflow runs awaiting approval.`;  }   override get message(): string {    return `GitHub CLI refused listWorkflowRunsRequiringApproval: ${this.detail}`;  }} /** GitHub omitted the immutable head identity needed to scope an approval safely. */export class GitHubWorkflowApprovalHeadUnavailableError extends Schema.TaggedError<GitHubWorkflowApprovalHeadUnavailableError>()(  "GitHubWorkflowApprovalHeadUnavailableError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,    number: Schema.Int,  },) {  get detail(): string {    return `GitHub did not report a complete head revision for #${this.number}.`;  }   override get message(): string {    return `GitHub CLI refused approve-workflows: ${this.detail}`;  }} /** The pull request moved after its approval candidates were read. */export class GitHubWorkflowApprovalHeadChangedError extends Schema.TaggedError<GitHubWorkflowApprovalHeadChangedError>()(  "GitHubWorkflowApprovalHeadChangedError",  {    command: Schema.Literal("gh"),    cwd: Schema.String,    number: Schema.Int,  },) {  get detail(): string {    return `The head revision of #${this.number} changed before its workflows could be approved.`;  }   override get message(): string {    return `GitHub CLI refused approve-workflows: ${this.detail}`;  }} export type GitHubPullRequestCliError =  | GitHubStackActionError  | GitHubCli.GitHubCliError  | GitHubPullRequestReadError  | GitHubDiffCursorError  | GitHubDiffCommitError  | GitHubDiffRevisionsUnavailableError  | GitHubDiffFileContentsUnavailableError  | GitHubRepositorySelectorError  | GitHubSubjectScopeError  | GitHubWorkflowApprovalRefusedError  | GitHubWorkflowApprovalHeadUnavailableError  | GitHubWorkflowApprovalHeadChangedError  | SourceControlRateLimit.SourceControlRateLimitPausedError  | GitHubViewerLoginUnavailableError  | GitHubPullRequestUpdatedAtUnavailableError; /** 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 getPullRequestSummary: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;    }) => Effect.Effect<ProviderChangeRequestSummary, GitHubPullRequestCliError>;     readonly getPullRequestDetail: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;    }) => Effect.Effect<GitHubPullRequestDetail, GitHubPullRequestCliError>;     readonly listWorkflowRunsRequiringApproval: (input: {      readonly cwd: string;      readonly repository: string;      readonly host: string;      readonly number: number;      readonly headSha: string;      readonly headBranch: string;      readonly headRepositoryOwner: string;      readonly isCrossRepository: true;    }) => Effect.Effect<ReadonlyArray<GitHubWorkflowRunApproval>, GitHubPullRequestCliError>;     /**     * The host-native stack this pull request is in, or null when it is in none — which is also     * the answer for a host that refuses the stacks preview altogether.     */    readonly getPullRequestStack: (input: {      readonly cwd: string;      readonly includeDetails?: boolean;      readonly repository: string;      readonly host: string;      readonly number: number;    }) => Effect.Effect<GitHubPullRequestStack | null, GitHubPullRequestCliError>;

Lines 1–500 of 2456.