108 lines
2.8 KiB
JavaScript
108 lines
2.8 KiB
JavaScript
const DEFAULT_OUTPUT = ".github/badges/ghcr-downloads.json";
|
|
const GH_API_BASE = "https://api.github.com";
|
|
|
|
const owner = process.env.GHCR_OWNER || process.env.GITHUB_REPOSITORY_OWNER;
|
|
const packageName = process.env.GHCR_PACKAGE || "charon";
|
|
const outputPath = process.env.BADGE_OUTPUT || DEFAULT_OUTPUT;
|
|
const token = process.env.GITHUB_TOKEN || "";
|
|
|
|
if (!owner) {
|
|
throw new Error("GHCR owner is required. Set GHCR_OWNER or GITHUB_REPOSITORY_OWNER.");
|
|
}
|
|
|
|
const headers = {
|
|
Accept: "application/vnd.github+json",
|
|
};
|
|
|
|
if (token) {
|
|
headers.Authorization = `Bearer ${token}`;
|
|
}
|
|
|
|
const formatCount = (value) => {
|
|
if (value >= 1_000_000_000) {
|
|
return `${(value / 1_000_000_000).toFixed(1).replace(/\.0$/, "")}B`;
|
|
}
|
|
if (value >= 1_000_000) {
|
|
return `${(value / 1_000_000).toFixed(1).replace(/\.0$/, "")}M`;
|
|
}
|
|
if (value >= 1_000) {
|
|
return `${(value / 1_000).toFixed(1).replace(/\.0$/, "")}k`;
|
|
}
|
|
return String(value);
|
|
};
|
|
|
|
const getNextLink = (linkHeader) => {
|
|
if (!linkHeader) {
|
|
return null;
|
|
}
|
|
const match = linkHeader.match(/<([^>]+)>;\s*rel="next"/);
|
|
return match ? match[1] : null;
|
|
};
|
|
|
|
const fetchPage = async (url) => {
|
|
const response = await fetch(url, { headers });
|
|
if (!response.ok) {
|
|
const detail = await response.text();
|
|
const error = new Error(`Request failed: ${response.status} ${response.statusText}`);
|
|
error.status = response.status;
|
|
error.detail = detail;
|
|
throw error;
|
|
}
|
|
const data = await response.json();
|
|
const link = response.headers.get("link");
|
|
return { data, next: getNextLink(link) };
|
|
};
|
|
|
|
const fetchAllVersions = async (baseUrl) => {
|
|
let url = `${baseUrl}?per_page=100`;
|
|
const versions = [];
|
|
|
|
while (url) {
|
|
const { data, next } = await fetchPage(url);
|
|
versions.push(...data);
|
|
url = next;
|
|
}
|
|
|
|
return versions;
|
|
};
|
|
|
|
const fetchVersionsWithFallback = async () => {
|
|
const userUrl = `${GH_API_BASE}/users/${owner}/packages/container/${packageName}/versions`;
|
|
try {
|
|
return await fetchAllVersions(userUrl);
|
|
} catch (error) {
|
|
if (error.status !== 404) {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
const orgUrl = `${GH_API_BASE}/orgs/${owner}/packages/container/${packageName}/versions`;
|
|
return fetchAllVersions(orgUrl);
|
|
};
|
|
|
|
const run = async () => {
|
|
const versions = await fetchVersionsWithFallback();
|
|
const totalDownloads = versions.reduce(
|
|
(sum, version) => sum + (version.download_count || 0),
|
|
0
|
|
);
|
|
|
|
const badge = {
|
|
schemaVersion: 1,
|
|
label: "GHCR pulls",
|
|
message: formatCount(totalDownloads),
|
|
color: "blue",
|
|
cacheSeconds: 3600,
|
|
};
|
|
|
|
const output = `${JSON.stringify(badge, null, 2)}\n`;
|
|
await import("node:fs/promises").then((fs) => fs.writeFile(outputPath, output));
|
|
|
|
console.log(`GHCR downloads: ${totalDownloads} -> ${outputPath}`);
|
|
};
|
|
|
|
run().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|