mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
186 lines
6.3 KiB
YAML
186 lines
6.3 KiB
YAML
name: Monthly Metrics Snapshot
|
|
|
|
on:
|
|
schedule:
|
|
- cron: '0 14 1 * *' # Monthly on the 1st at 14:00 UTC
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
issues: write
|
|
|
|
jobs:
|
|
snapshot:
|
|
name: Update metrics issue
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Update monthly metrics issue
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const owner = context.repo.owner;
|
|
const repo = context.repo.repo;
|
|
const title = "Monthly Metrics Snapshot";
|
|
const label = "metrics-snapshot";
|
|
const monthKey = new Date().toISOString().slice(0, 7);
|
|
|
|
function parseLastPage(linkHeader) {
|
|
if (!linkHeader) return null;
|
|
const match = linkHeader.match(/&page=(\d+)>; rel="last"/);
|
|
return match ? Number(match[1]) : null;
|
|
}
|
|
|
|
function fmt(value) {
|
|
if (value === null || value === undefined) return "n/a";
|
|
return Number(value).toLocaleString("en-US");
|
|
}
|
|
|
|
async function getNpmDownloads(range, pkg) {
|
|
try {
|
|
const res = await fetch(`https://api.npmjs.org/downloads/point/${range}/${pkg}`);
|
|
if (!res.ok) return null;
|
|
const data = await res.json();
|
|
return data.downloads ?? null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function getContributorsCount() {
|
|
try {
|
|
const resp = await github.rest.repos.listContributors({
|
|
owner,
|
|
repo,
|
|
per_page: 1,
|
|
anon: "false"
|
|
});
|
|
return parseLastPage(resp.headers.link) ?? resp.data.length;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function getReleasesCount() {
|
|
try {
|
|
const resp = await github.rest.repos.listReleases({
|
|
owner,
|
|
repo,
|
|
per_page: 1
|
|
});
|
|
return parseLastPage(resp.headers.link) ?? resp.data.length;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function getTraffic(metric) {
|
|
try {
|
|
const route = metric === "clones"
|
|
? "GET /repos/{owner}/{repo}/traffic/clones"
|
|
: "GET /repos/{owner}/{repo}/traffic/views";
|
|
const resp = await github.request(route, { owner, repo });
|
|
return resp.data?.count ?? null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const [
|
|
mainWeek,
|
|
shieldWeek,
|
|
mainMonth,
|
|
shieldMonth,
|
|
repoData,
|
|
contributors,
|
|
releases,
|
|
views14d,
|
|
clones14d
|
|
] = await Promise.all([
|
|
getNpmDownloads("last-week", "ecc-universal"),
|
|
getNpmDownloads("last-week", "ecc-agentshield"),
|
|
getNpmDownloads("last-month", "ecc-universal"),
|
|
getNpmDownloads("last-month", "ecc-agentshield"),
|
|
github.rest.repos.get({ owner, repo }),
|
|
getContributorsCount(),
|
|
getReleasesCount(),
|
|
getTraffic("views"),
|
|
getTraffic("clones")
|
|
]);
|
|
|
|
const stars = repoData.data.stargazers_count;
|
|
const forks = repoData.data.forks_count;
|
|
|
|
const tableHeader = [
|
|
"| Month (UTC) | ecc-universal (week) | ecc-agentshield (week) | ecc-universal (30d) | ecc-agentshield (30d) | Stars | Forks | Contributors | GitHub App installs (manual) | Views (14d) | Clones (14d) | Releases |",
|
|
"|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:|"
|
|
].join("\n");
|
|
|
|
const row = `| ${monthKey} | ${fmt(mainWeek)} | ${fmt(shieldWeek)} | ${fmt(mainMonth)} | ${fmt(shieldMonth)} | ${fmt(stars)} | ${fmt(forks)} | ${fmt(contributors)} | n/a | ${fmt(views14d)} | ${fmt(clones14d)} | ${fmt(releases)} |`;
|
|
|
|
const intro = [
|
|
"# Monthly Metrics Snapshot",
|
|
"",
|
|
"Automated monthly snapshot for sponsor/partner reporting.",
|
|
"",
|
|
"- `GitHub App installs (manual)` is intentionally manual until a stable public API path is available.",
|
|
"- Traffic metrics are 14-day rolling windows from the GitHub traffic API and can show `n/a` if unavailable.",
|
|
"",
|
|
tableHeader
|
|
].join("\n");
|
|
|
|
try {
|
|
await github.rest.issues.getLabel({ owner, repo, name: label });
|
|
} catch (error) {
|
|
if (error.status === 404) {
|
|
await github.rest.issues.createLabel({
|
|
owner,
|
|
repo,
|
|
name: label,
|
|
color: "0e8a16",
|
|
description: "Automated monthly project metrics snapshots"
|
|
});
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
const issuesResp = await github.rest.issues.listForRepo({
|
|
owner,
|
|
repo,
|
|
state: "open",
|
|
labels: label,
|
|
per_page: 100
|
|
});
|
|
|
|
let issue = issuesResp.data.find((item) => item.title === title);
|
|
|
|
if (!issue) {
|
|
const created = await github.rest.issues.create({
|
|
owner,
|
|
repo,
|
|
title,
|
|
labels: [label],
|
|
body: `${intro}\n${row}\n`
|
|
});
|
|
console.log(`Created issue #${created.data.number}`);
|
|
return;
|
|
}
|
|
|
|
const currentBody = issue.body || "";
|
|
if (currentBody.includes(`| ${monthKey} |`)) {
|
|
console.log(`Issue #${issue.number} already has snapshot row for ${monthKey}`);
|
|
return;
|
|
}
|
|
|
|
const body = currentBody.includes("| Month (UTC) |")
|
|
? `${currentBody.trimEnd()}\n${row}\n`
|
|
: `${intro}\n${row}\n`;
|
|
|
|
await github.rest.issues.update({
|
|
owner,
|
|
repo,
|
|
issue_number: issue.number,
|
|
body
|
|
});
|
|
console.log(`Updated issue #${issue.number}`);
|