mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +08:00
docs: add sponsorship playbook and monthly metrics automation
This commit is contained in:
185
.github/workflows/monthly-metrics.yml
vendored
Normal file
185
.github/workflows/monthly-metrics.yml
vendored
Normal file
@@ -0,0 +1,185 @@
|
||||
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}`);
|
||||
Reference in New Issue
Block a user