-
Notifications
You must be signed in to change notification settings - Fork 179
docs: refresh home stats for oxlint, vite, and vitest Issue: #1511 #1512
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ed939b2
23b81b2
b1ef0af
55163f2
93be819
29b39bb
a453a70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this script live under |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| /** | ||
| * Fetches last-week npm download counts and GitHub star counts, then writes | ||
| * docs/.vitepress/theme/data/trusted-stack-stats.json for the docs home page. | ||
| * | ||
| * Requires Node.js >=22.18 (strip types). Run: | ||
| * `pnpm -C docs update-trusted-stack-stats` | ||
| * or: `node .github/scripts/fetch-trusted-stack-stats.ts` | ||
| */ | ||
| import { writeFile } from 'node:fs/promises'; | ||
| import { dirname, join } from 'node:path'; | ||
| import { fileURLToPath } from 'node:url'; | ||
|
|
||
| import type { | ||
| TrustedStackProjectId, | ||
| TrustedStackStatProject, | ||
| TrustedStackStatsFile, | ||
| } from '../../docs/.vitepress/theme/data/trusted-stack-stats.types.ts'; | ||
|
|
||
| const __dirname = dirname(fileURLToPath(import.meta.url)); | ||
| const OUT = join(__dirname, '../../docs/.vitepress/theme/data/trusted-stack-stats.json'); | ||
|
|
||
| interface ProjectSource { | ||
| readonly id: TrustedStackProjectId; | ||
| readonly npmPackage: string; | ||
| readonly githubRepo: string; | ||
| } | ||
|
|
||
| const PROJECTS: readonly ProjectSource[] = [ | ||
| { id: 'vite', npmPackage: 'vite', githubRepo: 'vitejs/vite' }, | ||
| { id: 'vitest', npmPackage: 'vitest', githubRepo: 'vitest-dev/vitest' }, | ||
| /** OXC row uses `oxlint` npm weekly downloads as a concrete proxy for the Oxc toolchain. */ | ||
| { id: 'oxc', npmPackage: 'oxlint', githubRepo: 'oxc-project/oxc' }, | ||
| ]; | ||
|
|
||
| function formatWeeklyDownloads(n: number): string { | ||
| if (n >= 10_000_000) { | ||
| return `${Math.round(n / 1e6)}m+`; | ||
| } | ||
| const m = n / 1e6; | ||
| const s = m.toFixed(1).replace(/\.0$/, ''); | ||
| return `${s}m+`; | ||
| } | ||
|
|
||
| function formatStars(s: number): string { | ||
| return `${(s / 1000).toFixed(1)}k`; | ||
| } | ||
|
|
||
| function parseNpmDownloadsJson(data: unknown, pkg: string): number { | ||
| if (typeof data !== 'object' || data === null || !('downloads' in data)) { | ||
| throw new Error(`npm API ${pkg}: unexpected payload`); | ||
| } | ||
| const downloads = (data as { downloads: unknown }).downloads; | ||
| if (typeof downloads !== 'number') { | ||
| throw new Error(`npm API ${pkg}: unexpected payload`); | ||
| } | ||
| return downloads; | ||
| } | ||
|
|
||
| async function npmLastWeekDownloads(pkg: string): Promise<number> { | ||
| const url = `https://api.npmjs.org/downloads/point/last-week/${encodeURIComponent(pkg)}`; | ||
| const res = await fetch(url); | ||
| if (!res.ok) { | ||
| const body = await res.text(); | ||
| throw new Error(`npm API ${pkg}: HTTP ${res.status} ${body}`); | ||
| } | ||
| return parseNpmDownloadsJson(await res.json(), pkg); | ||
| } | ||
|
|
||
| function parseGithubRepoJson(data: unknown, repo: string): number { | ||
| if (typeof data !== 'object' || data === null || !('stargazers_count' in data)) { | ||
| throw new Error(`GitHub API ${repo}: unexpected payload`); | ||
| } | ||
| const count = (data as { stargazers_count: unknown }).stargazers_count; | ||
| if (typeof count !== 'number') { | ||
| throw new Error(`GitHub API ${repo}: unexpected payload`); | ||
| } | ||
| return count; | ||
| } | ||
|
|
||
| async function fetchGithubStargazers(repo: string): Promise<number> { | ||
| const url = `https://api.github.com/repos/${repo}`; | ||
| const headers: Record<string, string> = { | ||
| Accept: 'application/vnd.github+json', | ||
| 'X-GitHub-Api-Version': '2022-11-28', | ||
| 'User-Agent': 'voidzero-dev/vite-plus (.github/scripts/fetch-trusted-stack-stats.ts)', | ||
| }; | ||
| const token = process.env.GITHUB_TOKEN; | ||
| if (token !== undefined && token !== '') { | ||
| headers.Authorization = `Bearer ${token}`; | ||
| } | ||
| const res = await fetch(url, { headers }); | ||
| if (!res.ok) { | ||
| const body = await res.text(); | ||
| throw new Error(`GitHub API ${repo}: HTTP ${res.status} ${body}`); | ||
| } | ||
| return parseGithubRepoJson(await res.json(), repo); | ||
| } | ||
|
|
||
| async function main(): Promise<void> { | ||
| const projects: TrustedStackStatProject[] = []; | ||
| for (const p of PROJECTS) { | ||
| const [npmWeeklyDownloads, stars] = await Promise.all([ | ||
| npmLastWeekDownloads(p.npmPackage), | ||
| fetchGithubStargazers(p.githubRepo), | ||
| ]); | ||
| const row: TrustedStackStatProject = { | ||
| id: p.id, | ||
| npmPackage: p.npmPackage, | ||
| githubRepo: p.githubRepo, | ||
| npmWeeklyDownloads, | ||
| githubStargazers: stars, | ||
| npmWeeklyDownloadsDisplay: formatWeeklyDownloads(npmWeeklyDownloads), | ||
| githubStarsDisplay: formatStars(stars), | ||
| }; | ||
| projects.push(row); | ||
| } | ||
| const payload: TrustedStackStatsFile = { projects }; | ||
| await writeFile(OUT, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); | ||
| console.log(`Wrote ${OUT} at ${new Date().toISOString()}`); | ||
| } | ||
|
|
||
| void main().catch((err: unknown) => { | ||
| console.error(err); | ||
| process.exitCode = 1; | ||
| }); |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this file actually needed? The root
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Root tsconfig.json isn’t a good fit because it lacks DOM lib (needed for fetch) and would typecheck too much of the repo. I moved the minimal config to .github/scripts/tsconfig.json (since it’s for CI scripts) and updated the docs check script to use it; removed docs/scripts/tsconfig.json, will push the commit changes shortly.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This script shouldn't rely on
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Good point — agreed. Even though the script uses fetch, it runs in Node, so it shouldn’t depend on the DOM lib. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2024", | ||
| "lib": ["ES2024"], | ||
| "module": "NodeNext", | ||
| "moduleResolution": "NodeNext", | ||
| "strict": true, | ||
| "skipLibCheck": true, | ||
| "noEmit": true, | ||
| "typeRoots": ["../../docs/node_modules/@types"], | ||
| "types": ["node"], | ||
| "verbatimModuleSyntax": true | ||
| }, | ||
| "include": [ | ||
| "./fetch-trusted-stack-stats.ts", | ||
| "../../docs/.vitepress/theme/data/trusted-stack-stats.types.ts" | ||
| ] | ||
| } |
|
camc314 marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| name: Update trusted stack stats | ||
|
|
||
| on: | ||
| schedule: | ||
| # Weekly: Monday 06:00 UTC | ||
| - cron: '0 6 * * 1' | ||
| workflow_dispatch: | ||
|
|
||
| defaults: | ||
| run: | ||
| shell: bash | ||
|
|
||
| jobs: | ||
| update: | ||
| if: github.repository == 'voidzero-dev/vite-plus' && github.event.repository.fork == false | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
| steps: | ||
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 | ||
| with: | ||
| node-version-file: .node-version | ||
|
|
||
| - name: Fetch npm and GitHub stats | ||
| env: | ||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: node .github/scripts/fetch-trusted-stack-stats.ts | ||
|
|
||
| - name: Create or update PR | ||
| uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11 | ||
| with: | ||
| base: main | ||
| branch: chore/docs-trusted-stack-stats | ||
| title: 'chore(docs): refresh trusted stack stats' | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| commit-message: 'chore(docs): refresh trusted stack stats' | ||
| add-paths: | | ||
| docs/.vitepress/theme/data/trusted-stack-stats.json | ||
| body: | | ||
| Automated update of trusted stack statistics on the docs homepage. | ||
|
|
||
| - npm weekly downloads (last-week): vite, vitest, oxlint | ||
| - GitHub stars: vitejs/vite, vitest-dev/vitest, oxc-project/oxc |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| { | ||
| "projects": [ | ||
| { | ||
| "id": "vite", | ||
| "npmPackage": "vite", | ||
| "githubRepo": "vitejs/vite", | ||
| "npmWeeklyDownloads": 114052837, | ||
| "githubStargazers": 80401, | ||
| "npmWeeklyDownloadsDisplay": "114m+", | ||
| "githubStarsDisplay": "80.4k" | ||
| }, | ||
| { | ||
| "id": "vitest", | ||
| "npmPackage": "vitest", | ||
| "githubRepo": "vitest-dev/vitest", | ||
| "npmWeeklyDownloads": 56727793, | ||
| "githubStargazers": 16471, | ||
| "npmWeeklyDownloadsDisplay": "57m+", | ||
| "githubStarsDisplay": "16.5k" | ||
| }, | ||
| { | ||
| "id": "oxc", | ||
| "npmPackage": "oxlint", | ||
| "githubRepo": "oxc-project/oxc", | ||
| "npmWeeklyDownloads": 5237088, | ||
| "githubStargazers": 20981, | ||
| "npmWeeklyDownloadsDisplay": "5.2m+", | ||
| "githubStarsDisplay": "21.0k" | ||
| } | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import raw from './trusted-stack-stats.json'; | ||
|
|
||
| import type { TrustedStackProjectId, TrustedStackStatProject, TrustedStackStatsFile } from './trusted-stack-stats.types'; | ||
|
|
||
| export type { TrustedStackProjectId, TrustedStackStatProject, TrustedStackStatsFile } from './trusted-stack-stats.types'; | ||
|
|
||
| export const trustedStackStats = raw as TrustedStackStatsFile; | ||
|
|
||
| export function trustedStackById(id: TrustedStackProjectId): TrustedStackStatProject { | ||
| const project = trustedStackStats.projects.find((p) => p.id === id); | ||
| if (!project) { | ||
| throw new Error(`trusted-stack-stats.json: missing project "${id}"`); | ||
| } | ||
| return project; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| export type TrustedStackProjectId = 'vite' | 'vitest' | 'oxc'; | ||
|
|
||
| export interface TrustedStackStatProject { | ||
| id: TrustedStackProjectId; | ||
| npmPackage: string; | ||
| githubRepo: string; | ||
| npmWeeklyDownloads: number; | ||
| githubStargazers: number; | ||
| npmWeeklyDownloadsDisplay: string; | ||
| githubStarsDisplay: string; | ||
| } | ||
|
|
||
| export interface TrustedStackStatsFile { | ||
| projects: TrustedStackStatProject[]; | ||
| } |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these two new
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I looked into this, and with our current setup it’s not really safe to remove those dependencies. Even though typescript and @types/node exist at the workspace root, the docs/ directory is installed and locked independently (pnpm -C docs install --frozen-lockfile with its own docs/pnpm-lock.yaml). That means docs/ won’t reliably inherit root devDependencies unless we switch to a full workspace-level install. Since docs/ also runs its own TypeScript check (update-trusted-stack-stats:check), it needs local access to tsc plus Node typings for imports and globals. So for now, keeping typescript and @types/node in docs/package.json makes the most sense — it keeps docs/ self-contained and avoids CI or isolated install issues. If we want to remove that duplication later, we’d first need to change the install/CI flow to rely on a root workspace install rather than pnpm -C docs.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can simplify further: the Could we drop:
cc. @camc314 WDYT?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point — let’s see what @camc314 thinks first, then we can figure out the best path forward from there. |
Uh oh!
There was an error while loading. Please reload this page.