@rmdes/indiekit-endpoint-github 1.2.1 → 1.2.3

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.
@@ -13,6 +13,7 @@ import {
13
13
  runIncrementalSync,
14
14
  runFullSync,
15
15
  getStarredSyncStatus,
16
+ syncListsManual,
16
17
  } from "../starred-sync.js";
17
18
 
18
19
  export const starredController = {
@@ -104,9 +105,11 @@ export const starredController = {
104
105
 
105
106
  // Run sync in background, respond immediately
106
107
  const syncFn = isFull ? runFullSync : runIncrementalSync;
107
- syncFn(db, config).catch((err) => {
108
- console.error("[GitHub Stars] Manual sync error:", err.message);
109
- });
108
+ syncFn(db, config)
109
+ .then(() => syncListsManual(db, config))
110
+ .catch((err) => {
111
+ console.error("[GitHub Stars] Manual sync error:", err.message);
112
+ });
110
113
 
111
114
  response.json({
112
115
  message: `${isFull ? "Full" : "Incremental"} sync started`,
@@ -39,13 +39,23 @@ const STARRED_QUERY = `
39
39
  const LISTS_QUERY = `
40
40
  query($cursor: String) {
41
41
  viewer {
42
- lists(first: 100, after: $cursor) {
42
+ lists(first: 5, after: $cursor) {
43
43
  totalCount
44
44
  nodes {
45
+ id
45
46
  name
46
47
  slug
47
48
  description
48
49
  isPrivate
50
+ items(first: 100) {
51
+ totalCount
52
+ nodes {
53
+ ... on Repository {
54
+ nameWithOwner
55
+ }
56
+ }
57
+ pageInfo { endCursor hasNextPage }
58
+ }
49
59
  }
50
60
  pageInfo { endCursor hasNextPage }
51
61
  }
@@ -54,9 +64,9 @@ const LISTS_QUERY = `
54
64
  `;
55
65
 
56
66
  const LIST_ITEMS_QUERY = `
57
- query($slug: String!, $cursor: String) {
58
- viewer {
59
- list(slug: $slug) {
67
+ query($id: ID!, $cursor: String) {
68
+ node(id: $id) {
69
+ ... on UserList {
60
70
  items(first: 100, after: $cursor) {
61
71
  nodes {
62
72
  ... on Repository {
@@ -174,11 +184,12 @@ export async function fetchAllLists(token) {
174
184
  throw new Error("GitHub token is required for GraphQL API");
175
185
  }
176
186
 
177
- // Stage 1: Fetch list metadata only (no items — avoids timeout)
178
- const listMeta = [];
187
+ const allLists = [];
179
188
  let cursor = null;
180
189
  let hasNextPage = true;
181
190
 
191
+ // Fetch lists in batches of 5 with inline items (first 100 per list)
192
+ // Small batches avoid GitHub GraphQL timeout on large accounts
182
193
  while (hasNextPage) {
183
194
  const response = await fetch(GITHUB_GRAPHQL_URL, {
184
195
  method: "POST",
@@ -206,10 +217,60 @@ export async function fetchAllLists(token) {
206
217
 
207
218
  for (const node of listsData.nodes) {
208
219
  if (node.isPrivate) continue;
209
- listMeta.push({
220
+
221
+ const repoFullNames = (node.items.nodes || [])
222
+ .map((n) => n.nameWithOwner)
223
+ .filter(Boolean);
224
+
225
+ // Paginate remaining items via node(id:) if list has >100 repos
226
+ if (node.items.pageInfo.hasNextPage) {
227
+ let itemsCursor = node.items.pageInfo.endCursor;
228
+ let itemsHasNext = true;
229
+
230
+ while (itemsHasNext) {
231
+ await new Promise((resolve) => setTimeout(resolve, 200));
232
+
233
+ const itemsResponse = await fetch(GITHUB_GRAPHQL_URL, {
234
+ method: "POST",
235
+ headers: {
236
+ Authorization: `Bearer ${token}`,
237
+ "Content-Type": "application/json",
238
+ },
239
+ body: JSON.stringify({
240
+ query: LIST_ITEMS_QUERY,
241
+ variables: { id: node.id, cursor: itemsCursor },
242
+ }),
243
+ });
244
+
245
+ if (!itemsResponse.ok) {
246
+ console.error(`[GitHub Lists] Failed to paginate items for list "${node.name}": ${itemsResponse.status}`);
247
+ break;
248
+ }
249
+
250
+ const itemsBody = await itemsResponse.json();
251
+
252
+ if (itemsBody.errors) {
253
+ console.error(`[GitHub Lists] GraphQL errors for list "${node.name}": ${itemsBody.errors.map((e) => e.message).join(", ")}`);
254
+ break;
255
+ }
256
+
257
+ const itemsData = itemsBody.data.node.items;
258
+ for (const itemNode of itemsData.nodes) {
259
+ if (itemNode.nameWithOwner) {
260
+ repoFullNames.push(itemNode.nameWithOwner);
261
+ }
262
+ }
263
+
264
+ itemsCursor = itemsData.pageInfo.endCursor;
265
+ itemsHasNext = itemsData.pageInfo.hasNextPage;
266
+ }
267
+ }
268
+
269
+ allLists.push({
210
270
  name: node.name,
211
271
  slug: node.slug,
212
272
  description: node.description || "",
273
+ repoFullNames,
213
274
  });
214
275
  }
215
276
 
@@ -221,60 +282,6 @@ export async function fetchAllLists(token) {
221
282
  }
222
283
  }
223
284
 
224
- // Stage 2: Fetch items per list individually (avoids timeout from bulk query)
225
- const allLists = [];
226
-
227
- for (const meta of listMeta) {
228
- const repoFullNames = [];
229
- let itemsCursor = null;
230
- let itemsHasNext = true;
231
-
232
- while (itemsHasNext) {
233
- await new Promise((resolve) => setTimeout(resolve, 200));
234
-
235
- const itemsResponse = await fetch(GITHUB_GRAPHQL_URL, {
236
- method: "POST",
237
- headers: {
238
- Authorization: `Bearer ${token}`,
239
- "Content-Type": "application/json",
240
- },
241
- body: JSON.stringify({
242
- query: LIST_ITEMS_QUERY,
243
- variables: { slug: meta.slug, cursor: itemsCursor },
244
- }),
245
- });
246
-
247
- if (!itemsResponse.ok) {
248
- console.error(`[GitHub Lists] Failed to fetch items for list "${meta.name}": ${itemsResponse.status}`);
249
- break;
250
- }
251
-
252
- const itemsBody = await itemsResponse.json();
253
-
254
- if (itemsBody.errors) {
255
- console.error(`[GitHub Lists] GraphQL errors for list "${meta.name}": ${itemsBody.errors.map((e) => e.message).join(", ")}`);
256
- break;
257
- }
258
-
259
- const itemsData = itemsBody.data.viewer.list.items;
260
- for (const itemNode of itemsData.nodes) {
261
- if (itemNode.nameWithOwner) {
262
- repoFullNames.push(itemNode.nameWithOwner);
263
- }
264
- }
265
-
266
- itemsCursor = itemsData.pageInfo.endCursor;
267
- itemsHasNext = itemsData.pageInfo.hasNextPage;
268
- }
269
-
270
- allLists.push({
271
- name: meta.name,
272
- slug: meta.slug,
273
- description: meta.description,
274
- repoFullNames,
275
- });
276
- }
277
-
278
285
  console.log(`[GitHub Lists] Fetched ${allLists.length} public lists`);
279
286
  return allLists;
280
287
  }
@@ -41,6 +41,10 @@ export function getStarredSyncStatus() {
41
41
  * @param {import("mongodb").Db} db
42
42
  * @param {object} options - Plugin options (needs token)
43
43
  */
44
+ export async function syncListsManual(db, options) {
45
+ return syncLists(db, options);
46
+ }
47
+
44
48
  async function syncLists(db, options) {
45
49
  try {
46
50
  const lists = await fetchAllLists(options.token);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-github",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "description": "GitHub activity endpoint for Indiekit. Display commits, stars, contributions, and featured repositories.",
5
5
  "keywords": [
6
6
  "indiekit",