From f380d3e362fb5ffecc38e0307dec52ceabb6d7eb Mon Sep 17 00:00:00 2001 From: Vincent Date: Tue, 27 Aug 2024 15:57:31 +0200 Subject: [PATCH] feat: consider issue comments in the attention set This is a large rework of how pull requests are searched and fetched from GitHub. It now happens in two steps. Searching returns only node IDs. Pull requests are then fetched from this node ID. This allows to retrieve much more information at once when fetching the pull request, while still keeping the request cost reasonable (cost=1), to avoid hitting rate limiting. As a consequence, we now fetch issue comments, reviews and review comments, and consider all of them when computing the attention set. Closes #60 --- apps/webapp/package.json | 1 + apps/webapp/src/components/PullTable.tsx | 14 +- apps/webapp/src/worker/instance.ts | 69 ++- .../tests/components/CommandBar.test.tsx | 4 +- apps/webapp/tests/worker/instance.test.ts | 38 +- .../content/docs/user-guide/attention-set.md | 6 +- .../src/content/docs/user-guide/inbox.md | 1 - packages/github/package.json | 6 +- packages/github/src/attention.ts | 57 +- packages/github/src/client.ts | 494 +++++++++++------- .../recording.har | 170 ++++++ .../should-get-pull_2195211117/recording.har | 170 ++++++ .../recording.har | 48 +- .../recording.har | 76 +-- packages/github/tests/attention.test.ts | 211 +++++--- packages/github/tests/client.test.ts | 105 +++- packages/model/src/comment.ts | 8 - packages/model/src/index.ts | 1 - packages/model/src/pull.ts | 71 ++- packages/testing/src/index.ts | 32 +- pnpm-lock.yaml | 450 +++++++++++----- 21 files changed, 1403 insertions(+), 629 deletions(-) create mode 100644 packages/github/tests/__recordings__/should-error-when-pull-does-not-exist_3266903050/recording.har create mode 100644 packages/github/tests/__recordings__/should-get-pull_2195211117/recording.har delete mode 100644 packages/model/src/comment.ts diff --git a/apps/webapp/package.json b/apps/webapp/package.json index b5915f9..d8c37e9 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -7,6 +7,7 @@ "dev": "vite --clearScreen false", "build": "vite build", "preview": "vite preview", + "typecheck": "tsc --noEmit", "lint": "eslint \"src/**/*.{ts,tsx}\"", "test": "vitest run", "coverage": "vitest run --coverage" diff --git a/apps/webapp/src/components/PullTable.tsx b/apps/webapp/src/components/PullTable.tsx index 026bbaf..49b6ad0 100644 --- a/apps/webapp/src/components/PullTable.tsx +++ b/apps/webapp/src/components/PullTable.tsx @@ -11,13 +11,13 @@ export type Props = { onStar?: (v: Pull) => void, } -const formatDate = (d: string) => { +const formatDate = (d: Date|string) => { return new Date(d).toLocaleDateString("en", { - year: 'numeric', - month: 'long', - day: 'numeric', - hour: 'numeric', - minute: 'numeric' + year: "numeric", + month: "long", + day: "numeric", + hour: "numeric", + minute: "numeric" }); } @@ -100,7 +100,7 @@ export default function PullTable({ pulls, onStar }: Props) { - + diff --git a/apps/webapp/src/worker/instance.ts b/apps/webapp/src/worker/instance.ts index 6c723ac..4bdd466 100644 --- a/apps/webapp/src/worker/instance.ts +++ b/apps/webapp/src/worker/instance.ts @@ -1,8 +1,8 @@ import * as Comlink from "comlink"; -import { groupBy, unique } from "remeda"; +import { groupBy, indexBy, prop, unique } from "remeda"; import { db } from "../lib/db"; import { splitQueries } from "@repo/github"; -import type { Pull, Connection } from "@repo/model"; +import type { Pull } from "@repo/model"; import { GitHubClient, isInAttentionSet } from "@repo/github"; import { gitHubClient } from "../github"; @@ -55,49 +55,70 @@ export async function syncPullsOnce(client: GitHubClient, force: boolean = false await executeActivity("syncPulls", syncPullsIntervalMillis, force, async () => { const connections = await db.connections.toArray(); const sections = await db.sections.toArray(); - const stars = new Set((await db.stars.toArray()).map(star => star.uid)); - const connectionByPull: Record = {}; - const rawPulls: Pull[] = ( + const rawResults = ( await Promise.all(sections.flatMap(section => { return connections.flatMap(connection => { const queries = splitQueries(section.search); return queries.flatMap(async query => { - const pulls = await client.getPulls(connection, query); - return pulls.map(pull => { - connectionByPull[pull.uid] = connection; - const sections = [section.id]; - const starred = stars.has(pull.uid) ? 1 : 0; - return { ...pull, sections, starred }; - }); + const pulls = await client.searchPulls(connection, query); + return pulls.map(res => ({ + ...res, + uid: `${connection.id}:${res.id}`, + sections: [section.id], + connection: connection.id, + })); }); }); })) ).flat(); // Deduplicate pull requests present in multiple sections. - const pulls: Pull[] = Object.values(groupBy(rawPulls, pull => pull.uid)) + const results = Object.values(groupBy(rawResults, pull => pull.uid)) .map(vs => ({ ...vs[0], sections: vs.flatMap(v => unique(v.sections)) })); - - // Compute whether pull requests are in the attention set after they have been - // deduplicated, since this can be quite expensive to do. + const stars = new Set((await db.stars.toArray()).map(star => star.uid)); + const connectionsById = indexBy(connections, prop("id")); const sectionsInAttentionSet = new Set(sections.filter(v => v.attention).map(v => v.id)); - for (const pull of pulls) { - if (pull.sections.some(v => sectionsInAttentionSet.has(v))) { - pull.attention = await isInAttentionSet(client, connectionByPull[pull.uid], pull); + const currentKeys = new Set(await db.pulls.toCollection().primaryKeys()); + + const stats = { new: 0, unchanged: 0, updated: 0 }; + + const pulls: Pull[] = await Promise.all(results.map(async res => { + if (currentKeys.has(res.uid)) { + const currentPull = await db.pulls.get(res.uid); + if (currentPull !== undefined && res.updatedAt <= currentPull.updatedAt) { + stats.unchanged += 1; + return currentPull; + } else { + stats.updated += 1; + } + } else { + stats.new += 1; } - } + const connection = connectionsById[res.connection]; + const pull = await client.getPull(connection, res.id); + const mayBeInAttentionSet = res.sections.some(v => sectionsInAttentionSet.has(v)); + return { + ...pull, + uid: res.uid, + connection: res.connection, + sections: res.sections, + host: connection.host, + starred: stars.has(res.uid) ? 1 : 0, + attention: mayBeInAttentionSet ? isInAttentionSet(connection, pull) : undefined, + } + })); // Upsert pull requests... await db.pulls.bulkPut(pulls); // ... and remove pull requests that are not anymore included in any sections. - const keys = new Set(await db.pulls.toCollection().primaryKeys()); - pulls.forEach(pull => keys.delete(pull.uid)); - await db.pulls.bulkDelete(Array.from(keys)); + const staleKeys = new Set(currentKeys); + pulls.forEach(pull => staleKeys.delete(pull.uid)); + await db.pulls.bulkDelete(Array.from(staleKeys)); - console.log(`Synced ${pulls.length} pull requests`); + console.log(`Synced ${pulls.length} pull requests: ${stats.new} new, ${stats.updated} updated, ${stats.unchanged} unchanged, ${staleKeys.size} deleted`); }); } diff --git a/apps/webapp/tests/components/CommandBar.test.tsx b/apps/webapp/tests/components/CommandBar.test.tsx index c872802..e9e3d21 100644 --- a/apps/webapp/tests/components/CommandBar.test.tsx +++ b/apps/webapp/tests/components/CommandBar.test.tsx @@ -12,8 +12,8 @@ describe("command bar", () => { beforeAll(async () => { user = userEvent.setup(); - await db.pulls.add(mockPull({ uid: "1:1", title: "Some title" })); - await db.pulls.add(mockPull({ uid: "1:2", title: "Another title" })); + await db.pulls.add(mockPull({ id: "PR_1", title: "Some title" })); + await db.pulls.add(mockPull({ id: "PR_2", title: "Another title" })); }) afterAll(async () => await db.pulls.clear()); diff --git a/apps/webapp/tests/worker/instance.test.ts b/apps/webapp/tests/worker/instance.test.ts index 89a8fd0..5342345 100644 --- a/apps/webapp/tests/worker/instance.test.ts +++ b/apps/webapp/tests/worker/instance.test.ts @@ -71,25 +71,25 @@ describe("sync pulls", () => { assert(connection !== undefined); const client = new TestGitHubClient(); - client.setPulls(connection, "author:@me draft:true", [ - mockPull({ uid: "1:1" }), - mockPull({ uid: "1:2" }), + client.setPullsBySearch(connection, "author:@me draft:true", [ + mockPull({ id: "PR_1", connection: connection.id }), + mockPull({ id: "PR_2", connection: connection.id }), ]); - client.setPulls(connection, "author:@me draft:false", [ - mockPull({ uid: "1:4" }), + client.setPullsBySearch(connection, "author:@me draft:false", [ + mockPull({ id: "PR_4", connection: connection.id }), ]); - client.setPulls(connection, "author:@me review:approved", [ - mockPull({ uid: "1:1" }), - mockPull({ uid: "1:3" }), + client.setPullsBySearch(connection, "author:@me review:approved", [ + mockPull({ id: "PR_1", connection: connection.id }), + mockPull({ id: "PR_3", connection: connection.id }), ]); connection = await db.connections.get("2"); assert(connection !== undefined); - client.setPulls(connection, "author:@me draft:true", [ - mockPull({ uid: "2:1" }), + client.setPullsBySearch(connection, "author:@me draft:true", [ + mockPull({ id: "PR_1", connection: connection.id }), ]); - client.setPulls(connection, "author:@me review:approved", [ - mockPull({ uid: "2:2" }), + client.setPullsBySearch(connection, "author:@me review:approved", [ + mockPull({ id: "PR_2", connection: connection.id }), ]); // WHEN syncing pull requests. @@ -97,19 +97,23 @@ describe("sync pulls", () => { // THEN every pull request must be present in the database. const pks = await db.pulls.toCollection().primaryKeys(); - expect(pks.sort()).toEqual(["1:1", "1:2", "1:3", "1:4", "2:1", "2:2"]); + expect(pks.sort()).toEqual(["1:PR_1", "1:PR_2", "1:PR_3", "1:PR_4", "2:PR_1", "2:PR_2"]); // THEN every pull request must be in the correct section(s). - let pull = await db.pulls.get("1:1"); + let pull = await db.pulls.get("1:PR_1"); + expect(pull).toBeDefined(); expect(pull?.sections).toEqual(["1", "2"]); - pull = await db.pulls.get("1:2"); + pull = await db.pulls.get("1:PR_2"); + expect(pull).toBeDefined(); expect(pull?.sections).toEqual(["1"]); - pull = await db.pulls.get("1:3"); + pull = await db.pulls.get("1:PR_3"); + expect(pull).toBeDefined(); expect(pull?.sections).toEqual(["2"]); - pull = await db.pulls.get("1:4"); + pull = await db.pulls.get("1:PR_4"); + expect(pull).toBeDefined(); expect(pull?.sections).toEqual(["1"]); // THEN the activity should have been updated. diff --git a/apps/website/src/content/docs/user-guide/attention-set.md b/apps/website/src/content/docs/user-guide/attention-set.md index 06c21b0..90c0edd 100644 --- a/apps/website/src/content/docs/user-guide/attention-set.md +++ b/apps/website/src/content/docs/user-guide/attention-set.md @@ -49,10 +49,10 @@ Whether a user is in the attention set of a pull request is determined by a set * A draft, merged or closed pull request has an empty attention set. * A requested reviewer (either directly or via a team) is in the attention set if the pull request is not already approved. Once they leave a review, they will not be "requested" anymore. -* An author or reviewer is in the attention set when somebody replied to them. -* An author is in the attention set when a reviewer left a comment in any thread. +* An author or reviewer is in the attention set when somebody replied in an unresolved discussion. +* An author is in the attention set when a reviewer left a comment in an unresolved discussion. * An author is in the attention set when CI is failing. -* An author is in the attention set when a pull request is approved. +* An author is in the attention set when the pull request is approved. :::note Those rules are designed to be "mostly correct", but they are not expected to be perfect and work for all use cases. diff --git a/apps/website/src/content/docs/user-guide/inbox.md b/apps/website/src/content/docs/user-guide/inbox.md index b6c8ec6..66bd2bf 100644 --- a/apps/website/src/content/docs/user-guide/inbox.md +++ b/apps/website/src/content/docs/user-guide/inbox.md @@ -26,7 +26,6 @@ The following dialog will open: The search query uses [GitHub's search language](https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests), with the following modifications: * A `type:pr` term is implicitely added, you do not need to include it. -* Pull requests are always sorted by last updated first (i.e., `sort:updated`). * Archived pull requests are excluded by default (i.e., `archived:false`), unless the search query is explicitely searching for archived pull requests. * Only pull requests from organizations configured in the connection are returned. * You may provide several individual search queries separated by a semi-colon (";"). diff --git a/packages/github/package.json b/packages/github/package.json index 9a98328..c6eb216 100644 --- a/packages/github/package.json +++ b/packages/github/package.json @@ -30,8 +30,8 @@ "vitest": "^2.0.5" }, "dependencies": { - "@octokit/core": "^6.1.2", - "@octokit/rest": "^20.1.1", - "@repo/model": "workspace:*" + "@octokit/plugin-throttling": "^9.3.1", + "@repo/model": "workspace:*", + "octokit": "^4.0.2" } } diff --git a/packages/github/src/attention.ts b/packages/github/src/attention.ts index 6588f8b..9cf2888 100644 --- a/packages/github/src/attention.ts +++ b/packages/github/src/attention.ts @@ -1,7 +1,7 @@ -import { type Attention, type PullProps, type Comment, type Connection, PullState, CheckState } from "@repo/model"; -import { GitHubClient } from "./client.js"; +import type { Attention, PullProps, Connection } from "@repo/model"; +import { PullState, CheckState } from "@repo/model"; -export async function isInAttentionSet(client: GitHubClient, connection: Connection, pull: PullProps): Promise { +export function isInAttentionSet(connection: Connection, pull: PullProps): Attention { if (pull.state === PullState.Draft || pull.state === PullState.Merged || pull.state === PullState.Closed) { // Draft, merged and closed pull requests have no attention set. return { set: false }; @@ -12,7 +12,7 @@ export async function isInAttentionSet(client: GitHubClient, connection: Connect const isAuthor = pull.author.name === viewerName; const isApproved = pull.state === PullState.Approved; - const isReviewer = pull.reviewers.some(r => r.name === viewerName); + const isReviewer = pull.reviews.some(r => r.author?.name === viewerName); const isRequestedReviewer = pull.requestedReviewers.some(r => r.name === viewerName) || pull.requestedTeams.some(r => viewerTeams.has(r.name)); @@ -21,33 +21,34 @@ export async function isInAttentionSet(client: GitHubClient, connection: Connect return { set: false }; } - const comments = await client.getComments(connection, pull.repo, pull.number); - const threads = groupByThread(comments); - - const reviewerNames = new Set(pull.reviewers.map(r => r.name)); + const reviewerNames = new Set(pull.reviews.map(r => r.author?.name)); const commenterNames = new Set(); - for (const thread of threads) { - if (thread.every(c => c.author.name === pull.author.name)) { + for (const discussion of pull.discussions) { + if (discussion.comments.every(c => c.author?.name === pull.author.name)) { // Threads containing only comments from the author are ignored. They are usually // part of the initial remarks left by the author. If nobody replied, we consider // they do not refer to any action to take. continue; } - const lastViewerCommentPos = thread.findLastIndex(c => c.author.name === viewerName); + if (discussion.resolved) { + // Resolved discussions are ignored. + continue; + } + const lastViewerCommentPos = discussion.comments.findLastIndex(c => c.author?.name === viewerName); const commentsAfterLastViewerComment = (lastViewerCommentPos === -1) - ? comments - : comments.slice(lastViewerCommentPos + 1); + ? discussion.comments + : discussion.comments.slice(lastViewerCommentPos + 1); if (lastViewerCommentPos > -1 && commentsAfterLastViewerComment.length > 0) { // The author and reviewers are always notified when somebody replied to them. commentsAfterLastViewerComment - .filter(c => c.author.name !== viewerName) - .forEach(c => commenterNames.add(c.author.name)); + .filter(c => c.author?.name !== viewerName) + .forEach(c => commenterNames.add(c.author?.name ?? "Anonymous")); } else if (isAuthor) { // The author (and only them) is notified when a reviewer left a comment in any thread, // even if the author did not participate in this thread. commentsAfterLastViewerComment - .filter(c => reviewerNames.has(c.author.name)) - .forEach(c => commenterNames.add(c.author.name)); + .filter(c => reviewerNames.has(c.author?.name)) + .forEach(c => commenterNames.add(c.author?.name ?? "Anonymous")); } } if (commenterNames.size > 0) { @@ -73,26 +74,4 @@ export async function isInAttentionSet(client: GitHubClient, connection: Connect } else { return { set: false }; } -} - -/** - * Group all review comments of a pull request into threads. - * - * @param comments Review comments of a pull request. - * @returns Review comments, grouped by thread (in the same order). - */ -function groupByThread(comments: Comment[]): Comment[][] { - // `inReplyTo` contains the identifier of the first comment of a thread. - // The first message in a thread does not have an `inReplyTo`. - const threads: Record = {}; - for (const comment of comments) { - if (comment.inReplyTo === undefined) { - threads[comment.uid] = [comment]; - } else if (comment.inReplyTo in threads) { - threads[comment.inReplyTo].push(comment); - } else { - threads[comment.inReplyTo] = [comment]; - } - } - return Object.values(threads); } \ No newline at end of file diff --git a/packages/github/src/client.ts b/packages/github/src/client.ts index 6c05277..1540756 100644 --- a/packages/github/src/client.ts +++ b/packages/github/src/client.ts @@ -1,71 +1,87 @@ -import { Octokit } from "@octokit/rest"; -import { type Connection, type Comment, PullState, type PullProps, type Team, type User, type Profile, CheckState } from "@repo/model"; +import { Octokit } from "octokit"; +import { throttling } from "@octokit/plugin-throttling"; +import { type Connection, type Comment, PullState, type PullProps, type Team, type User, type Profile, CheckState, type PullResult, type Review, Discussion } from "@repo/model"; import { SearchQuery } from "./search.js"; const MAX_PULLS_TO_FETCH = 50; -type GHPull = { - id: string, - number: number, - title: string, - state: string, - author: GHUser, - createdAt: string, - updatedAt: string, - url: string, - additions: number, - deletions: number, - totalCommentsCount: number, - repository: { - nameWithOwner: string, - }, - isDraft: boolean, - merged: boolean, - closed: boolean, - reviewDecision?: string, - reviewRequests: { - nodes: GHReviewRequest[], - } - latestOpinionatedReviews: { - nodes: GHReview[], - } - statusCheckRollup: { - state: "ERROR" | "EXPECTED" | "FAILURE" | "PENDING" | "SUCCESS" - }|null - mergeable: string - headRefName: string - headRefOid: string - baseRefName: string - baseRefOid: string -} +const MyOctokit = Octokit.plugin(throttling); -type GHUserReviewRequest = GHUser & { - __typename: "Bot" | "Mannequin" | "User" +export interface GitHubClient { + getViewer(connection: Connection): Promise; + searchPulls(connection: Connection, search: string): Promise; + getPull(connection: Connection, id: string): Promise; } -type GHTeamReviewRequest = GHTeam & { - __typename: "Team" +type GHPull = { + number: number + title: string + repository: { + nameWithOwner: string + } + author: GHUser + createdAt: string + updatedAt: string + url: string + additions: number + deletions: number + isDraft: boolean, + merged: boolean + closed: boolean + reviewDecision: "CHANGES_REQUESTED"|"APPROVED"|"REVIEW_REQUIRED"|null, + statusCheckRollup: { state: "EXPECTED"|"ERROR"|"FAILURE"|"PENDING"|"SUCCESS" }|null, + comments: { + totalCount: number + nodes: GHComment[] + } + reviewRequests: { + totalCount: number + nodes: GHReviewRequest[], + } + reviews: { + totalCount: number + nodes: GHReview[] + } + reviewThreads: { + totalCount: number + nodes: { + isResolved: boolean + path: string + comments: { + totalCount: number + nodes: GHComment[] + }, + }[], + } } -type GHReviewRequest = GHUserReviewRequest | GHTeamReviewRequest +type GHReviewRequest = { + requestedReviewer: ({ __typename: "Bot"|"Mannequin"|"User" } & GHUser) | ({ __typename: "Team" } & GHTeam) +} type GHReview = { - author: GHUser + id: string + author: GHUser|null + state: "PENDING"|"COMMENTED"|"APPROVED"|"CHANGES_REQUESTED"|"DISMISSED" + body: string + createdAt: string + authorCanPushToRepository: boolean } type GHUser = { - login: string - avatarUrl: string + login: string + avatarUrl: string } type GHTeam = { combinedSlug: string } -export interface GitHubClient { - getViewer(connection: Connection): Promise; - getPulls(connection: Connection, search: string): Promise; - getComments(connection: Connection, repo: string, pullNumber: number): Promise; +type GHComment = { + id: string + author: GHUser|null + createdAt: string + body: string } export class DefaultGitHubClient implements GitHubClient { @@ -82,98 +98,19 @@ export class DefaultGitHubClient implements GitHubClient { const teams: Team[] = teamsResponse.map(obj => ({ name: `${obj.organization.login}/${obj.slug}` })); return { user, teams }; } - async getPulls(connection: Connection, search: string): Promise { - const query = `query dashboard($search: String!) { - search(query: $search, type: ISSUE, first: ${MAX_PULLS_TO_FETCH}) { - issueCount - edges { - node { - ... on PullRequest { - id - number - title - author { - login - avatarUrl - } - statusCheckRollup { - state - } - reviewRequests(first: 100) { - nodes { - requestedReviewer { - __typename - ... on Bot { - login - avatarUrl - } - ... on Mannequin { - login - avatarUrl - } - ... on User { - login - avatarUrl - } - ... on Team { - combinedSlug - } - } - } - } - latestOpinionatedReviews(first: 100) { - nodes { - author { - login - avatarUrl - } - } - } - repository { - nameWithOwner - } - createdAt - updatedAt - state - url - isDraft - closed - merged - reviewDecision - additions - deletions - totalCommentsCount - } - } - } - } - rateLimit { - cost - } - }`; - type Data = { - search: { - issueCount: number, - edges: { node: GHPull }[], - }, - rateLimit: { - cost: number, - }, - } + + async searchPulls(connection: Connection, search: string): Promise { // Enforce searching for PRs, and filter by org as required by the connection. const q = new SearchQuery(search); q.set("type", "pr"); - q.set("sort", "updated"); if (connection.orgs.length > 0) { q.setAll("org", connection.orgs); } - if (!q.has("archived")) { // Unless the query explicitely allows PRs from archived repositories, exclude // them by default as we cannot act on them anymore. q.set("archived", "false"); } - if (q.has("org") && q.has("repo")) { // GitHub API does not seem to support having both terms with an "org" // and "repo" qualifier in a given query. In this situation, the term @@ -187,73 +124,207 @@ export class DefaultGitHubClient implements GitHubClient { } const octokit = this.getOctokit(connection); - const data = await octokit.graphql(query, {search: q.toString()}); - return data.search.edges.map(edge => edge.node).map(pull => ({ - uid: `${connection.id}:${pull.id}`, - host: connection.host, - repo: pull.repository.nameWithOwner, - number: pull.number, - title: pull.title, - state: pull.isDraft - ? PullState.Draft - : pull.merged - ? PullState.Merged - : pull.closed - ? PullState.Closed - : pull.reviewDecision == "APPROVED" - ? PullState.Approved - : PullState.Pending, - ciState: pull.statusCheckRollup?.state == "ERROR" - ? CheckState.Error - : pull.statusCheckRollup?.state == "FAILURE" - ? CheckState.Failure - : pull.statusCheckRollup?.state == "SUCCESS" - ? CheckState.Success - // Do not differentiate between "Pending" and "Expected". - : pull.statusCheckRollup?.state == "PENDING" - ? CheckState.Pending - : pull.statusCheckRollup?.state == "EXPECTED" - ? CheckState.Pending - : undefined, - createdAt: pull.createdAt, - updatedAt: pull.updatedAt, - url: pull.url, - additions: pull.additions, - deletions: pull.deletions, - comments: pull.totalCommentsCount, - author: this.makeUser(pull.author), - requestedReviewers: pull.reviewRequests.nodes - .filter(n => n.__typename != "Team") - .map(n => this.makeUser(n as GHUser)), - requestedTeams: pull.reviewRequests.nodes - .filter(n => n.__typename == "Team") - .map(n => this.makeTeam(n as GHTeam)), - reviewers: pull.latestOpinionatedReviews.nodes - .map(n => this.makeUser(n.author)), - })); + const response = await octokit.rest.search.issuesAndPullRequests({ + q: q.toString(), + sort: "updated", + per_page: MAX_PULLS_TO_FETCH, + }); + return response.data.items.map(obj => ({ id: obj.node_id, updatedAt: new Date(obj.updated_at) })); } - - async getComments(connection: Connection, repo: string, pullNumber: number): Promise { + + async getPull(connection: Connection, id: string): Promise { + const query = `query pull($id: ID!) { + node(id: $id) { + ... on PullRequest { + number + title + repository { + nameWithOwner + } + createdAt + updatedAt + state + url + isDraft + closed + merged + reviewDecision + additions + deletions + author { + login + avatarUrl + } + statusCheckRollup { + state + } + comments(first: 100) { + totalCount + nodes { + id + author { + login + avatarUrl + } + body + createdAt + } + } + reviewRequests(first: 100) { + totalCount + nodes { + requestedReviewer { + __typename + ... on Bot { + login + avatarUrl + } + ... on Mannequin { + login + avatarUrl + } + ... on User { + login + avatarUrl + } + ... on Team { + combinedSlug + } + } + } + } + reviews(first: 100) { + totalCount + nodes { + id + author { + login + avatarUrl + } + authorCanPushToRepository + state + body + createdAt + } + } + reviewThreads(first: 100) { + totalCount + nodes { + isResolved + path + comments(first: 100) { + totalCount + nodes { + id + author { + login + avatarUrl + } + createdAt + body + } + } + } + } + } + } + rateLimit { + cost + } + }`; + type Data = { + node: GHPull + rateLimit: { + cost: number + } + } const octokit = this.getOctokit(connection); - const [ repoOwner, repoName ] = repo.split("/"); - const comments = await octokit.paginate( - "GET /repos/{owner}/{repo}/pulls/{pull_number}/comments", - { owner: repoOwner, repo: repoName, pull_number: pullNumber, per_page: 100 } - ); - return comments.map(comment => ({ - uid: `${connection.id}:${comment.id}`, - inReplyTo: comment.in_reply_to_id ? `${connection.id}:${comment.in_reply_to_id}` : undefined, - author: { - name: comment.user.login, - avatarUrl: comment.user.avatar_url, - }, - createdAt: new Date(comment.created_at), - })); + const data = await octokit.graphql(query, { id }); + const reviews: Review[] = data.node.reviews.nodes + .filter(n => n.authorCanPushToRepository) + .map(n => this.makeReview(n)); + + const topLevelComments: Comment[] = []; + data.node.comments.nodes.forEach(n => topLevelComments.push(this.makeComment(n))); + data.node.reviews.nodes.map(n => topLevelComments.push(this.makeComment(n))); + topLevelComments.sort((a, b) => a.createdAt < b.createdAt ? -1 : a.createdAt > b.createdAt ? 1 : 0); + + const discussions: Discussion[] = [{ + resolved: false, + comments: topLevelComments, + }]; + data.node.reviewThreads.nodes.forEach(n => { + discussions.push({ + resolved: n.isResolved, + comments: n.comments.nodes.map(n2 => this.makeComment(n2)), + file: { path: n.path }, + }); + }); + + return { + id, + repo: data.node.repository.nameWithOwner, + number: data.node.number, + title: data.node.title, + state: data.node.isDraft + ? PullState.Draft + : data.node.merged + ? PullState.Merged + : data.node.closed + ? PullState.Closed + : data.node.reviewDecision == "APPROVED" + ? PullState.Approved + : PullState.Pending, + ciState: data.node.statusCheckRollup?.state == "ERROR" + ? CheckState.Error + : data.node.statusCheckRollup?.state == "FAILURE" + ? CheckState.Failure + : data.node.statusCheckRollup?.state == "SUCCESS" + ? CheckState.Success + // Do not differentiate between "Pending" and "Expected". + : data.node.statusCheckRollup?.state == "PENDING" + ? CheckState.Pending + : data.node.statusCheckRollup?.state == "EXPECTED" + ? CheckState.Pending + : CheckState.None, + createdAt: new Date(data.node.createdAt), + updatedAt: new Date(data.node.updatedAt), + url: data.node.url, + additions: data.node.additions, + deletions: data.node.deletions, + author: this.makeUser(data.node.author), + requestedReviewers: data.node.reviewRequests.nodes + .filter(n => n.requestedReviewer.__typename != "Team") + .map(n => this.makeUser(n.requestedReviewer as GHUser)), + requestedTeams: data.node.reviewRequests.nodes + .filter(n => n.requestedReviewer.__typename == "Team") + .map(n => this.makeTeam(n.requestedReviewer as GHTeam)), + reviews, + discussions, + }; } private getOctokit(connection: Connection): Octokit { if (!(connection.id in this.octokits)) { - this.octokits[connection.id] = new Octokit({auth: connection.auth, baseUrl: connection.baseUrl}); + this.octokits[connection.id] = new MyOctokit({ + auth: connection.auth, + baseUrl: connection.baseUrl, + throttle: { + // For now, allow retries in all situations. + onRateLimit: (retryAfter, options, octokit, retryCount) => { + octokit.log.warn( + `Request quota exhausted for request ${options.method} ${options.url}, retrying after ${retryAfter} seconds`, + ); + return true; + }, + onSecondaryRateLimit: (retryAfter, options, octokit) => { + octokit.log.warn( + `Secondary rate limit detected for request ${options.method} ${options.url}, retrying after ${retryAfter} seconds`, + ); + return true; + }, + }, + }); } return this.octokits[connection.id]; } @@ -265,11 +336,28 @@ export class DefaultGitHubClient implements GitHubClient { private makeTeam(team: GHTeam): Team { return { name: team.combinedSlug }; } + + private makeReview(review: GHReview): Review { + return { + author: (review.author !== null) ? this.makeUser(review.author) : undefined, + createdAt: new Date(review.createdAt), + lgtm: review.state === "APPROVED", + } + } + + private makeComment(comment: GHComment): Comment { + return { + id: comment.id, + author: (comment.author !== null) ? this.makeUser(comment.author) : undefined, + createdAt: new Date(comment.createdAt), + body: comment.body, + }; + } } export class TestGitHubClient implements GitHubClient { - private pulls: Record = {}; - private comments: Record = {}; + private pullsByKey: Record = {}; + private pullsBySearch: Record = {}; getViewer(connection: Connection): Promise { return Promise.resolve({ @@ -278,24 +366,26 @@ export class TestGitHubClient implements GitHubClient { }); } - getPulls(connection: Connection, search: string): Promise { - return Promise.resolve(this.pulls[`${connection.id}/${search}`] || []); + searchPulls(connection: Connection, search: string): Promise { + return Promise.resolve(this.pullsBySearch[`${connection.id}:${search}`] || []); } - setPulls(connection: Connection, search: string, pulls: PullProps[]) { - this.pulls[`${connection.id}/${search}`] = pulls; + setPullsBySearch(connection: Connection, search: string, pulls: PullProps[]) { + this.pullsBySearch[`${connection.id}:${search}`] = pulls; + pulls.forEach(pull => this.addPull(connection, pull)); } - getComments(connection: Connection, repo: string, pullNumber: number): Promise { - return Promise.resolve(this.comments[`${connection.id}/${repo}/${pullNumber}`] || []); + addPull(connection: Connection, pull: PullProps) { + this.pullsByKey[`${connection.id}:${pull.id}`] = pull; } - setComments(connection: Connection, repo: string, pullNumber: number, comments: Comment[]) { - this.comments[`${connection.id}/${repo}/${pullNumber}`] = comments; + getPull(connection: Connection, id: string): Promise { + const pull = this.pullsByKey[`${connection.id}:${id}`]; + return (pull !== undefined) ? Promise.resolve(pull) : Promise.reject(new Error("Not Found")); } clear(): void { - this.pulls = {}; - this.comments = {}; + this.pullsBySearch = {}; + this.pullsByKey = {}; } } \ No newline at end of file diff --git a/packages/github/tests/__recordings__/should-error-when-pull-does-not-exist_3266903050/recording.har b/packages/github/tests/__recordings__/should-error-when-pull-does-not-exist_3266903050/recording.har new file mode 100644 index 0000000..dd068f0 --- /dev/null +++ b/packages/github/tests/__recordings__/should-error-when-pull-does-not-exist_3266903050/recording.har @@ -0,0 +1,170 @@ +{ + "log": { + "_recordingName": "should error when pull does not exist", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "cc0646ae14889a5d068e12034aa668ad", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 2278, + "cookies": [], + "headers": [ + { + "name": "accept", + "value": "application/vnd.github.v3+json" + }, + { + "name": "authorization", + "value": "token ghp_token" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "user-agent", + "value": "octokit.js/0.0.0-development octokit-core.js/6.1.2 Node.js/22" + } + ], + "headersSize": 273, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "application/json; charset=utf-8", + "params": [], + "text": "{\"query\":\"query pull($id: ID!) {\\n node(id: $id) {\\n ... on PullRequest {\\n number\\n title\\n repository {\\n nameWithOwner\\n }\\n createdAt\\n updatedAt\\n state\\n url\\n isDraft\\n closed\\n merged\\n reviewDecision\\n additions\\n deletions\\n author {\\n login\\n avatarUrl \\n }\\n statusCheckRollup {\\n state\\n }\\n comments(first: 100) {\\n totalCount\\n nodes {\\n id\\n author {\\n login\\n avatarUrl\\n }\\n body\\n createdAt\\n }\\n }\\n reviewRequests(first: 100) {\\n totalCount\\n nodes {\\n requestedReviewer {\\n __typename\\n ... on Bot {\\n login\\n avatarUrl\\n }\\n ... on Mannequin {\\n login\\n avatarUrl\\n }\\n ... on User {\\n login\\n avatarUrl\\n }\\n ... on Team {\\n combinedSlug\\n }\\n }\\n }\\n }\\n reviews(first: 100) {\\n totalCount\\n nodes {\\n id\\n author {\\n login\\n avatarUrl\\n }\\n authorCanPushToRepository\\n state\\n body\\n createdAt\\n }\\n }\\n reviewThreads(first: 100) {\\n totalCount\\n nodes {\\n isResolved\\n path\\n comments(first: 100) {\\n totalCount\\n nodes {\\n id\\n author {\\n login\\n avatarUrl\\n }\\n createdAt\\n body\\n }\\n }\\n }\\n }\\n }\\n }\\n rateLimit {\\n cost\\n }\\n }\",\"variables\":{\"id\":\"PR_none\"}}" + }, + "queryString": [], + "url": "https://api.github.com/graphql" + }, + "response": { + "bodySize": 201, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 201, + "text": "{\"data\":{\"node\":null,\"rateLimit\":{\"cost\":1}},\"errors\":[{\"type\":\"NOT_FOUND\",\"path\":[\"node\"],\"locations\":[{\"line\":2,\"column\":7}],\"message\":\"Could not resolve to a node with the global id of 'PR_none'\"}]}" + }, + "cookies": [], + "headers": [ + { + "name": "access-control-allow-origin", + "value": "*" + }, + { + "name": "access-control-expose-headers", + "value": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset" + }, + { + "name": "content-encoding", + "value": "gzip" + }, + { + "name": "content-security-policy", + "value": "default-src 'none'" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "date", + "value": "Tue, 27 Aug 2024 14:12:26 GMT" + }, + { + "name": "referrer-policy", + "value": "origin-when-cross-origin, strict-origin-when-cross-origin" + }, + { + "name": "server", + "value": "github.com" + }, + { + "name": "strict-transport-security", + "value": "max-age=31536000; includeSubdomains; preload" + }, + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "vary", + "value": "Accept-Encoding, Accept, X-Requested-With" + }, + { + "name": "x-accepted-oauth-scopes", + "value": "repo" + }, + { + "name": "x-content-type-options", + "value": "nosniff" + }, + { + "name": "x-frame-options", + "value": "deny" + }, + { + "name": "x-github-media-type", + "value": "github.v3; format=json" + }, + { + "name": "x-github-request-id", + "value": "C5FC:2D68BC:38B277D:395A312:66CDDEC9" + }, + { + "name": "x-oauth-scopes", + "value": "read:org, repo, user" + }, + { + "name": "x-ratelimit-limit", + "value": "5000" + }, + { + "name": "x-ratelimit-remaining", + "value": "4874" + }, + { + "name": "x-ratelimit-reset", + "value": "1724768719" + }, + { + "name": "x-ratelimit-resource", + "value": "graphql" + }, + { + "name": "x-ratelimit-used", + "value": "126" + }, + { + "name": "x-xss-protection", + "value": "0" + } + ], + "headersSize": 1138, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-08-27T14:12:25.809Z", + "time": 201, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 201 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/packages/github/tests/__recordings__/should-get-pull_2195211117/recording.har b/packages/github/tests/__recordings__/should-get-pull_2195211117/recording.har new file mode 100644 index 0000000..b4430eb --- /dev/null +++ b/packages/github/tests/__recordings__/should-get-pull_2195211117/recording.har @@ -0,0 +1,170 @@ +{ + "log": { + "_recordingName": "should get pull", + "creator": { + "comment": "persister:fs", + "name": "Polly.JS", + "version": "6.0.6" + }, + "entries": [ + { + "_id": "73055b5195ca9190b7ad5f4d1e351e32", + "_order": 0, + "cache": {}, + "request": { + "bodySize": 2290, + "cookies": [], + "headers": [ + { + "name": "accept", + "value": "application/vnd.github.v3+json" + }, + { + "name": "authorization", + "value": "token ghp_token" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "user-agent", + "value": "octokit.js/0.0.0-development octokit-core.js/6.1.2 Node.js/22" + } + ], + "headersSize": 273, + "httpVersion": "HTTP/1.1", + "method": "POST", + "postData": { + "mimeType": "application/json; charset=utf-8", + "params": [], + "text": "{\"query\":\"query pull($id: ID!) {\\n node(id: $id) {\\n ... on PullRequest {\\n number\\n title\\n repository {\\n nameWithOwner\\n }\\n createdAt\\n updatedAt\\n state\\n url\\n isDraft\\n closed\\n merged\\n reviewDecision\\n additions\\n deletions\\n author {\\n login\\n avatarUrl \\n }\\n statusCheckRollup {\\n state\\n }\\n comments(first: 100) {\\n totalCount\\n nodes {\\n id\\n author {\\n login\\n avatarUrl\\n }\\n body\\n createdAt\\n }\\n }\\n reviewRequests(first: 100) {\\n totalCount\\n nodes {\\n requestedReviewer {\\n __typename\\n ... on Bot {\\n login\\n avatarUrl\\n }\\n ... on Mannequin {\\n login\\n avatarUrl\\n }\\n ... on User {\\n login\\n avatarUrl\\n }\\n ... on Team {\\n combinedSlug\\n }\\n }\\n }\\n }\\n reviews(first: 100) {\\n totalCount\\n nodes {\\n id\\n author {\\n login\\n avatarUrl\\n }\\n authorCanPushToRepository\\n state\\n body\\n createdAt\\n }\\n }\\n reviewThreads(first: 100) {\\n totalCount\\n nodes {\\n isResolved\\n path\\n comments(first: 100) {\\n totalCount\\n nodes {\\n id\\n author {\\n login\\n avatarUrl\\n }\\n createdAt\\n body\\n }\\n }\\n }\\n }\\n }\\n }\\n rateLimit {\\n cost\\n }\\n }\",\"variables\":{\"id\":\"PR_kwDOFFj3yM53-IjI\"}}" + }, + "queryString": [], + "url": "https://api.github.com/graphql" + }, + "response": { + "bodySize": 1972, + "content": { + "mimeType": "application/json; charset=utf-8", + "size": 1972, + "text": "{\"data\":{\"node\":{\"number\":2632,\"title\":\"remove deprecated (DocValues,Norms)FieldExistsQuery use\",\"repository\":{\"nameWithOwner\":\"apache/solr\"},\"createdAt\":\"2024-08-09T17:18:47Z\",\"updatedAt\":\"2024-08-23T19:12:59Z\",\"state\":\"OPEN\",\"url\":\"https://github.com/apache/solr/pull/2632\",\"isDraft\":false,\"closed\":false,\"merged\":false,\"reviewDecision\":\"CHANGES_REQUESTED\",\"additions\":25,\"deletions\":29,\"author\":{\"login\":\"cpoerschke\",\"avatarUrl\":\"https://avatars.githubusercontent.com/u/6458642?u=8ccfc26ce7209695b2fcca628a59baca42cff00f&v=4\"},\"statusCheckRollup\":{\"state\":\"SUCCESS\"},\"comments\":{\"totalCount\":0,\"nodes\":[]},\"reviewRequests\":{\"totalCount\":1,\"nodes\":[{\"requestedReviewer\":{\"__typename\":\"User\",\"login\":\"HoustonPutman\",\"avatarUrl\":\"https://avatars.githubusercontent.com/u/3376422?u=323311c61ac2b04e5deac436faaa4f4309fe7beb&v=4\"}}]},\"reviews\":{\"totalCount\":1,\"nodes\":[{\"id\":\"PRR_kwDOFFj3yM6GlVVP\",\"author\":{\"login\":\"dsmiley\",\"avatarUrl\":\"https://avatars.githubusercontent.com/u/377295?u=85f6ade89e5b34f001267475e806da2a52b2755a&v=4\"},\"authorCanPushToRepository\":true,\"state\":\"CHANGES_REQUESTED\",\"body\":\"As there is now FieldExistsQuery covering a range of cases (not just even docValues & norms), this probably obsoletes complexity inside FieldType.getExistenceQuery. Can we just call that and remove getSpecializedExistenceQuery as needless in lieu of subtypes overriding getExistenceQuery?\\r\\n\\r\\nCC @HoustonPutman as you worked on this method\",\"createdAt\":\"2024-08-23T19:07:24Z\"}]},\"reviewThreads\":{\"totalCount\":1,\"nodes\":[{\"isResolved\":false,\"path\":\"solr/core/src/java/org/apache/solr/search/facet/MissingAgg.java\",\"comments\":{\"totalCount\":1,\"nodes\":[{\"id\":\"PRRC_kwDOFFj3yM5nFK8J\",\"author\":{\"login\":\"dsmiley\",\"avatarUrl\":\"https://avatars.githubusercontent.com/u/377295?u=85f6ade89e5b34f001267475e806da2a52b2755a&v=4\"},\"createdAt\":\"2024-08-23T19:07:24Z\",\"body\":\"I suspect this code pre-dated FieldType.getExistenceQuery -- just call that.\"}]}}]}},\"rateLimit\":{\"cost\":1}}}" + }, + "cookies": [], + "headers": [ + { + "name": "access-control-allow-origin", + "value": "*" + }, + { + "name": "access-control-expose-headers", + "value": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset" + }, + { + "name": "content-encoding", + "value": "gzip" + }, + { + "name": "content-security-policy", + "value": "default-src 'none'" + }, + { + "name": "content-type", + "value": "application/json; charset=utf-8" + }, + { + "name": "date", + "value": "Tue, 27 Aug 2024 14:12:25 GMT" + }, + { + "name": "referrer-policy", + "value": "origin-when-cross-origin, strict-origin-when-cross-origin" + }, + { + "name": "server", + "value": "github.com" + }, + { + "name": "strict-transport-security", + "value": "max-age=31536000; includeSubdomains; preload" + }, + { + "name": "transfer-encoding", + "value": "chunked" + }, + { + "name": "vary", + "value": "Accept-Encoding, Accept, X-Requested-With" + }, + { + "name": "x-accepted-oauth-scopes", + "value": "repo" + }, + { + "name": "x-content-type-options", + "value": "nosniff" + }, + { + "name": "x-frame-options", + "value": "deny" + }, + { + "name": "x-github-media-type", + "value": "github.v3; format=json" + }, + { + "name": "x-github-request-id", + "value": "C5FC:2D68BC:38B2313:3959E78:66CDDEC8" + }, + { + "name": "x-oauth-scopes", + "value": "read:org, repo, user" + }, + { + "name": "x-ratelimit-limit", + "value": "5000" + }, + { + "name": "x-ratelimit-remaining", + "value": "4875" + }, + { + "name": "x-ratelimit-reset", + "value": "1724768719" + }, + { + "name": "x-ratelimit-resource", + "value": "graphql" + }, + { + "name": "x-ratelimit-used", + "value": "125" + }, + { + "name": "x-xss-protection", + "value": "0" + } + ], + "headersSize": 1138, + "httpVersion": "HTTP/1.1", + "redirectURL": "", + "status": 200, + "statusText": "OK" + }, + "startedDateTime": "2024-08-27T14:12:24.809Z", + "time": 362, + "timings": { + "blocked": -1, + "connect": -1, + "dns": -1, + "receive": 0, + "send": 0, + "ssl": -1, + "wait": 362 + } + } + ], + "pages": [], + "version": "1.2" + } +} diff --git a/packages/github/tests/__recordings__/should-return-viewer_972235810/recording.har b/packages/github/tests/__recordings__/should-return-viewer_972235810/recording.har index 040e97f..392d0df 100644 --- a/packages/github/tests/__recordings__/should-return-viewer_972235810/recording.har +++ b/packages/github/tests/__recordings__/should-return-viewer_972235810/recording.har @@ -8,7 +8,7 @@ }, "entries": [ { - "_id": "54671ab74f2172d274a21fca2cddffb2", + "_id": "ec09cf349d430c0c088d60312d2a4893", "_order": 0, "cache": {}, "request": { @@ -25,10 +25,10 @@ }, { "name": "user-agent", - "value": "octokit-rest.js/20.1.1 octokit-core.js/5.2.0 Node.js/22" + "value": "octokit.js/0.0.0-development octokit-core.js/6.1.2 Node.js/22" } ], - "headersSize": 216, + "headersSize": 222, "httpVersion": "HTTP/1.1", "method": "GET", "queryString": [], @@ -39,7 +39,7 @@ "content": { "mimeType": "application/json; charset=utf-8", "size": 1431, - "text": "{\"login\":\"pvcnt\",\"id\":944506,\"node_id\":\"MDQ6VXNlcjk0NDUwNg==\",\"avatar_url\":\"https://avatars.githubusercontent.com/u/944506?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/pvcnt\",\"html_url\":\"https://github.com/pvcnt\",\"followers_url\":\"https://api.github.com/users/pvcnt/followers\",\"following_url\":\"https://api.github.com/users/pvcnt/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/pvcnt/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/pvcnt/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/pvcnt/subscriptions\",\"organizations_url\":\"https://api.github.com/users/pvcnt/orgs\",\"repos_url\":\"https://api.github.com/users/pvcnt/repos\",\"events_url\":\"https://api.github.com/users/pvcnt/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/pvcnt/received_events\",\"type\":\"User\",\"site_admin\":false,\"name\":\"Vincent Primault\",\"company\":\"Salesforce\",\"blog\":\"\",\"location\":\"Grenoble, France\",\"email\":null,\"hireable\":null,\"bio\":\"Software Engineer at Salesforce\",\"twitter_username\":null,\"notification_email\":null,\"public_repos\":13,\"public_gists\":1,\"followers\":11,\"following\":1,\"created_at\":\"2011-07-28T13:51:46Z\",\"updated_at\":\"2024-07-03T07:53:42Z\",\"private_gists\":0,\"total_private_repos\":15,\"owned_private_repos\":15,\"disk_usage\":230456,\"collaborators\":6,\"two_factor_authentication\":true,\"plan\":{\"name\":\"free\",\"space\":976562499,\"collaborators\":0,\"private_repos\":10000}}" + "text": "{\"login\":\"pvcnt\",\"id\":944506,\"node_id\":\"MDQ6VXNlcjk0NDUwNg==\",\"avatar_url\":\"https://avatars.githubusercontent.com/u/944506?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/pvcnt\",\"html_url\":\"https://github.com/pvcnt\",\"followers_url\":\"https://api.github.com/users/pvcnt/followers\",\"following_url\":\"https://api.github.com/users/pvcnt/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/pvcnt/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/pvcnt/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/pvcnt/subscriptions\",\"organizations_url\":\"https://api.github.com/users/pvcnt/orgs\",\"repos_url\":\"https://api.github.com/users/pvcnt/repos\",\"events_url\":\"https://api.github.com/users/pvcnt/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/pvcnt/received_events\",\"type\":\"User\",\"site_admin\":false,\"name\":\"Vincent Primault\",\"company\":\"Salesforce\",\"blog\":\"\",\"location\":\"Grenoble, France\",\"email\":null,\"hireable\":null,\"bio\":\"Software Engineer at Salesforce\",\"twitter_username\":null,\"notification_email\":null,\"public_repos\":13,\"public_gists\":1,\"followers\":11,\"following\":1,\"created_at\":\"2011-07-28T13:51:46Z\",\"updated_at\":\"2024-07-03T07:53:42Z\",\"private_gists\":0,\"total_private_repos\":15,\"owned_private_repos\":15,\"disk_usage\":230480,\"collaborators\":6,\"two_factor_authentication\":true,\"plan\":{\"name\":\"free\",\"space\":976562499,\"collaborators\":0,\"private_repos\":10000}}" }, "cookies": [], "headers": [ @@ -69,11 +69,11 @@ }, { "name": "date", - "value": "Mon, 26 Aug 2024 14:25:28 GMT" + "value": "Tue, 27 Aug 2024 14:12:24 GMT" }, { "name": "etag", - "value": "W/\"d65c98c34955e4485a19f3abecc6fea5d961acfa5a8bec7349a6e07a736f9400\"" + "value": "W/\"1e419b430c7d3aea50f5e6ed4f49d2768b939fd0a3f7237967e9d767b1e68b9f\"" }, { "name": "last-modified", @@ -121,7 +121,7 @@ }, { "name": "x-github-request-id", - "value": "F7F7:33AF21:10B2AC9:10DC13C:66CC9057" + "value": "C5FC:2D68BC:38B1F4E:3959AE9:66CDDEC8" }, { "name": "x-oauth-scopes", @@ -133,11 +133,11 @@ }, { "name": "x-ratelimit-remaining", - "value": "4980" + "value": "4963" }, { "name": "x-ratelimit-reset", - "value": "1724685197" + "value": "1724770037" }, { "name": "x-ratelimit-resource", @@ -145,7 +145,7 @@ }, { "name": "x-ratelimit-used", - "value": "20" + "value": "37" }, { "name": "x-xss-protection", @@ -158,8 +158,8 @@ "status": 200, "statusText": "OK" }, - "startedDateTime": "2024-08-26T14:25:27.885Z", - "time": 271, + "startedDateTime": "2024-08-27T14:12:23.949Z", + "time": 257, "timings": { "blocked": -1, "connect": -1, @@ -167,11 +167,11 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 271 + "wait": 257 } }, { - "_id": "8afdeae1a960456811207a71942c7040", + "_id": "3cbef758dd5ea02ae5d45421ceff6064", "_order": 0, "cache": {}, "request": { @@ -188,10 +188,10 @@ }, { "name": "user-agent", - "value": "octokit-rest.js/20.1.1 octokit-core.js/5.2.0 Node.js/22" + "value": "octokit.js/0.0.0-development octokit-core.js/6.1.2 Node.js/22" } ], - "headersSize": 235, + "headersSize": 241, "httpVersion": "HTTP/1.1", "method": "GET", "queryString": [ @@ -237,7 +237,7 @@ }, { "name": "date", - "value": "Mon, 26 Aug 2024 14:25:28 GMT" + "value": "Tue, 27 Aug 2024 14:12:24 GMT" }, { "name": "etag", @@ -285,7 +285,7 @@ }, { "name": "x-github-request-id", - "value": "F7F7:33AF21:10B2BC1:10DC242:66CC9058" + "value": "C5FC:2D68BC:38B2041:3959BD6:66CDDEC8" }, { "name": "x-oauth-scopes", @@ -297,11 +297,11 @@ }, { "name": "x-ratelimit-remaining", - "value": "4979" + "value": "4962" }, { "name": "x-ratelimit-reset", - "value": "1724685197" + "value": "1724770037" }, { "name": "x-ratelimit-resource", @@ -309,7 +309,7 @@ }, { "name": "x-ratelimit-used", - "value": "21" + "value": "38" }, { "name": "x-xss-protection", @@ -322,8 +322,8 @@ "status": 200, "statusText": "OK" }, - "startedDateTime": "2024-08-26T14:25:28.160Z", - "time": 184, + "startedDateTime": "2024-08-27T14:12:24.226Z", + "time": 238, "timings": { "blocked": -1, "connect": -1, @@ -331,7 +331,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 184 + "wait": 238 } } ], diff --git a/packages/github/tests/__recordings__/should-search-pulls_3772538870/recording.har b/packages/github/tests/__recordings__/should-search-pulls_3772538870/recording.har index 1e15b51..66a0775 100644 --- a/packages/github/tests/__recordings__/should-search-pulls_3772538870/recording.har +++ b/packages/github/tests/__recordings__/should-search-pulls_3772538870/recording.har @@ -8,11 +8,11 @@ }, "entries": [ { - "_id": "8e34e6a346fd5f0a142ed26cd721e2ca", + "_id": "3b5142de41179440505774a0cf8ad895", "_order": 0, "cache": {}, "request": { - "bodySize": 1970, + "bodySize": 0, "cookies": [], "headers": [ { @@ -23,32 +23,36 @@ "name": "authorization", "value": "token ghp_token" }, - { - "name": "content-type", - "value": "application/json; charset=utf-8" - }, { "name": "user-agent", - "value": "octokit-rest.js/20.1.1 octokit-core.js/5.2.0 Node.js/22" + "value": "octokit.js/0.0.0-development octokit-core.js/6.1.2 Node.js/22" } ], - "headersSize": 267, + "headersSize": 312, "httpVersion": "HTTP/1.1", - "method": "POST", - "postData": { - "mimeType": "application/json; charset=utf-8", - "params": [], - "text": "{\"query\":\"query dashboard($search: String!) {\\n search(query: $search, type: ISSUE, first: 50) {\\n issueCount\\n edges {\\n node {\\n ... on PullRequest {\\n id\\n number\\n title\\n author {\\n login\\n avatarUrl \\n }\\n statusCheckRollup {\\n state\\n }\\n reviewRequests(first: 100) {\\n nodes {\\n requestedReviewer {\\n __typename\\n ... on Bot {\\n login\\n avatarUrl\\n }\\n ... on Mannequin {\\n login\\n avatarUrl\\n }\\n ... on User {\\n login\\n avatarUrl\\n }\\n ... on Team {\\n combinedSlug\\n }\\n }\\n }\\n }\\n latestOpinionatedReviews(first: 100) {\\n nodes {\\n author {\\n login\\n avatarUrl\\n }\\n }\\n }\\n repository {\\n nameWithOwner\\n }\\n createdAt\\n updatedAt\\n state\\n url\\n isDraft\\n closed\\n merged\\n reviewDecision\\n additions\\n deletions\\n totalCommentsCount\\n }\\n }\\n }\\n }\\n rateLimit {\\n cost\\n }\\n }\",\"variables\":{\"search\":\"repo:pvcnt/sandbox type:pr sort:updated archived:false\"}}" - }, - "queryString": [], - "url": "https://api.github.com/graphql" + "method": "GET", + "queryString": [ + { + "name": "q", + "value": "repo:pvcnt/sandbox type:pr archived:false" + }, + { + "name": "sort", + "value": "updated" + }, + { + "name": "per_page", + "value": "50" + } + ], + "url": "https://api.github.com/search/issues?q=repo%3Apvcnt%2Fsandbox%20type%3Apr%20archived%3Afalse&sort=updated&per_page=50" }, "response": { - "bodySize": 674, + "bodySize": 2282, "content": { "mimeType": "application/json; charset=utf-8", - "size": 674, - "text": "{\"data\":{\"search\":{\"issueCount\":1,\"edges\":[{\"node\":{\"id\":\"PR_kwDOMoYZC855afun\",\"number\":1,\"title\":\"Update README.md\",\"author\":{\"login\":\"pvcnt\",\"avatarUrl\":\"https://avatars.githubusercontent.com/u/944506?u=d5c9f112310265a0c7b3be509ecc911620eca4ed&v=4\"},\"statusCheckRollup\":null,\"reviewRequests\":{\"nodes\":[]},\"latestOpinionatedReviews\":{\"nodes\":[]},\"repository\":{\"nameWithOwner\":\"pvcnt/sandbox\"},\"createdAt\":\"2024-08-26T09:21:33Z\",\"updatedAt\":\"2024-08-26T10:05:52Z\",\"state\":\"OPEN\",\"url\":\"https://github.com/pvcnt/sandbox/pull/1\",\"isDraft\":false,\"closed\":false,\"merged\":false,\"reviewDecision\":null,\"additions\":1,\"deletions\":1,\"totalCommentsCount\":8}}]},\"rateLimit\":{\"cost\":1}}}" + "size": 2282, + "text": "{\"total_count\":1,\"incomplete_results\":false,\"items\":[{\"url\":\"https://api.github.com/repos/pvcnt/sandbox/issues/1\",\"repository_url\":\"https://api.github.com/repos/pvcnt/sandbox\",\"labels_url\":\"https://api.github.com/repos/pvcnt/sandbox/issues/1/labels{/name}\",\"comments_url\":\"https://api.github.com/repos/pvcnt/sandbox/issues/1/comments\",\"events_url\":\"https://api.github.com/repos/pvcnt/sandbox/issues/1/events\",\"html_url\":\"https://github.com/pvcnt/sandbox/pull/1\",\"id\":2486380328,\"node_id\":\"PR_kwDOMoYZC855afun\",\"number\":1,\"title\":\"Update README.md\",\"user\":{\"login\":\"pvcnt\",\"id\":944506,\"node_id\":\"MDQ6VXNlcjk0NDUwNg==\",\"avatar_url\":\"https://avatars.githubusercontent.com/u/944506?v=4\",\"gravatar_id\":\"\",\"url\":\"https://api.github.com/users/pvcnt\",\"html_url\":\"https://github.com/pvcnt\",\"followers_url\":\"https://api.github.com/users/pvcnt/followers\",\"following_url\":\"https://api.github.com/users/pvcnt/following{/other_user}\",\"gists_url\":\"https://api.github.com/users/pvcnt/gists{/gist_id}\",\"starred_url\":\"https://api.github.com/users/pvcnt/starred{/owner}{/repo}\",\"subscriptions_url\":\"https://api.github.com/users/pvcnt/subscriptions\",\"organizations_url\":\"https://api.github.com/users/pvcnt/orgs\",\"repos_url\":\"https://api.github.com/users/pvcnt/repos\",\"events_url\":\"https://api.github.com/users/pvcnt/events{/privacy}\",\"received_events_url\":\"https://api.github.com/users/pvcnt/received_events\",\"type\":\"User\",\"site_admin\":false},\"labels\":[],\"state\":\"open\",\"locked\":false,\"assignee\":null,\"assignees\":[],\"milestone\":null,\"comments\":2,\"created_at\":\"2024-08-26T09:21:33Z\",\"updated_at\":\"2024-08-27T13:45:07Z\",\"closed_at\":null,\"author_association\":\"OWNER\",\"active_lock_reason\":null,\"draft\":false,\"pull_request\":{\"url\":\"https://api.github.com/repos/pvcnt/sandbox/pulls/1\",\"html_url\":\"https://github.com/pvcnt/sandbox/pull/1\",\"diff_url\":\"https://github.com/pvcnt/sandbox/pull/1.diff\",\"patch_url\":\"https://github.com/pvcnt/sandbox/pull/1.patch\",\"merged_at\":null},\"body\":null,\"reactions\":{\"url\":\"https://api.github.com/repos/pvcnt/sandbox/issues/1/reactions\",\"total_count\":0,\"+1\":0,\"-1\":0,\"laugh\":0,\"hooray\":0,\"confused\":0,\"heart\":0,\"rocket\":0,\"eyes\":0},\"timeline_url\":\"https://api.github.com/repos/pvcnt/sandbox/issues/1/timeline\",\"performed_via_github_app\":null,\"state_reason\":null,\"score\":1.0}]}" }, "cookies": [], "headers": [ @@ -60,6 +64,10 @@ "name": "access-control-expose-headers", "value": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset" }, + { + "name": "cache-control", + "value": "no-cache" + }, { "name": "content-encoding", "value": "gzip" @@ -74,7 +82,7 @@ }, { "name": "date", - "value": "Mon, 26 Aug 2024 14:25:28 GMT" + "value": "Tue, 27 Aug 2024 14:12:24 GMT" }, { "name": "referrer-policy", @@ -94,11 +102,11 @@ }, { "name": "vary", - "value": "Accept-Encoding, Accept, X-Requested-With" + "value": "Accept, Authorization, Cookie, X-GitHub-OTP,Accept-Encoding, Accept, X-Requested-With" }, { "name": "x-accepted-oauth-scopes", - "value": "repo" + "value": "" }, { "name": "x-content-type-options", @@ -108,13 +116,17 @@ "name": "x-frame-options", "value": "deny" }, + { + "name": "x-github-api-version-selected", + "value": "2022-11-28" + }, { "name": "x-github-media-type", "value": "github.v3; format=json" }, { "name": "x-github-request-id", - "value": "F7F7:33AF21:10B2CA2:10DC317:66CC9058" + "value": "C5FC:2D68BC:38B2197:3959D0B:66CDDEC8" }, { "name": "x-oauth-scopes", @@ -122,37 +134,37 @@ }, { "name": "x-ratelimit-limit", - "value": "5000" + "value": "30" }, { "name": "x-ratelimit-remaining", - "value": "4961" + "value": "29" }, { "name": "x-ratelimit-reset", - "value": "1724684717" + "value": "1724768004" }, { "name": "x-ratelimit-resource", - "value": "graphql" + "value": "search" }, { "name": "x-ratelimit-used", - "value": "39" + "value": "1" }, { "name": "x-xss-protection", "value": "0" } ], - "headersSize": 1137, + "headersSize": 1239, "httpVersion": "HTTP/1.1", "redirectURL": "", "status": 200, "statusText": "OK" }, - "startedDateTime": "2024-08-26T14:25:28.354Z", - "time": 446, + "startedDateTime": "2024-08-27T14:12:24.489Z", + "time": 293, "timings": { "blocked": -1, "connect": -1, @@ -160,7 +172,7 @@ "receive": 0, "send": 0, "ssl": -1, - "wait": 446 + "wait": 293 } } ], diff --git a/packages/github/tests/attention.test.ts b/packages/github/tests/attention.test.ts index 547ea8c..3908175 100644 --- a/packages/github/tests/attention.test.ts +++ b/packages/github/tests/attention.test.ts @@ -1,144 +1,191 @@ -import { test, expect, beforeEach } from "vitest"; +import { test, expect } from "vitest"; import { isInAttentionSet } from "../src/attention.js"; -import { TestGitHubClient } from "../src/client.js"; import { mockConnection, mockPull } from "@repo/testing"; import { CheckState, PullState } from "@repo/model"; -const client = new TestGitHubClient(); const me = { name: "test", avatarUrl: "" }; const user1 = { name: "test1", avatarUrl: "" }; const user2 = { name: "test2", avatarUrl: "" }; const user3 = { name: "test3", avatarUrl: "" }; const connection = mockConnection({ viewer: { user: me, teams: []}}); -beforeEach(() => { - client.clear(); -}) - -test("should contain the author when pull is approved", async () => { +test("should contain the author when pull is approved", () => { const pull = mockPull({ state: PullState.Approved, author: me }); - const attention = await isInAttentionSet(client, connection, pull); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: true, reason: "Pull request is approved" }); }) -test("should contain only the author when pull is approved", async () => { +test("should contain only the author when pull is approved", () => { const pull = mockPull({ state: PullState.Approved }); - const attention = await isInAttentionSet(client, connection, pull); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: false }); }) -test("should contain the author when CI is failing", async () => { +test("should contain the author when CI is failing", () => { const pull = mockPull({ author: me, ciState: CheckState.Failure }); - const attention = await isInAttentionSet(client, connection, pull); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: true, reason: "CI is failing" }); }) -test("should contain only the author when CI is failing", async () => { +test("should contain only the author when CI is failing", () => { const pull = mockPull({ ciState: CheckState.Failure }); - const attention = await isInAttentionSet(client, connection, pull); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: false }); }) -test("should contain a requested reviewer when pull is not approved", async () => { +test("should contain a requested reviewer when pull is not approved", () => { const pull = mockPull({ state: PullState.Pending, requestedReviewers: [me] }); - const attention = await isInAttentionSet(client, connection, pull); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: true, reason: "Review is requested" }); }) -test("should be empty when pull is draft", async () => { +test("should be empty when pull is draft", () => { const pull = mockPull({ state: PullState.Draft, author: me }); - const attention = await isInAttentionSet(client, connection, pull); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: false }); }) -test("should be empty when pull is merged", async () => { +test("should be empty when pull is merged", () => { const pull = mockPull({ state: PullState.Merged, author: me }); - const attention = await isInAttentionSet(client, connection, pull); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: false }); }) -test("should be empty when pull is closed", async () => { +test("should be empty when pull is closed", () => { const pull = mockPull({ state: PullState.Closed, author: me }); - const attention = await isInAttentionSet(client, connection, pull); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: false }); }) -test("should contain the author when a user replied", async () => { - const pull = mockPull({ state: PullState.Pending, author: me }); - const comments = [ - { uid: "1", author: me, createdAt: new Date(0) }, - { uid: "2", inReplyTo: "1", author: user1, createdAt: new Date(1) }, - ]; - client.setComments(connection, pull.repo, pull.number, comments); - const attention = await isInAttentionSet(client, connection, pull); +test("should contain the author when a user replied", () => { + const pull = mockPull({ + state: PullState.Pending, + author: me, + discussions: [ + { + resolved: false, + comments: [ + { id: "1", author: me, createdAt: new Date(0), body: "" }, + { id: "2", author: user1, createdAt: new Date(1), body: "" }, + ], + }, + ], + }); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: true, reason: "test1 left a comment" }); }) -test("should contain the author when two users replied", async () => { - const pull = mockPull({ state: PullState.Pending, author: me }); - const comments = [ - { uid: "1", author: me, createdAt: new Date(0) }, - { uid: "2", inReplyTo: "1", author: user1, createdAt: new Date(1) }, - { uid: "3", inReplyTo: "1", author: user2, createdAt: new Date(2) }, - ]; - client.setComments(connection, pull.repo, pull.number, comments); - const attention = await isInAttentionSet(client, connection, pull); +test("should contain the author when two users replied", () => { + const pull = mockPull({ + state: PullState.Pending, + author: me, + discussions: [ + { + resolved: false, + comments: [ + { id: "1", author: me, createdAt: new Date(0), body: "" }, + { id: "2", author: user1, createdAt: new Date(1), body: "" }, + { id: "3", author: user2, createdAt: new Date(2), body: "" }, + ], + } + ] + }); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: true, reason: "test1 and 1 other left a comment" }); }) -test("should contain the author when three users replied", async () => { - const pull = mockPull({ state: PullState.Pending, author: me }); - const comments = [ - { uid: "1", author: me, createdAt: new Date(0) }, - { uid: "2", inReplyTo: "1", author: user1, createdAt: new Date(1) }, - { uid: "3", inReplyTo: "1", author: user2, createdAt: new Date(2) }, - { uid: "4", inReplyTo: "1", author: user1, createdAt: new Date(3) }, - { uid: "5", inReplyTo: "1", author: user3, createdAt: new Date(4) }, - ]; - client.setComments(connection, pull.repo, pull.number, comments); - const attention = await isInAttentionSet(client, connection, pull); +test("should contain the author when three users replied", () => { + const pull = mockPull({ + state: PullState.Pending, + author: me, + discussions: [ + { + resolved: false, + comments: [ + { id: "1", author: me, createdAt: new Date(0), body: "" }, + { id: "2", author: user1, createdAt: new Date(1), body: "" }, + { id: "3", author: user2, createdAt: new Date(2), body: "" }, + { id: "4", author: user1, createdAt: new Date(3), body: "" }, + { id: "5", author: user3, createdAt: new Date(4), body: "" }, + ], + } + ] + }); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: true, reason: "test1 and 2 others left a comment" }); }) -test("should contain a reviewer when a user replied", async () => { - const pull = mockPull({ state: PullState.Pending, author: user1, reviewers: [me] }); - const comments = [ - { uid: "1", author: me, createdAt: new Date(0) }, - { uid: "2", inReplyTo: "1", author: user2, createdAt: new Date(1) }, - ]; - client.setComments(connection, pull.repo, pull.number, comments); - const attention = await isInAttentionSet(client, connection, pull); +test("should contain a reviewer when a user replied", () => { + const pull = mockPull({ + state: PullState.Pending, + author: user1, + reviews: [ + { author: me, createdAt: new Date(0), lgtm: false }, + ], + discussions: [ + { + resolved: false, + comments: [ + { id: "1", author: me, createdAt: new Date(0), body: "" }, + { id: "2", author: user2, createdAt: new Date(1), body: "" }, + ], + }, + ], + }); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: true, reason: "test2 left a comment" }); }) -test("should not contain the author when nobody replied", async () => { - const pull = mockPull({ state: PullState.Pending, author: me }); - const comments = [ - { uid: "1", author: me, createdAt: new Date(0) }, - { uid: "2", inReplyTo: "1", author: user1, createdAt: new Date(1) }, - { uid: "3", inReplyTo: "1", author: me, createdAt: new Date(3) }, - ]; - client.setComments(connection, pull.repo, pull.number, comments); - const attention = await isInAttentionSet(client, connection, pull); +test("should not contain the author when nobody replied", () => { + const pull = mockPull({ + state: PullState.Pending, + author: me, + discussions: [ + { + resolved: false, + comments: [ + { id: "1", author: me, createdAt: new Date(0), body: "" }, + { id: "2", author: user1, createdAt: new Date(1), body: "" }, + { id: "3", author: me, createdAt: new Date(2), body: "" }, + ], + } + ] + }); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: false }); }) -test("should contain the author when a reviewer left a comment", async () => { - const pull = mockPull({ state: PullState.Pending, author: me, reviewers: [user1] }); - const comments = [ - { uid: "1", author: user1, createdAt: new Date(0) }, - ]; - client.setComments(connection, pull.repo, pull.number, comments); - const attention = await isInAttentionSet(client, connection, pull); +test("should contain the author when a reviewer left a comment", () => { + const pull = mockPull({ + state: PullState.Pending, + author: me, + reviews: [{ author: user1, createdAt: new Date(0), lgtm: false }], + discussions: [ + { + resolved: false, + comments: [ + { id: "1", author: user1, createdAt: new Date(0), body: "" }, + ], + } + ] + }); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: true, reason: "test1 left a comment" }); }) -test("should not contain the author when a non-reviewer left a comment", async () => { - const pull = mockPull({ state: PullState.Pending, author: me }); - const comments = [ - { uid: "1", author: user1, createdAt: new Date(0) }, - ]; - client.setComments(connection, pull.repo, pull.number, comments); - const attention = await isInAttentionSet(client, connection, pull); +test("should not contain the author when a non-reviewer left a comment", () => { + const pull = mockPull({ + state: PullState.Pending, + author: me, + discussions: [ + { + resolved: false, + comments: [ + { id: "1", author: user1, createdAt: new Date(0), body: "" }, + ], + } + ] + }); + const attention = isInAttentionSet(connection, pull); expect(attention).toEqual({ set: false }); }) \ No newline at end of file diff --git a/packages/github/tests/client.test.ts b/packages/github/tests/client.test.ts index ff3fcde..68732b7 100644 --- a/packages/github/tests/client.test.ts +++ b/packages/github/tests/client.test.ts @@ -2,6 +2,7 @@ import { test, expect, } from "vitest"; import { DefaultGitHubClient } from "../src/client.js"; import { mockConnection } from "@repo/testing"; import { useRecording } from "./polly.js"; +import { CheckState, PullState } from "@repo/model"; useRecording(); @@ -27,30 +28,92 @@ test("should search pulls", async () => { const client = new DefaultGitHubClient(); const connection = mockConnection({ auth: "ghp_token" }); - const pulls = await client.getPulls(connection, "repo:pvcnt/sandbox"); + const pulls = await client.searchPulls(connection, "repo:pvcnt/sandbox"); expect(pulls).toEqual([ { - uid: ":PR_kwDOMoYZC855afun", - host: "github.com", - repo: "pvcnt/sandbox", - number: 1, - title: "Update README.md", - state: 1, - ciState: undefined, - createdAt: "2024-08-26T09:21:33Z", - updatedAt: "2024-08-26T10:05:52Z", - url: "https://github.com/pvcnt/sandbox/pull/1", - additions: 1, - deletions: 1, - comments: 8, - author: { - name: "pvcnt", - avatarUrl: "https://avatars.githubusercontent.com/u/944506?u=d5c9f112310265a0c7b3be509ecc911620eca4ed&v=4" - }, - requestedReviewers: [], - requestedTeams: [], - reviewers: [] + id: "PR_kwDOMoYZC855afun", + updatedAt: new Date("2024-08-27T13:45:07.000Z"), } ]); +}) + +test("should get pull", async () => { + const client = new DefaultGitHubClient(); + const connection = mockConnection({ auth: "ghp_token" }); + + const pull = await client.getPull(connection, "PR_kwDOFFj3yM53-IjI"); + + expect(pull).toEqual({ + id: "PR_kwDOFFj3yM53-IjI", + repo: "apache/solr", + number: 2632, + title: "remove deprecated (DocValues,Norms)FieldExistsQuery use", + state: PullState.Pending, + ciState: CheckState.Success, + createdAt: new Date("2024-08-09T17:18:47.000Z"), + updatedAt: new Date("2024-08-23T19:12:59.000Z"), + url: "https://github.com/apache/solr/pull/2632", + additions: 25, + deletions: 29, + author: { + name: "cpoerschke", + avatarUrl: "https://avatars.githubusercontent.com/u/6458642?u=8ccfc26ce7209695b2fcca628a59baca42cff00f&v=4", + }, + requestedReviewers: [ + { + name: "HoustonPutman", + avatarUrl: "https://avatars.githubusercontent.com/u/3376422?u=323311c61ac2b04e5deac436faaa4f4309fe7beb&v=4", + }, + ], + requestedTeams: [], + reviews: [ + { + author: { + name: "dsmiley", + avatarUrl: "https://avatars.githubusercontent.com/u/377295?u=85f6ade89e5b34f001267475e806da2a52b2755a&v=4", + }, + createdAt: new Date("2024-08-23T19:07:24.000Z"), + lgtm: false, + } + ], + discussions: [ + { + resolved: false, + comments: [ + { + id: "PRR_kwDOFFj3yM6GlVVP", + author: { + name: "dsmiley", + avatarUrl: "https://avatars.githubusercontent.com/u/377295?u=85f6ade89e5b34f001267475e806da2a52b2755a&v=4", + }, + body: "As there is now FieldExistsQuery covering a range of cases (not just even docValues & norms), this probably obsoletes complexity inside FieldType.getExistenceQuery. Can we just call that and remove getSpecializedExistenceQuery as needless in lieu of subtypes overriding getExistenceQuery?\r\n\r\nCC @HoustonPutman as you worked on this method", + createdAt: new Date("2024-08-23T19:07:24.000Z"), + } + ], + }, + { + resolved: false, + comments: [ + { + id: "PRRC_kwDOFFj3yM5nFK8J", + author: { + avatarUrl: "https://avatars.githubusercontent.com/u/377295?u=85f6ade89e5b34f001267475e806da2a52b2755a&v=4", + name: "dsmiley", + }, + body: "I suspect this code pre-dated FieldType.getExistenceQuery -- just call that.", + createdAt: new Date("2024-08-23T19:07:24.000Z"), + } + ], + file: { path: "solr/core/src/java/org/apache/solr/search/facet/MissingAgg.java" }, + } + ] + }); +}) + +test("should error when pull does not exist", async () => { + const client = new DefaultGitHubClient(); + const connection = mockConnection({ auth: "ghp_token" }); + + await expect(client.getPull(connection, "PR_none")).rejects.toThrowError(); }) \ No newline at end of file diff --git a/packages/model/src/comment.ts b/packages/model/src/comment.ts deleted file mode 100644 index 5588def..0000000 --- a/packages/model/src/comment.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { User } from "./user.js" - -export type Comment = { - uid: string - inReplyTo?: string - author: User - createdAt: Date -} \ No newline at end of file diff --git a/packages/model/src/index.ts b/packages/model/src/index.ts index cd5f5a9..956ba70 100644 --- a/packages/model/src/index.ts +++ b/packages/model/src/index.ts @@ -1,4 +1,3 @@ -export * from "./comment.js"; export * from "./config.js"; export * from "./pull.js"; export * from "./user.js"; \ No newline at end of file diff --git a/packages/model/src/pull.ts b/packages/model/src/pull.ts index 24aaa9a..1f1dfc6 100644 --- a/packages/model/src/pull.ts +++ b/packages/model/src/pull.ts @@ -1,4 +1,9 @@ -import type { Team, User } from "./user.js" +import type { Team, User } from "./user.js"; + +export type PullResult = { + id: string + updatedAt: Date +} export enum PullState { Draft, @@ -9,30 +14,51 @@ export enum PullState { } export enum CheckState { + None, Pending, Error, Failure, Success, } +export type Review = { + author?: User + createdAt: Date + lgtm: boolean +} + +export type Comment = { + id: string + author?: User + body: string + createdAt: Date +} + +export type Discussion = { + resolved: boolean + comments: Comment[] + file?: { + path: string + } +} + export type PullProps = { - uid: string, - host: string, - repo: string, - number: number, - title: string, - state: PullState, - ciState?: CheckState, - createdAt: string, - updatedAt: string, - url: string, - additions: number, - deletions: number, - comments: number, - author: User, - requestedReviewers: User[], - requestedTeams: Team[], - reviewers: User[], + id: string + repo: string + number: number + title: string + state: PullState + ciState: CheckState + createdAt: Date + updatedAt: Date + url: string + additions: number + deletions: number + author: User + requestedReviewers: User[] + requestedTeams: Team[] + reviews: Review[] + discussions: Discussion[] } export type Attention = { @@ -41,7 +67,10 @@ export type Attention = { } export type Pull = PullProps & { - starred: number, // boolean is not indexable. - sections: string[], - attention?: Attention, + uid: string + host: string + starred: number // boolean is not indexable. + sections: string[] + attention?: Attention + connection: string } \ No newline at end of file diff --git a/packages/testing/src/index.ts b/packages/testing/src/index.ts index 410a825..28a2ef7 100644 --- a/packages/testing/src/index.ts +++ b/packages/testing/src/index.ts @@ -1,25 +1,33 @@ -import { type Pull, type Section, type Connection, PullState } from "@repo/model"; +import { type Pull, type Section, type Connection, PullState, CheckState } from "@repo/model"; -export function mockPull(props?: Partial): Pull { +export function mockPull(props?: Omit, "uid"|"url">): Pull { + const id = props?.id ?? "PR_1"; + const repo = props?.repo ?? "pvcnt/mergeable"; + const number = props?.number ?? 1; + const host = props?.host ?? "github.com"; + const connection = props?.connection ?? "1"; return { - uid: "1:1", - host: "github.com", - repo: "pvcnt/mergeable", - number: 1, - title: "Title", + id, + repo, + number, + title: "Pull request", state: PullState.Pending, - createdAt: "2024-08-05T15:57:00Z", - updatedAt: "2024-08-05T15:57:00Z", - url: "https://github.com/pvcnt/mergeable/1", + ciState: CheckState.None, + createdAt: new Date("2024-08-05T15:57:00Z"), + updatedAt: new Date("2024-08-05T15:57:00Z"), + url: `https://${host}/${repo}/${number}`, additions: 0, deletions: 0, author: { name: "pvcnt", avatarUrl: "" }, - comments: 0, requestedReviewers: [], requestedTeams: [], - reviewers: [], + reviews: [], + discussions: [], + uid: `${connection}:${id}`, + host, starred: 0, sections: [], + connection, ...props, }; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e92967c..35be096 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -177,15 +177,15 @@ importers: packages/github: dependencies: - '@octokit/core': - specifier: ^6.1.2 - version: 6.1.2 - '@octokit/rest': - specifier: ^20.1.1 - version: 20.1.1 + '@octokit/plugin-throttling': + specifier: ^9.3.1 + version: 9.3.1(@octokit/core@6.1.2) '@repo/model': specifier: workspace:* version: link:../model + octokit: + specifier: ^4.0.2 + version: 4.0.2 devDependencies: '@pollyjs/adapter-fetch': specifier: ^6.0.6 @@ -374,6 +374,10 @@ packages: resolution: {integrity: sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.25.5': + resolution: {integrity: sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.24.7': resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} engines: {node: '>=6.9.0'} @@ -488,6 +492,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.25.4': + resolution: {integrity: sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-syntax-async-generators@7.8.4': resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} peerDependencies: @@ -503,6 +512,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.24.7': + resolution: {integrity: sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-import-meta@7.10.4': resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} peerDependencies: @@ -549,14 +570,20 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-syntax-top-level-await@7.14.5': resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.24.7': - resolution: {integrity: sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==} + '@babel/plugin-syntax-typescript@7.25.4': + resolution: {integrity: sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -583,6 +610,10 @@ packages: resolution: {integrity: sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.25.4': + resolution: {integrity: sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==} + engines: {node: '>=6.9.0'} + '@babel/template@7.24.6': resolution: {integrity: sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw==} engines: {node: '>=6.9.0'} @@ -607,6 +638,10 @@ packages: resolution: {integrity: sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.25.4': + resolution: {integrity: sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} @@ -1088,16 +1123,32 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@octokit/auth-token@4.0.0': - resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} + '@octokit/app@15.1.0': + resolution: {integrity: sha512-TkBr7QgOmE6ORxvIAhDbZsqPkF7RSqTY4pLTtUQCvr6dTXqvi2fFo46q3h1lxlk/sGMQjqyZ0kEahkD/NyzOHg==} + engines: {node: '>= 18'} + + '@octokit/auth-app@7.1.0': + resolution: {integrity: sha512-cazGaJPSgeZ8NkVYeM/C5l/6IQ5vZnsI8p1aMucadCkt/bndI+q+VqwrlnWbASRmenjOkf1t1RpCKrif53U8gw==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-app@8.1.1': + resolution: {integrity: sha512-5UtmxXAvU2wfcHIPPDWzVSAWXVJzG3NWsxb7zCFplCWEmMCArSZV0UQu5jw5goLQXbFyOr5onzEH37UJB3zQQg==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-device@7.1.1': + resolution: {integrity: sha512-HWl8lYueHonuyjrKKIup/1tiy0xcmQCdq5ikvMO1YwkNNkxb6DXfrPjrMYItNLyCP/o2H87WuijuE+SlBTT8eg==} + engines: {node: '>= 18'} + + '@octokit/auth-oauth-user@5.1.1': + resolution: {integrity: sha512-rRkMz0ErOppdvEfnemHJXgZ9vTPhBuC6yASeFaB7I2yLMd7QpjfrL1mnvRPlyKo+M6eeLxrKanXJ9Qte29SRsw==} engines: {node: '>= 18'} '@octokit/auth-token@5.1.1': resolution: {integrity: sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==} engines: {node: '>= 18'} - '@octokit/core@5.2.0': - resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==} + '@octokit/auth-unauthenticated@6.1.0': + resolution: {integrity: sha512-zPSmfrUAcspZH/lOFQnVnvjQZsIvmfApQH6GzJrkIunDooU1Su2qt2FfMTSVPRp7WLTQyC20Kd55lF+mIYaohQ==} engines: {node: '>= 18'} '@octokit/core@6.1.2': @@ -1108,62 +1159,77 @@ packages: resolution: {integrity: sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==} engines: {node: '>= 18'} - '@octokit/endpoint@9.0.5': - resolution: {integrity: sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==} + '@octokit/graphql@8.1.1': + resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + engines: {node: '>= 18'} + + '@octokit/oauth-app@7.1.3': + resolution: {integrity: sha512-EHXbOpBkSGVVGF1W+NLMmsnSsJRkcrnVmDKt0TQYRBb6xWfWzoi9sBD4DIqZ8jGhOWO/V8t4fqFyJ4vDQDn9bg==} engines: {node: '>= 18'} - '@octokit/graphql@7.1.0': - resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==} + '@octokit/oauth-authorization-url@7.1.1': + resolution: {integrity: sha512-ooXV8GBSabSWyhLUowlMIVd9l1s2nsOGQdlP2SQ4LnkEsGXzeCvbSbCPdZThXhEFzleGPwbapT0Sb+YhXRyjCA==} engines: {node: '>= 18'} - '@octokit/graphql@8.1.1': - resolution: {integrity: sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==} + '@octokit/oauth-methods@5.1.2': + resolution: {integrity: sha512-C5lglRD+sBlbrhCUTxgJAFjWgJlmTx5bQ7Ch0+2uqRjYv7Cfb5xpX4WuSC9UgQna3sqRGBL9EImX9PvTpMaQ7g==} engines: {node: '>= 18'} '@octokit/openapi-types@22.2.0': resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==} - '@octokit/plugin-paginate-rest@11.3.1': - resolution: {integrity: sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==} + '@octokit/openapi-webhooks-types@8.3.0': + resolution: {integrity: sha512-vKLsoR4xQxg4Z+6rU/F65ItTUz/EXbD+j/d4mlq2GW8TsA4Tc8Kdma2JTAAJ5hrKWUQzkR/Esn2fjsqiVRYaQg==} + + '@octokit/plugin-paginate-graphql@5.2.2': + resolution: {integrity: sha512-7znSVvlNAOJisCqAnjN1FtEziweOHSjPGAuc5W58NeGNAr/ZB57yCsjQbXDlWsVryA7hHQaEQPcBbJYFawlkyg==} engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '5' + '@octokit/core': '>=6' - '@octokit/plugin-request-log@4.0.1': - resolution: {integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==} + '@octokit/plugin-paginate-rest@11.3.3': + resolution: {integrity: sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA==} engines: {node: '>= 18'} peerDependencies: - '@octokit/core': '5' + '@octokit/core': '>=6' - '@octokit/plugin-rest-endpoint-methods@13.2.2': - resolution: {integrity: sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==} + '@octokit/plugin-rest-endpoint-methods@13.2.4': + resolution: {integrity: sha512-gusyAVgTrPiuXOdfqOySMDztQHv6928PQ3E4dqVGEtOvRXAKRbJR4b1zQyniIT9waqaWk/UDaoJ2dyPr7Bk7Iw==} engines: {node: '>= 18'} peerDependencies: - '@octokit/core': ^5 + '@octokit/core': '>=6' - '@octokit/request-error@5.1.0': - resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==} + '@octokit/plugin-retry@7.1.1': + resolution: {integrity: sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==} engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': '>=6' - '@octokit/request-error@6.1.1': - resolution: {integrity: sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==} + '@octokit/plugin-throttling@9.3.1': + resolution: {integrity: sha512-Qd91H4liUBhwLB2h6jZ99bsxoQdhgPk6TdwnClPyTBSDAdviGPceViEgUwj+pcQDmB/rfAXAXK7MTochpHM3yQ==} engines: {node: '>= 18'} + peerDependencies: + '@octokit/core': ^6.0.0 - '@octokit/request@8.4.0': - resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==} + '@octokit/request-error@6.1.1': + resolution: {integrity: sha512-1mw1gqT3fR/WFvnoVpY/zUM2o/XkMs/2AszUUG9I69xn0JFLv6PGkPhNk5lbfvROs79wiS0bqiJNxfCZcRJJdg==} engines: {node: '>= 18'} '@octokit/request@9.1.1': resolution: {integrity: sha512-pyAguc0p+f+GbQho0uNetNQMmLG1e80WjkIaqqgUkihqUp0boRU6nKItXO4VWnr+nbZiLGEyy4TeKRwqaLvYgw==} engines: {node: '>= 18'} - '@octokit/rest@20.1.1': - resolution: {integrity: sha512-MB4AYDsM5jhIHro/dq4ix1iWTLGToIGk6cWF5L6vanFaMble5jTX/UBQyiv05HsWnwUtY8JrfHy2LWfKwihqMw==} - engines: {node: '>= 18'} - '@octokit/types@13.5.0': resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==} + '@octokit/webhooks-methods@5.1.0': + resolution: {integrity: sha512-yFZa3UH11VIxYnnoOYCVoJ3q4ChuSOk2IVBBQ0O3xtKX4x9bmKb/1t+Mxixv2iUhzMdOl1qeWJqEhouXXzB3rQ==} + engines: {node: '>= 18'} + + '@octokit/webhooks@13.3.0': + resolution: {integrity: sha512-TUkJLtI163Bz5+JK0O+zDkQpn4gKwN+BovclUvCj6pI/6RXrFqQvUMRS2M+Rt8Rv0qR3wjoMoOPmpJKeOh0nBg==} + engines: {node: '>= 18'} + '@oslojs/encoding@0.4.1': resolution: {integrity: sha512-hkjo6MuIK/kQR5CrGNdAPZhS01ZCXuWDRJ187zh6qqF2+yMHZpD9fAYpX8q2bOO6Ryhl3XpCT6kUX76N8hhm4Q==} @@ -1396,6 +1462,9 @@ packages: '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/aws-lambda@8.10.143': + resolution: {integrity: sha512-u5vzlcR14ge/4pMTTMDQr3MF0wEe38B2F9o84uC4F43vN5DGTy63npRrB6jQhyt+C0lGv4ZfiRcRkqJoZuPnmg==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -1489,8 +1558,8 @@ packages: '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - '@types/yargs@17.0.32': - resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} '@typescript-eslint/eslint-plugin@8.1.0': resolution: {integrity: sha512-LlNBaHFCEBPHyD4pZXb35mzjGkuGKXU5eeCA1SxvHfiRES0E82dOounfVpL4DCqYvJEKab0bZIA0gCRpdLKkCw==} @@ -1616,8 +1685,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} + acorn-walk@8.3.3: + resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} engines: {node: '>=0.4.0'} acorn@8.11.3: @@ -1742,8 +1811,8 @@ packages: resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - babel-preset-current-node-syntax@1.0.1: - resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} + babel-preset-current-node-syntax@1.1.0: + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} peerDependencies: '@babel/core': ^7.0.0 @@ -1790,9 +1859,6 @@ packages: bcp-47@2.1.0: resolution: {integrity: sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==} - before-after-hook@2.2.3: - resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} - before-after-hook@3.0.2: resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} @@ -1813,6 +1879,9 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + bottleneck@2.19.5: + resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} + bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} @@ -1950,8 +2019,8 @@ packages: resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} engines: {node: '>=8'} - cjs-module-lexer@1.3.1: - resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} + cjs-module-lexer@1.4.0: + resolution: {integrity: sha512-N1NGmowPlGBLsOZLPvm48StN04V4YvQRL0i6b7ctrVY3epjP/ct7hFLOItz6pDIvRjwpfPxi52a2UWV2ziir8g==} classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} @@ -2155,9 +2224,6 @@ packages: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} - deprecation@2.3.1: - resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -2784,8 +2850,8 @@ packages: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} - import-local@3.1.0: - resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} engines: {node: '>=8'} hasBin: true @@ -2844,8 +2910,9 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} + is-core-module@2.15.1: + resolution: {integrity: sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==} + engines: {node: '>= 0.4'} is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} @@ -3455,6 +3522,10 @@ packages: resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} engines: {node: '>=8.6'} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -3603,6 +3674,10 @@ packages: resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} engines: {node: '>= 0.4'} + octokit@4.0.2: + resolution: {integrity: sha512-wbqF4uc1YbcldtiBFfkSnquHtECEIpYD78YUXI6ri1Im5OO2NLo6ZVpRdbJpdnpZ05zMrVPssNiEo6JQtea+Qg==} + engines: {node: '>= 18'} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -4650,8 +4725,8 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - universal-user-agent@6.0.1: - resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} + universal-github-app-jwt@2.2.0: + resolution: {integrity: sha512-G5o6f95b5BggDGuUfKDApKaCgNYy2x7OdHY0zSMF081O0EJobw+1130VONhrA7ezGSV2FNOGyM+KQpQZAr9bIQ==} universal-user-agent@7.0.2: resolution: {integrity: sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==} @@ -4714,8 +4789,8 @@ packages: v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - v8-to-istanbul@9.2.0: - resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} vary@1.1.2: @@ -5293,6 +5368,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 + '@babel/generator@7.25.5': + dependencies: + '@babel/types': 7.25.4 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + optional: true + '@babel/helper-annotate-as-pure@7.24.7': dependencies: '@babel/types': 7.25.2 @@ -5417,6 +5500,11 @@ snapshots: dependencies: '@babel/types': 7.25.2 + '@babel/parser@7.25.4': + dependencies: + '@babel/types': 7.25.4 + optional: true + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -5435,6 +5523,18 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 optional: true + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + + '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 @@ -5488,13 +5588,19 @@ snapshots: '@babel/helper-plugin-utils': 7.24.8 optional: true + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.25.2)': + dependencies: + '@babel/core': 7.25.2 + '@babel/helper-plugin-utils': 7.24.8 + optional: true + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 optional: true - '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.25.2)': + '@babel/plugin-syntax-typescript@7.25.4(@babel/core@7.25.2)': dependencies: '@babel/core': 7.25.2 '@babel/helper-plugin-utils': 7.24.8 @@ -5525,6 +5631,10 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.25.4': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.24.6': dependencies: '@babel/code-frame': 7.24.6 @@ -5576,6 +5686,13 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 + '@babel/types@7.25.4': + dependencies: + '@babel/helper-string-parser': 7.24.8 + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + optional: true + '@bcoe/v8-coverage@0.2.3': optional: true @@ -5929,7 +6046,7 @@ snapshots: jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 strip-ansi: 6.0.1 @@ -6005,7 +6122,7 @@ snapshots: slash: 3.0.0 string-length: 4.0.2 strip-ansi: 6.0.1 - v8-to-istanbul: 9.2.0 + v8-to-istanbul: 9.3.0 transitivePeerDependencies: - supports-color optional: true @@ -6051,7 +6168,7 @@ snapshots: jest-haste-map: 29.7.0 jest-regex-util: 29.6.3 jest-util: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 pirates: 4.0.6 slash: 3.0.0 write-file-atomic: 4.0.2 @@ -6065,7 +6182,7 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 '@types/node': 22.5.0 - '@types/yargs': 17.0.32 + '@types/yargs': 17.0.33 chalk: 4.1.2 optional: true @@ -6134,19 +6251,56 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 - '@octokit/auth-token@4.0.0': {} + '@octokit/app@15.1.0': + dependencies: + '@octokit/auth-app': 7.1.0 + '@octokit/auth-unauthenticated': 6.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-app': 7.1.3 + '@octokit/plugin-paginate-rest': 11.3.3(@octokit/core@6.1.2) + '@octokit/types': 13.5.0 + '@octokit/webhooks': 13.3.0 + + '@octokit/auth-app@7.1.0': + dependencies: + '@octokit/auth-oauth-app': 8.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/request': 9.1.1 + '@octokit/request-error': 6.1.1 + '@octokit/types': 13.5.0 + lru-cache: 10.4.3 + universal-github-app-jwt: 2.2.0 + universal-user-agent: 7.0.2 + + '@octokit/auth-oauth-app@8.1.1': + dependencies: + '@octokit/auth-oauth-device': 7.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/request': 9.1.1 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/auth-oauth-device@7.1.1': + dependencies: + '@octokit/oauth-methods': 5.1.2 + '@octokit/request': 9.1.1 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 + + '@octokit/auth-oauth-user@5.1.1': + dependencies: + '@octokit/auth-oauth-device': 7.1.1 + '@octokit/oauth-methods': 5.1.2 + '@octokit/request': 9.1.1 + '@octokit/types': 13.5.0 + universal-user-agent: 7.0.2 '@octokit/auth-token@5.1.1': {} - '@octokit/core@5.2.0': + '@octokit/auth-unauthenticated@6.1.0': dependencies: - '@octokit/auth-token': 4.0.0 - '@octokit/graphql': 7.1.0 - '@octokit/request': 8.4.0 - '@octokit/request-error': 5.1.0 + '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 - before-after-hook: 2.2.3 - universal-user-agent: 6.0.1 '@octokit/core@6.1.2': dependencies: @@ -6163,55 +6317,66 @@ snapshots: '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 - '@octokit/endpoint@9.0.5': + '@octokit/graphql@8.1.1': dependencies: + '@octokit/request': 9.1.1 '@octokit/types': 13.5.0 - universal-user-agent: 6.0.1 + universal-user-agent: 7.0.2 - '@octokit/graphql@7.1.0': + '@octokit/oauth-app@7.1.3': dependencies: - '@octokit/request': 8.4.0 - '@octokit/types': 13.5.0 - universal-user-agent: 6.0.1 + '@octokit/auth-oauth-app': 8.1.1 + '@octokit/auth-oauth-user': 5.1.1 + '@octokit/auth-unauthenticated': 6.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-authorization-url': 7.1.1 + '@octokit/oauth-methods': 5.1.2 + '@types/aws-lambda': 8.10.143 + universal-user-agent: 7.0.2 - '@octokit/graphql@8.1.1': + '@octokit/oauth-authorization-url@7.1.1': {} + + '@octokit/oauth-methods@5.1.2': dependencies: + '@octokit/oauth-authorization-url': 7.1.1 '@octokit/request': 9.1.1 + '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 - universal-user-agent: 7.0.2 '@octokit/openapi-types@22.2.0': {} - '@octokit/plugin-paginate-rest@11.3.1(@octokit/core@5.2.0)': + '@octokit/openapi-webhooks-types@8.3.0': {} + + '@octokit/plugin-paginate-graphql@5.2.2(@octokit/core@6.1.2)': dependencies: - '@octokit/core': 5.2.0 - '@octokit/types': 13.5.0 + '@octokit/core': 6.1.2 - '@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.0)': + '@octokit/plugin-paginate-rest@11.3.3(@octokit/core@6.1.2)': dependencies: - '@octokit/core': 5.2.0 + '@octokit/core': 6.1.2 + '@octokit/types': 13.5.0 - '@octokit/plugin-rest-endpoint-methods@13.2.2(@octokit/core@5.2.0)': + '@octokit/plugin-rest-endpoint-methods@13.2.4(@octokit/core@6.1.2)': dependencies: - '@octokit/core': 5.2.0 + '@octokit/core': 6.1.2 '@octokit/types': 13.5.0 - '@octokit/request-error@5.1.0': + '@octokit/plugin-retry@7.1.1(@octokit/core@6.1.2)': dependencies: + '@octokit/core': 6.1.2 + '@octokit/request-error': 6.1.1 '@octokit/types': 13.5.0 - deprecation: 2.3.1 - once: 1.4.0 + bottleneck: 2.19.5 - '@octokit/request-error@6.1.1': + '@octokit/plugin-throttling@9.3.1(@octokit/core@6.1.2)': dependencies: + '@octokit/core': 6.1.2 '@octokit/types': 13.5.0 + bottleneck: 2.19.5 - '@octokit/request@8.4.0': + '@octokit/request-error@6.1.1': dependencies: - '@octokit/endpoint': 9.0.5 - '@octokit/request-error': 5.1.0 '@octokit/types': 13.5.0 - universal-user-agent: 6.0.1 '@octokit/request@9.1.1': dependencies: @@ -6220,17 +6385,18 @@ snapshots: '@octokit/types': 13.5.0 universal-user-agent: 7.0.2 - '@octokit/rest@20.1.1': - dependencies: - '@octokit/core': 5.2.0 - '@octokit/plugin-paginate-rest': 11.3.1(@octokit/core@5.2.0) - '@octokit/plugin-request-log': 4.0.1(@octokit/core@5.2.0) - '@octokit/plugin-rest-endpoint-methods': 13.2.2(@octokit/core@5.2.0) - '@octokit/types@13.5.0': dependencies: '@octokit/openapi-types': 22.2.0 + '@octokit/webhooks-methods@5.1.0': {} + + '@octokit/webhooks@13.3.0': + dependencies: + '@octokit/openapi-webhooks-types': 8.3.0 + '@octokit/request-error': 6.1.1 + '@octokit/webhooks-methods': 5.1.0 + '@oslojs/encoding@0.4.1': {} '@pagefind/darwin-arm64@1.1.0': @@ -6393,7 +6559,7 @@ snapshots: '@testing-library/dom@10.1.0': dependencies: '@babel/code-frame': 7.24.7 - '@babel/runtime': 7.24.6 + '@babel/runtime': 7.25.4 '@types/aria-query': 5.0.4 aria-query: 5.3.0 chalk: 4.1.2 @@ -6448,6 +6614,8 @@ snapshots: '@types/aria-query@5.0.4': {} + '@types/aws-lambda@8.10.143': {} + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.24.6 @@ -6562,7 +6730,7 @@ snapshots: '@types/yargs-parser@21.0.3': optional: true - '@types/yargs@17.0.32': + '@types/yargs@17.0.33': dependencies: '@types/yargs-parser': 21.0.3 optional: true @@ -6773,7 +6941,9 @@ snapshots: dependencies: acorn: 8.12.1 - acorn-walk@8.3.2: + acorn-walk@8.3.3: + dependencies: + acorn: 8.12.1 optional: true acorn@8.11.3: {} @@ -6978,17 +7148,19 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: '@babel/template': 7.25.0 - '@babel/types': 7.25.2 + '@babel/types': 7.25.4 '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.20.6 optional: true - babel-preset-current-node-syntax@1.0.1(@babel/core@7.25.2): + babel-preset-current-node-syntax@1.1.0(@babel/core@7.25.2): dependencies: '@babel/core': 7.25.2 '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.25.2) '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.25.2) '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.25.2) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.25.2) + '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.25.2) '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.25.2) '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.25.2) '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.25.2) @@ -6997,6 +7169,7 @@ snapshots: '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.25.2) '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.25.2) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.25.2) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.25.2) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.25.2) optional: true @@ -7004,7 +7177,7 @@ snapshots: dependencies: '@babel/core': 7.25.2 babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.25.2) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.25.2) optional: true bail@2.0.2: {} @@ -7050,8 +7223,6 @@ snapshots: is-alphanumerical: 2.0.1 is-decimal: 2.0.1 - before-after-hook@2.2.3: {} - before-after-hook@3.0.2: {} binary-extensions@2.3.0: {} @@ -7083,6 +7254,8 @@ snapshots: boolbase@1.0.0: {} + bottleneck@2.19.5: {} + bowser@2.11.0: {} boxen@7.1.1: @@ -7248,7 +7421,7 @@ snapshots: ci-info@4.0.0: {} - cjs-module-lexer@1.3.1: + cjs-module-lexer@1.4.0: optional: true classnames@2.5.1: {} @@ -7424,8 +7597,6 @@ snapshots: depd@2.0.0: {} - deprecation@2.3.1: {} - dequal@2.0.3: {} destroy@1.2.0: {} @@ -8249,7 +8420,7 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-local@3.1.0: + import-local@3.2.0: dependencies: pkg-dir: 4.2.0 resolve-cwd: 3.0.0 @@ -8296,7 +8467,7 @@ snapshots: dependencies: binary-extensions: 2.3.0 - is-core-module@2.13.1: + is-core-module@2.15.1: dependencies: hasown: 2.0.2 optional: true @@ -8360,7 +8531,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.25.2 - '@babel/parser': 7.25.3 + '@babel/parser': 7.25.4 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -8458,7 +8629,7 @@ snapshots: chalk: 4.1.2 create-jest: 29.7.0(@types/node@22.5.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)) exit: 0.1.2 - import-local: 3.1.0 + import-local: 3.2.0 jest-config: 29.7.0(@types/node@22.5.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)) jest-util: 29.7.0 jest-validate: 29.7.0 @@ -8489,7 +8660,7 @@ snapshots: jest-runner: 29.7.0 jest-util: 29.7.0 jest-validate: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 parse-json: 5.2.0 pretty-format: 29.7.0 slash: 3.0.0 @@ -8548,7 +8719,7 @@ snapshots: jest-regex-util: 29.6.3 jest-util: 29.7.0 jest-worker: 29.7.0 - micromatch: 4.0.7 + micromatch: 4.0.8 walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 @@ -8575,7 +8746,7 @@ snapshots: '@types/stack-utils': 2.0.3 chalk: 4.1.2 graceful-fs: 4.2.11 - micromatch: 4.0.7 + micromatch: 4.0.8 pretty-format: 29.7.0 slash: 3.0.0 stack-utils: 2.0.6 @@ -8655,7 +8826,7 @@ snapshots: '@jest/types': 29.6.3 '@types/node': 22.5.0 chalk: 4.1.2 - cjs-module-lexer: 1.3.1 + cjs-module-lexer: 1.4.0 collect-v8-coverage: 1.0.2 glob: 7.2.3 graceful-fs: 4.2.11 @@ -8675,14 +8846,14 @@ snapshots: jest-snapshot@29.7.0: dependencies: '@babel/core': 7.25.2 - '@babel/generator': 7.25.0 + '@babel/generator': 7.25.5 '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.25.2) - '@babel/types': 7.25.2 + '@babel/plugin-syntax-typescript': 7.25.4(@babel/core@7.25.2) + '@babel/types': 7.25.4 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.25.2) + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.25.2) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -8742,7 +8913,7 @@ snapshots: dependencies: '@jest/core': 29.7.0(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)) '@jest/types': 29.6.3 - import-local: 3.1.0 + import-local: 3.2.0 jest-cli: 29.7.0(@types/node@22.5.0)(ts-node@10.9.2(@types/node@22.5.0)(typescript@5.5.4)) transitivePeerDependencies: - '@types/node' @@ -9413,6 +9584,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + optional: true + mime-db@1.52.0: {} mime-types@2.1.35: @@ -9528,6 +9705,19 @@ snapshots: object-inspect@1.13.2: {} + octokit@4.0.2: + dependencies: + '@octokit/app': 15.1.0 + '@octokit/core': 6.1.2 + '@octokit/oauth-app': 7.1.3 + '@octokit/plugin-paginate-graphql': 5.2.2(@octokit/core@6.1.2) + '@octokit/plugin-paginate-rest': 11.3.3(@octokit/core@6.1.2) + '@octokit/plugin-rest-endpoint-methods': 13.2.4(@octokit/core@6.1.2) + '@octokit/plugin-retry': 7.1.1(@octokit/core@6.1.2) + '@octokit/plugin-throttling': 9.3.1(@octokit/core@6.1.2) + '@octokit/request-error': 6.1.1 + '@octokit/types': 13.5.0 + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -10100,7 +10290,7 @@ snapshots: resolve@1.22.8: dependencies: - is-core-module: 2.13.1 + is-core-module: 2.15.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 optional: true @@ -10596,7 +10786,7 @@ snapshots: '@tsconfig/node16': 1.0.4 '@types/node': 22.5.0 acorn: 8.12.1 - acorn-walk: 8.3.2 + acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 @@ -10761,7 +10951,7 @@ snapshots: unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - universal-user-agent@6.0.1: {} + universal-github-app-jwt@2.2.0: {} universal-user-agent@7.0.2: {} @@ -10820,7 +11010,7 @@ snapshots: v8-compile-cache-lib@3.0.1: optional: true - v8-to-istanbul@9.2.0: + v8-to-istanbul@9.3.0: dependencies: '@jridgewell/trace-mapping': 0.3.25 '@types/istanbul-lib-coverage': 2.0.6