Files
wehub-resource-sync 0d3cb498a3
Deploy local.promptfoo.app / Deploy to Cloudflare Pages (push) Waiting to run
Test and Publish Multi-arch Docker Image / test (push) Waiting to run
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-amd64 platform:linux/amd64 runner:ubuntu-latest]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / build-docker-and-push-digests (map[digest-suffix:linux-arm64 platform:linux/arm64 runner:ubuntu-24.04-arm]) (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / merge-docker-digests (push) Blocked by required conditions
Test and Publish Multi-arch Docker Image / Attest Multi-arch Image (push) Blocked by required conditions
CI / Shell Format Check (push) Waiting to run
CI / Check Ruby (3.4) (push) Waiting to run
CI / CI Config (push) Waiting to run
CI / Test on Node ${{ matrix.node }} and ${{ matrix.os }}${{ matrix.shard && format(' (shard {0}/3)', matrix.shard) || '' }} (push) Blocked by required conditions
CI / Build on Node ${{ matrix.node }} (push) Blocked by required conditions
CI / Style Check (push) Waiting to run
CI / Generate Assets (push) Waiting to run
CI / Check Python (3.14) (push) Waiting to run
CI / Check Python (3.9) (push) Waiting to run
CI / Build Docs (push) Waiting to run
CI / Code Scan Action (push) Waiting to run
CI / Site tests (push) Waiting to run
CI / webui tests (push) Waiting to run
CI / Run Integration Tests (push) Waiting to run
CI / Run Smoke Tests (push) Waiting to run
CI / Go Tests (push) Waiting to run
CI / Share Test (push) Waiting to run
CI / Redteam (Production API) (push) Waiting to run
CI / Redteam (Staging API) (push) Waiting to run
CI / GitHub Actions Lint (push) Waiting to run
CI / Check Ruby (3.0) (push) Waiting to run
release-please / release-please (push) Waiting to run
release-please / build (push) Blocked by required conditions
release-please / publish-npm (push) Blocked by required conditions
release-please / publish-npm-backfill (push) Waiting to run
release-please / docker (push) Blocked by required conditions
release-please / publish-code-scan-action (push) Blocked by required conditions
release-please / attest-code-scan-action (push) Blocked by required conditions
Validate Renovate Config / Validate Renovate Configuration (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:24:08 +08:00

119 lines
3.7 KiB
JavaScript

/**
* Pre-build script that fetches live site statistics from public APIs.
* Writes results to site/src/.generated-stats.json, which is merged over
* the static fallbacks in site-stats.json by constants.ts.
*
* Always exits 0 — build never fails due to stats fetching.
*/
import { writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const OUTPUT_PATH = join(__dirname, '..', 'src', '.generated-stats.json');
const TIMEOUT_MS = 10_000;
const REPO = 'promptfoo/promptfoo';
const NPM_PACKAGE = 'promptfoo';
function formatCompact(num) {
if (num >= 1000) {
const k = num / 1000;
return k % 1 === 0 ? `${k}k` : `${k.toFixed(1)}k`;
}
return String(num);
}
function formatWithCommas(num) {
return num.toLocaleString('en-US');
}
function roundToNearest(num, nearest) {
return Math.round(num / nearest) * nearest;
}
async function fetchWithTimeout(url, options = {}) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
try {
const headers = { 'User-Agent': 'promptfoo-site-build', ...options.headers };
if (process.env.GITHUB_TOKEN) {
const { hostname } = new URL(url);
if (hostname === 'api.github.com' || hostname === 'github.com') {
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
}
}
const res = await fetch(url, { ...options, headers, signal: controller.signal });
if (!res.ok) {
throw new Error(`HTTP ${res.status} ${res.statusText}`);
}
return res;
} finally {
clearTimeout(timer);
}
}
async function fetchGitHubStars() {
const res = await fetchWithTimeout(`https://api.github.com/repos/${REPO}`);
const data = await res.json();
return { GITHUB_STARS_DISPLAY: formatCompact(data.stargazers_count) };
}
async function fetchContributorCount() {
const res = await fetchWithTimeout(
`https://api.github.com/repos/${REPO}/contributors?per_page=1&anon=true`,
);
const linkHeader = res.headers.get('link');
if (!linkHeader) {
// If no Link header, there's only one page — count the response
const data = await res.json();
return { CONTRIBUTOR_COUNT: data.length };
}
const match = linkHeader.match(/page=(\d+)>;\s*rel="last"/);
if (!match) {
throw new Error('Could not parse contributor count from Link header');
}
return { CONTRIBUTOR_COUNT: Number(match[1]) };
}
async function fetchNpmDownloads() {
const res = await fetchWithTimeout(
`https://api.npmjs.org/downloads/point/last-week/${NPM_PACKAGE}`,
);
const data = await res.json();
const rounded = roundToNearest(data.downloads, 1000);
return { WEEKLY_DOWNLOADS_DISPLAY: formatWithCommas(rounded) };
}
async function main() {
const fetchers = [
{ name: 'GitHub stars', fn: fetchGitHubStars },
{ name: 'contributor count', fn: fetchContributorCount },
{ name: 'npm downloads', fn: fetchNpmDownloads },
];
const results = await Promise.allSettled(fetchers.map((f) => f.fn()));
const stats = {};
results.forEach((result, i) => {
if (result.status === 'fulfilled') {
Object.assign(stats, result.value);
} else {
console.warn(`[fetch-stats] Failed to fetch ${fetchers[i].name}: ${result.reason}`);
}
});
await writeFile(OUTPUT_PATH, JSON.stringify(stats, null, 2) + '\n');
const keys = Object.keys(stats);
if (keys.length > 0) {
console.log(`[fetch-stats] Wrote ${keys.length} stats: ${keys.join(', ')}`);
} else {
console.log('[fetch-stats] No stats fetched; fallback values will be used');
}
}
main().catch((err) => {
console.warn(`[fetch-stats] Unexpected error: ${err.message}`);
});