Skip to content

Commit b3c8bd2

Browse files
fix: only flag explicit GitHub zero-watchers
1 parent 45bab59 commit b3c8bd2

5 files changed

Lines changed: 84 additions & 3 deletions

File tree

scripts/check-modules/__tests__/result-markdown.test.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ describe("result-markdown", () => {
2626
maintainer: "Dana",
2727
url: "https://github.com/example/MMM-Quiet",
2828
watchersCount: 0
29+
},
30+
{
31+
name: "MMM-NotGitHub",
32+
maintainer: "Erin",
33+
url: "https://gitlab.com/example/MMM-NotGitHub",
34+
watchersCount: 0
2935
}
3036
]);
3137

scripts/check-modules/result-markdown.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ interface ProcessedModuleLike {
2222
url?: string;
2323
}
2424

25+
function isGitHubUrl(url: unknown): boolean {
26+
if (typeof url !== "string" || url.length === 0) {
27+
return false;
28+
}
29+
30+
try {
31+
const host = new URL(url).hostname.toLowerCase();
32+
return host === "github.com" || host === "www.github.com";
33+
}
34+
catch {
35+
return false;
36+
}
37+
}
38+
2539
export function normalizeIssuesInput(issues: string[] | string | boolean | null | undefined): string[] {
2640
if (Array.isArray(issues)) {
2741
return issues.slice();
@@ -42,7 +56,7 @@ export function collectIssueSummaries(modules: unknown[]): IssueSummary[] {
4256

4357
const stageModule = module as ProcessedModuleLike;
4458
const issues = normalizeIssuesInput(stageModule.issues);
45-
const watcherIssue = stageModule.watchersCount === 0
59+
const watcherIssue = isGitHubUrl(stageModule.url) && stageModule.watchersCount === 0
4660
? "GitHub reports 0 watchers; maintainer notifications may be missed."
4761
: null;
4862

scripts/collect-metadata/index.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,31 @@ async function fetchIndividualModule(module: EnrichedModule, repoType: string, c
311311
}
312312
}
313313

314+
/**
315+
* Public fallback for unauthenticated GitHub API rate limits.
316+
* GitHub's commits Atom feed is typically still reachable when REST API returns 403.
317+
*/
318+
async function fetchGitHubLastCommitFromAtom(moduleUrl: string, client: HttpClient): Promise<string | null> {
319+
const repoId = getRepositoryId(moduleUrl);
320+
if (!repoId) {
321+
return null;
322+
}
323+
324+
try {
325+
const atomUrl = `https://github.com/${repoId}/commits.atom`;
326+
const result = await client.getText(atomUrl) as FetchTextResult;
327+
if (!result.ok) {
328+
return null;
329+
}
330+
331+
const updatedMatch = result.data.match(/<updated>([^<]+)<\/updated>/u);
332+
return updatedMatch?.[1] ?? null;
333+
}
334+
catch {
335+
return null;
336+
}
337+
}
338+
314339
async function appendGitHubBatchResult({
315340
module,
316341
batchResults,
@@ -430,6 +455,25 @@ async function processModule(module: EnrichedModule, context: ProcessModuleConte
430455
};
431456
}
432457

458+
if (repoType === "github" && !process.env.GITHUB_TOKEN && recovery.error.message.includes("403")) {
459+
const lastCommitFromAtom = await fetchGitHubLastCommitFromAtom(module.url, client);
460+
if (lastCommitFromAtom) {
461+
const atomRecovery: CachedRepositoryValue = {
462+
lastCommit: lastCommitFromAtom,
463+
isArchived: typeof module.isArchived === "boolean" ? module.isArchived : undefined,
464+
hasGithubIssues: typeof module.hasGithubIssues === "boolean" ? module.hasGithubIssues : undefined
465+
};
466+
467+
repositoryCache.set(repoId, atomRecovery);
468+
logger.info(`Recovered ${module.name} lastCommit via GitHub Atom fallback`);
469+
470+
return {
471+
...module,
472+
...atomRecovery
473+
};
474+
}
475+
}
476+
433477
logger.warn(`Failed to fetch metadata for ${module.name}`, { url: module.url, error: recovery.error.message });
434478
circuitBreaker.recordError(recovery.error);
435479

scripts/updateRepositoryApiData/__tests__/api.test.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,15 @@ describe("updateRepositoryApiData/api", () => {
2828

2929
assert.strictEqual(normalized.watchersCount, 7);
3030
});
31+
32+
it("does not infer watchersCount when subscribers_count is missing", () => {
33+
const normalized = normalizeRepositoryData({
34+
stargazers_count: 12,
35+
has_issues: true,
36+
archived: false,
37+
defaultBranchRef: { target: { committedDate: "2026-01-01T00:00:00.000Z" } }
38+
}, null, "github");
39+
40+
assert.strictEqual(typeof normalized.watchersCount, "undefined");
41+
});
3142
});

scripts/updateRepositoryApiData/api.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ interface RepositoryApiData {
3737
licenses?: Array<string | null>;
3838
mainbranch?: { name?: string | null } | null;
3939
pushedAt?: string | null;
40+
pushed_at?: string | null;
4041
star_count?: number;
4142
stargazerCount?: number;
4243
stargazers_count?: number;
@@ -180,8 +181,13 @@ export function normalizeRepositoryData(data: RepositoryApiData, branchData: Rep
180181
stars = data.stargazers_count ?? data.stargazerCount ?? 0;
181182
license = data.license?.spdx_id ?? data.licenseInfo?.spdxId ?? null;
182183
hasGithubIssues = data.has_issues ?? data.hasIssuesEnabled ?? false;
183-
lastCommit = branchData?.commit?.author?.date ?? data.defaultBranchRef?.target?.committedDate ?? data.pushedAt ?? null;
184-
watchersCount = data.subscribers_count ?? 0;
184+
lastCommit = branchData?.commit?.author?.date
185+
?? data.defaultBranchRef?.target?.committedDate
186+
?? data.pushed_at
187+
?? data.pushedAt
188+
?? null;
189+
// Only trust an explicit API value. GraphQL batch payloads do not provide subscribers_count.
190+
watchersCount = typeof data.subscribers_count === "number" ? data.subscribers_count : undefined;
185191
break;
186192
case "gitlab":
187193
stars = data.star_count ?? 0;

0 commit comments

Comments
 (0)