From f47d57121c0a69f285d60228b0344c0cb0c5642e Mon Sep 17 00:00:00 2001 From: Ali Sher Date: Tue, 18 Aug 2026 14:23:55 +0500 Subject: [PATCH] chore(metrics): weekly launch-metrics snapshot (stars, downloads, contributors, action repo) --- METRICS.md | 10 ++++ eslint.config.mjs | 1 + package.json | 1 + scripts/metrics.mjs | 117 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+) create mode 100644 METRICS.md create mode 100644 scripts/metrics.mjs diff --git a/METRICS.md b/METRICS.md new file mode 100644 index 0000000..165d0f0 --- /dev/null +++ b/METRICS.md @@ -0,0 +1,10 @@ +# Launch metrics + +Tracked weekly with `pnpm run metrics`. Targets: **1K stars**, **10K weekly downloads**, +**100 action installs** (proxied by the action repo's stars/forks), **5 contributors**, +Product Hunt top-5, HN front page. + +| Date | Stars | Forks | Contributors | Open issues | npm weekly downloads | Action stars | Action forks | Notes | +| ---- | ----- | ----- | ------------ | ----------- | -------------------- | ------------ | ------------ | ----- | + +| 2026-08-18 | 1 | 1 | 2 | 3 | 254 | 0 | 0 | baseline — v0.7.0 released | diff --git a/eslint.config.mjs b/eslint.config.mjs index 3c40fd4..e5c22af 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -23,6 +23,7 @@ export default tseslint.config( process: "readonly", console: "readonly", performance: "readonly", + fetch: "readonly", }, }, }, diff --git a/package.json b/package.json index 2ad1685..e974719 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "lint:fix": "eslint . --fix", "format": "prettier --write .", "format:check": "prettier --check .", + "metrics": "node scripts/metrics.mjs", "prepublishOnly": "pnpm run typecheck && pnpm run lint && pnpm run test && pnpm run build" }, "dependencies": { diff --git a/scripts/metrics.mjs b/scripts/metrics.mjs new file mode 100644 index 0000000..4bbca17 --- /dev/null +++ b/scripts/metrics.mjs @@ -0,0 +1,117 @@ +#!/usr/bin/env node +/** + * Weekly launch-metrics snapshot for Ripple. + * + * Fetches GitHub repo stats (via `gh`), npm weekly downloads, and the + * companion action repo's popularity, then appends one row per run to + * METRICS.md (rerunning on the same day updates that day's row). + * + * node scripts/metrics.mjs # snapshot and write METRICS.md + * node scripts/metrics.mjs --json # print raw numbers, skip the file write + * + * Requires the GitHub CLI to be authenticated. Targets are tracked in + * METRICS.md: 1K stars, 10K weekly downloads, 100 action installs, + * 5 contributors, Product Hunt top-5, HN front page. + */ +import { execFileSync } from "node:child_process"; +import { existsSync, readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const repoRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url))); +const metricsFile = path.join(repoRoot, "METRICS.md"); +const PACKAGE = "@alimaandev/ripple"; +const REPO = "alimaandev/ripple"; +const ACTION_REPO = "alimaandev/ripple-action"; + +function gh(args) { + return JSON.parse( + execFileSync("gh", ["api", ...args], { + encoding: "utf8", + stdio: ["ignore", "pipe", "ignore"], + }), + ); +} + +async function snapshot() { + const repo = gh([ + "--jq", + "{stars:.stargazers_count,forks:.forks_count,openIssues:.open_issues_count}", + `repos/${REPO}`, + ]); + const contributors = gh([ + "--paginate", + "--jq", + "[.[].login] | length", + `repos/${REPO}/contributors?per_page=100&anon=false`, + ]); + const openIssues = gh([ + "--paginate", + "--jq", + "[.[] | select(.pull_request == null)] | length", + `repos/${REPO}/issues?state=open&per_page=100`, + ]); + const action = gh([ + "--jq", + "{stars:.stargazers_count,forks:.forks_count}", + `repos/${ACTION_REPO}`, + ]); + const downloads = await fetch(`https://api.npmjs.org/downloads/point/last-week/${PACKAGE}`) + .then((r) => r.json()) + .then((d) => d.downloads ?? 0); + + return { + date: new Date().toISOString().slice(0, 10), + stars: repo.stars, + forks: repo.forks, + contributors, + openIssues, + downloads, + actionStars: action.stars, + actionForks: action.forks, + }; +} + +const HEADER = `# Launch metrics + +Tracked weekly with \`pnpm run metrics\`. Targets: **1K stars**, **10K weekly downloads**, +**100 action installs** (proxied by the action repo's stars/forks), **5 contributors**, +Product Hunt top-5, HN front page. + +| Date | Stars | Forks | Contributors | Open issues | npm weekly downloads | Action stars | Action forks | Notes | +|---|---|---|---|---|---|---|---|---| +`; + +function writeMetrics(entry, note = "") { + let rows = []; + if (existsSync(metricsFile)) { + rows = readFileSync(metricsFile, "utf8") + .split("\n") + .filter((l) => /^\| \d{4}-\d{2}-\d{2} \|/.test(l)); + } + const existing = rows.findIndex((l) => l.startsWith(`| ${entry.date} |`)); + const row = `| ${entry.date} | ${entry.stars} | ${entry.forks} | ${entry.contributors} | ${entry.openIssues} | ${entry.downloads} | ${entry.actionStars} | ${entry.actionForks} | ${note} |`; + if (existing >= 0) rows[existing] = row; + else rows.push(row); + writeFileSync(metricsFile, `${HEADER}\n${rows.join("\n")}\n`); +} + +const entry = await snapshot(); + +if (process.argv.includes("--json")) { + console.log(JSON.stringify(entry, null, 2)); +} else { + writeMetrics( + entry, + process.argv.includes("--note") ? (process.argv[process.argv.indexOf("--note") + 1] ?? "") : "", + ); + console.log(`\n date ${entry.date}`); + console.log(` stars ${entry.stars}`); + console.log(` forks ${entry.forks}`); + console.log(` contributors ${entry.contributors}`); + console.log(` open issues ${entry.openIssues}`); + console.log(` weekly dl ${entry.downloads}`); + console.log(` action stars ${entry.actionStars}`); + console.log(` action forks ${entry.actionForks}`); + console.log(`\n wrote ${path.relative(repoRoot, metricsFile)}\n`); +}