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
119 lines
3.7 KiB
JavaScript
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}`);
|
|
});
|