@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.
- package/lib/controllers/starred.js +6 -3
- package/lib/github-graphql.js +68 -61
- package/lib/starred-sync.js +4 -0
- package/package.json +1 -1
|
@@ -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)
|
|
108
|
-
|
|
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`,
|
package/lib/github-graphql.js
CHANGED
|
@@ -39,13 +39,23 @@ const STARRED_QUERY = `
|
|
|
39
39
|
const LISTS_QUERY = `
|
|
40
40
|
query($cursor: String) {
|
|
41
41
|
viewer {
|
|
42
|
-
lists(first:
|
|
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($
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/lib/starred-sync.js
CHANGED
|
@@ -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