chore: add scheduled download stats reporting (#230)

This commit is contained in:
ben
2026-01-23 22:42:32 -08:00
committed by GitHub
parent 904549a329
commit c3873be0f2
3 changed files with 223 additions and 0 deletions

42
.github/workflows/download-stats.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Download Stats
on:
schedule:
- cron: "0 12 * * *" # Run daily at 12:00 UTC
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
stats:
if: github.repository == 'different-ai/openwork'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Run stats script
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
POSTHOG_KEY: ${{ secrets.POSTHOG_KEY }}
POSTHOG_HOST: https://us.i.posthog.com
POSTHOG_EVENT: download
POSTHOG_DISTINCT_ID: openwork-download
GITHUB_REPO: different-ai/openwork
run: node scripts/stats.mjs
- name: Commit stats
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add STATS.md
git diff --staged --quiet || git commit -m "ignore: update download stats $(date -I)"
git push

4
STATS.md Normal file
View File

@@ -0,0 +1,4 @@
# Download Stats
| Date | GitHub Downloads | Total |
|------|------------------|-------|

177
scripts/stats.mjs Normal file
View File

@@ -0,0 +1,177 @@
#!/usr/bin/env node
const POSTHOG_KEY = process.env.POSTHOG_KEY || process.env.POSTHOG_API_KEY
const POSTHOG_HOST = process.env.POSTHOG_HOST || "https://us.i.posthog.com"
const POSTHOG_EVENT = process.env.POSTHOG_EVENT || "download"
const POSTHOG_DISTINCT_ID = process.env.POSTHOG_DISTINCT_ID || "openwork-download"
const GITHUB_REPO = process.env.GITHUB_REPO || "different-ai/openwork"
const STATS_FILE = process.env.STATS_FILE || "STATS.md"
async function sendToPostHog(event, properties) {
if (!POSTHOG_KEY) {
console.warn("POSTHOG_KEY not set, skipping PostHog event")
return
}
const response = await fetch(`${POSTHOG_HOST}/i/v0/e/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
distinct_id: POSTHOG_DISTINCT_ID,
api_key: POSTHOG_KEY,
event,
properties,
}),
}).catch(() => null)
if (response && !response.ok) {
console.warn(`PostHog API error: ${response.status}`)
}
}
async function fetchReleases() {
const releases = []
let page = 1
const perPage = 100
const headers = {
Accept: "application/vnd.github+json",
"User-Agent": "openwork-download-stats",
}
const token = process.env.GITHUB_TOKEN
if (token) {
headers.Authorization = `Bearer ${token}`
}
while (true) {
const url = `https://api.github.com/repos/${GITHUB_REPO}/releases?page=${page}&per_page=${perPage}`
const response = await fetch(url, { headers })
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`)
}
const batch = await response.json()
if (!Array.isArray(batch) || batch.length === 0) break
releases.push(...batch)
console.log(`Fetched page ${page} with ${batch.length} releases`)
if (batch.length < perPage) break
page += 1
await new Promise((resolve) => setTimeout(resolve, 1000))
}
return releases
}
function calculate(releases) {
let total = 0
const stats = []
for (const release of releases) {
let downloads = 0
const assets = []
for (const asset of release.assets ?? []) {
downloads += asset.download_count
assets.push({
name: asset.name,
downloads: asset.download_count,
})
}
total += downloads
stats.push({
tag: release.tag_name,
name: release.name,
downloads,
assets,
})
}
return { total, stats }
}
async function save(githubTotal) {
const date = new Date().toISOString().split("T")[0]
const total = githubTotal
let previousGithub = 0
let previousTotal = 0
let content = ""
try {
const file = await import("node:fs/promises")
content = await file.readFile(STATS_FILE, "utf8")
const lines = content.trim().split("\n")
for (let i = lines.length - 1; i >= 0; i -= 1) {
const line = lines[i].trim()
if (line.startsWith("|") && !line.includes("Date") && !line.includes("---")) {
const match = line.match(
/\|\s*[\d-]+\s*\|\s*([\d,]+)\s*(?:\([^)]*\))?\s*\|\s*([\d,]+)\s*(?:\([^)]*\))?\s*\|/,
)
if (match) {
previousGithub = parseInt(match[1].replace(/,/g, ""), 10)
previousTotal = parseInt(match[2].replace(/,/g, ""), 10)
break
}
}
}
} catch {
content =
"# Download Stats\n\n| Date | GitHub Downloads | Total |\n|------|------------------|-------|\n"
}
const githubChange = githubTotal - previousGithub
const totalChange = total - previousTotal
const githubChangeStr =
githubChange > 0
? ` (+${githubChange.toLocaleString()})`
: githubChange < 0
? ` (${githubChange.toLocaleString()})`
: " (+0)"
const totalChangeStr =
totalChange > 0
? ` (+${totalChange.toLocaleString()})`
: totalChange < 0
? ` (${totalChange.toLocaleString()})`
: " (+0)"
const line = `| ${date} | ${githubTotal.toLocaleString()}${githubChangeStr} | ${total.toLocaleString()}${totalChangeStr} |\n`
if (!content.includes("# Download Stats")) {
content =
"# Download Stats\n\n| Date | GitHub Downloads | Total |\n|------|------------------|-------|\n"
}
const file = await import("node:fs/promises")
await file.writeFile(STATS_FILE, content + line, "utf8")
console.log(
`\nAppended stats to ${STATS_FILE}: GitHub ${githubTotal.toLocaleString()}${githubChangeStr}, Total ${total.toLocaleString()}${totalChangeStr}`,
)
}
console.log(`Fetching GitHub releases for ${GITHUB_REPO}...\n`)
const releases = await fetchReleases()
console.log(`\nFetched ${releases.length} releases total\n`)
const { total: githubTotal } = calculate(releases)
await save(githubTotal)
await sendToPostHog(POSTHOG_EVENT, {
count: githubTotal,
source: "github",
repo: GITHUB_REPO,
})
console.log("=".repeat(60))
console.log(`TOTAL DOWNLOADS: ${githubTotal.toLocaleString()}`)
console.log(` GitHub: ${githubTotal.toLocaleString()}`)
console.log("=".repeat(60))