edsger 0.49.0 โ†’ 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,11 +21,27 @@ export declare function fetchFileContent(opts: FetchFileContentOptions): Promise
21
21
  * Fetch PR file changes (diff information and full content)
22
22
  */
23
23
  export declare function fetchPRFileChanges(octokit: Octokit, owner: string, repo: string, prNumber: number, verbose?: boolean): Promise<PRFileChange[]>;
24
+ export interface GraphQLClient {
25
+ graphql: <ResponseData>(query: string, variables?: Record<string, unknown>) => Promise<ResponseData>;
26
+ }
27
+ /**
28
+ * Page through every `reviewThreads` connection, following `pageInfo.endCursor`
29
+ * until GitHub reports `hasNextPage: false`. Exported for tests.
30
+ */
31
+ export declare function fetchAllReviewThreads(octokit: GraphQLClient, owner: string, repo: string, prNumber: number, verbose?: boolean): Promise<ReviewThread[]>;
24
32
  /**
25
33
  * Fetch unresolved review threads using GitHub GraphQL API
26
34
  * This provides accurate resolution status unlike REST API
27
35
  */
28
36
  export declare function fetchUnresolvedReviewThreads(octokit: Octokit, owner: string, repo: string, prNumber: number, verbose?: boolean): Promise<ReviewThread[]>;
37
+ /**
38
+ * For any thread whose `comments` connection was truncated (>100 comments),
39
+ * page through the remaining comments and append them to the thread in place.
40
+ * Each thread is hydrated concurrently; the per-thread loop is still
41
+ * sequential because each page depends on the previous cursor. Exported for
42
+ * tests.
43
+ */
44
+ export declare function hydrateThreadComments(octokit: GraphQLClient, threads: ReviewThread[], verbose?: boolean): Promise<void>;
29
45
  /**
30
46
  * Mark review threads as resolved using GraphQL API
31
47
  */
@@ -87,50 +87,108 @@ export async function fetchPRFileChanges(octokit, owner, repo, prNumber, verbose
87
87
  return [];
88
88
  }
89
89
  }
90
- /**
91
- * Fetch unresolved review threads using GitHub GraphQL API
92
- * This provides accurate resolution status unlike REST API
93
- */
94
- export async function fetchUnresolvedReviewThreads(octokit, owner, repo, prNumber, verbose) {
95
- try {
96
- if (verbose) {
97
- logInfo('๐Ÿ” Fetching review threads via GraphQL API...');
98
- }
99
- const query = `
100
- query($owner: String!, $repo: String!, $prNumber: Int!) {
101
- repository(owner: $owner, name: $repo) {
102
- pullRequest(number: $prNumber) {
103
- reviewThreads(first: 100) {
90
+ const REVIEW_THREADS_QUERY = `
91
+ query($owner: String!, $repo: String!, $prNumber: Int!, $threadCursor: String) {
92
+ repository(owner: $owner, name: $repo) {
93
+ pullRequest(number: $prNumber) {
94
+ reviewThreads(first: 100, after: $threadCursor) {
95
+ pageInfo {
96
+ hasNextPage
97
+ endCursor
98
+ }
99
+ nodes {
100
+ id
101
+ isResolved
102
+ isOutdated
103
+ comments(first: 100) {
104
+ totalCount
105
+ pageInfo {
106
+ hasNextPage
107
+ endCursor
108
+ }
104
109
  nodes {
105
110
  id
106
- isResolved
107
- isOutdated
108
- comments(first: 100) {
109
- totalCount
110
- nodes {
111
- id
112
- author {
113
- login
114
- }
115
- body
116
- path
117
- line
118
- url
119
- }
111
+ author {
112
+ login
120
113
  }
114
+ body
115
+ path
116
+ line
117
+ url
121
118
  }
122
119
  }
123
120
  }
124
121
  }
125
122
  }
126
- `;
127
- const result = await octokit.graphql(query, {
123
+ }
124
+ }
125
+ `;
126
+ const THREAD_COMMENTS_QUERY = `
127
+ query($threadId: ID!, $cursor: String) {
128
+ node(id: $threadId) {
129
+ ... on PullRequestReviewThread {
130
+ comments(first: 100, after: $cursor) {
131
+ pageInfo {
132
+ hasNextPage
133
+ endCursor
134
+ }
135
+ nodes {
136
+ id
137
+ author {
138
+ login
139
+ }
140
+ body
141
+ path
142
+ line
143
+ url
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ `;
150
+ /**
151
+ * Page through every `reviewThreads` connection, following `pageInfo.endCursor`
152
+ * until GitHub reports `hasNextPage: false`. Exported for tests.
153
+ */
154
+ export async function fetchAllReviewThreads(octokit, owner, repo, prNumber, verbose) {
155
+ const allThreads = [];
156
+ let threadCursor = null;
157
+ let threadPage = 0;
158
+ while (true) {
159
+ threadPage += 1;
160
+ const result = await octokit.graphql(REVIEW_THREADS_QUERY, {
128
161
  owner,
129
162
  repo,
130
163
  prNumber,
164
+ threadCursor,
131
165
  });
132
- const allThreads = result?.repository
133
- ?.pullRequest?.reviewThreads?.nodes || [];
166
+ const page = result.repository?.pullRequest?.reviewThreads;
167
+ const pageThreads = page?.nodes ?? [];
168
+ allThreads.push(...pageThreads);
169
+ if (verbose) {
170
+ logInfo(` ยท page ${threadPage}: fetched ${pageThreads.length} thread(s) (total so far: ${allThreads.length})`);
171
+ }
172
+ const pageInfo = page?.pageInfo;
173
+ if (!pageInfo?.hasNextPage || !pageInfo.endCursor) {
174
+ break;
175
+ }
176
+ threadCursor = pageInfo.endCursor;
177
+ }
178
+ return allThreads;
179
+ }
180
+ /**
181
+ * Fetch unresolved review threads using GitHub GraphQL API
182
+ * This provides accurate resolution status unlike REST API
183
+ */
184
+ export async function fetchUnresolvedReviewThreads(octokit, owner, repo, prNumber, verbose) {
185
+ try {
186
+ if (verbose) {
187
+ logInfo('๐Ÿ” Fetching review threads via GraphQL API...');
188
+ }
189
+ const allThreads = await fetchAllReviewThreads(octokit, owner, repo, prNumber, verbose);
190
+ // Fill in any threads whose comments were truncated at 100
191
+ await hydrateThreadComments(octokit, allThreads, verbose);
134
192
  // Filter for truly unresolved threads
135
193
  // - Exclude resolved threads (isResolved = true)
136
194
  // - Exclude outdated threads (isOutdated = true) - these mean code has changed, should auto-resolve
@@ -158,6 +216,34 @@ export async function fetchUnresolvedReviewThreads(octokit, owner, repo, prNumbe
158
216
  return [];
159
217
  }
160
218
  }
219
+ /**
220
+ * For any thread whose `comments` connection was truncated (>100 comments),
221
+ * page through the remaining comments and append them to the thread in place.
222
+ * Each thread is hydrated concurrently; the per-thread loop is still
223
+ * sequential because each page depends on the previous cursor. Exported for
224
+ * tests.
225
+ */
226
+ export async function hydrateThreadComments(octokit, threads, verbose) {
227
+ await Promise.all(threads.map((thread) => hydrateOneThreadComments(octokit, thread, verbose)));
228
+ }
229
+ async function hydrateOneThreadComments(octokit, thread, verbose) {
230
+ let { pageInfo } = thread.comments;
231
+ let cursor = pageInfo?.endCursor ?? null;
232
+ while (pageInfo?.hasNextPage && cursor) {
233
+ const result = await octokit.graphql(THREAD_COMMENTS_QUERY, {
234
+ threadId: thread.id,
235
+ cursor,
236
+ });
237
+ const page = result.node?.comments;
238
+ const pageComments = page?.nodes ?? [];
239
+ thread.comments.nodes.push(...pageComments);
240
+ if (verbose) {
241
+ logInfo(` ยท thread ${thread.id}: fetched ${pageComments.length} extra comment(s)`);
242
+ }
243
+ pageInfo = page?.pageInfo;
244
+ cursor = pageInfo?.endCursor ?? null;
245
+ }
246
+ }
161
247
  /**
162
248
  * Mark review threads as resolved using GraphQL API
163
249
  */
@@ -2,22 +2,27 @@
2
2
  * Type definitions for Code Refine Verification
3
3
  */
4
4
  import { type EdsgerConfig } from '../../types/index.js';
5
+ export interface ReviewThreadComment {
6
+ id: string;
7
+ author: {
8
+ login: string;
9
+ };
10
+ body: string;
11
+ path: string;
12
+ line: number | null;
13
+ url: string;
14
+ }
5
15
  export interface ReviewThread {
6
16
  id: string;
7
17
  isResolved: boolean;
8
18
  isOutdated: boolean;
9
19
  comments: {
10
20
  totalCount: number;
11
- nodes: {
12
- id: string;
13
- author: {
14
- login: string;
15
- };
16
- body: string;
17
- path: string;
18
- line: number | null;
19
- url: string;
20
- }[];
21
+ pageInfo?: {
22
+ hasNextPage: boolean;
23
+ endCursor: string | null;
24
+ };
25
+ nodes: ReviewThreadComment[];
21
26
  };
22
27
  }
23
28
  export interface PRFileChange {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.49.0",
3
+ "version": "0.50.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"
package/vitest.config.ts CHANGED
@@ -2,10 +2,14 @@ import { defineConfig } from 'vitest/config'
2
2
 
3
3
  export default defineConfig({
4
4
  test: {
5
- // The other __tests__ folders in this package use the node:test API
6
- // (assert + node --test), not vitest. We scope vitest to just the
7
- // run-sheet tests for now; migrating the rest can happen separately.
8
- include: ['src/phases/run-sheet/__tests__/**/*.test.ts'],
5
+ // Most other __tests__ folders in this package use the node:test API
6
+ // (assert + node --test), not vitest. Scope vitest to the folders whose
7
+ // tests are written against `vitest`; migrating the rest can happen
8
+ // separately.
9
+ include: [
10
+ 'src/phases/run-sheet/__tests__/**/*.test.ts',
11
+ 'src/phases/code-refine-verification/__tests__/**/*.test.ts',
12
+ ],
9
13
  exclude: ['dist/**', 'node_modules/**'],
10
14
  environment: 'node',
11
15
  },