Skip to content

Commit 4286043

Browse files
committed
Merge branch 'feat/backfill-developer-contributions'
2 parents 2b4fce2 + cb1172a commit 4286043

228 files changed

Lines changed: 2753 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
/**
2+
* @description 通过 GitHub REST API 为开发者页面补全「主要贡献项目」列表。
3+
* - 扫描 source/ 下正文为空(或正文无实质内容且无仓库链接)的开发者页面;
4+
* - 合并「本人非 fork 仓库」与「公开事件(Push/PullRequest)涉及的他人仓库」两类候选,
5+
* 按 stargazers_count 排序取前 10(star 相同按名称字典序);
6+
* - 对 NEW_LOGINS 中尚未建页的组织成员,先创建 front-matter 再补全正文。
7+
* 已有实质正文(含仓库链接或富文本介绍)的页面绝不修改。
8+
*
9+
* 运行:GITHUB_TOKEN=$(gh auth token) node script/enrich-contributions.js
10+
* @param GITHUB_TOKEN github token,未配置时匿名限流 60 次/小时,无法跑完全量
11+
*/
12+
13+
const fs = require('fs').promises;
14+
const path = require('path');
15+
16+
const GITHUB_TOKEN = process.env.GITHUB_TOKEN || '';
17+
// 防止速度过快被 github 限制,每次请求间隔 500~800ms
18+
const DELAY_MIN = 500;
19+
const DELAY_MAX = 800;
20+
// 每个开发者最多为事件涉及的仓库查询 star 的次数上限(按事件频次排序)
21+
const MAX_EVENT_REPO_LOOKUPS = 30;
22+
// 每个开发者最终写入的仓库数上限
23+
const TOP_N = 10;
24+
// 组织成员中尚无页面、需要新建的用户
25+
const NEW_LOGINS = ['joyqi', 'shuashuai', 'sunshineg'];
26+
// 非开发者目录,扫描时排除
27+
const EXCLUDE_DIRS = new Set(['_data', '_posts', 'opensource-ranking']);
28+
29+
const SOURCE_DIR = path.join(__dirname, '../source');
30+
const API_BASE = 'https://api.github.com';
31+
32+
if (!GITHUB_TOKEN) {
33+
console.warn('[警告] 未配置 GITHUB_TOKEN,GitHub 匿名请求限流 60 次/小时,无法跑完全量!');
34+
console.warn(' 请先执行:export GITHUB_TOKEN=$(gh auth token)');
35+
}
36+
37+
// 延迟函数
38+
async function delay(ms) {
39+
return new Promise(resolve => setTimeout(resolve, ms));
40+
}
41+
42+
// 请求间隔 500~800ms 随机
43+
async function throttle() {
44+
await delay(DELAY_MIN + Math.floor(Math.random() * (DELAY_MAX - DELAY_MIN + 1)));
45+
}
46+
47+
// 带限流退避重试的 GitHub API 请求
48+
async function githubFetch(apiPath, retries = 0) {
49+
await throttle();
50+
const headers = {
51+
'Accept': 'application/vnd.github+json',
52+
'User-Agent': 'opensourcewin-enrich-contributions',
53+
};
54+
if (GITHUB_TOKEN) {
55+
headers.Authorization = `token ${GITHUB_TOKEN}`;
56+
}
57+
58+
let response;
59+
try {
60+
response = await fetch(`${API_BASE}${apiPath}`, { headers });
61+
} catch (error) {
62+
if (retries < 5) {
63+
const wait = 2000 * Math.pow(2, retries);
64+
console.warn(`网络错误,${wait}ms 后重试 (${apiPath}):`, error.message);
65+
await delay(wait);
66+
return githubFetch(apiPath, retries + 1);
67+
}
68+
throw error;
69+
}
70+
71+
if (response.status === 403 || response.status === 429) {
72+
if (retries < 5) {
73+
const retryAfter = parseInt(response.headers.get('retry-after') || '0', 10);
74+
const wait = Math.max(retryAfter * 1000, 2000 * Math.pow(2, retries));
75+
console.warn(`触发限流 (${response.status}),${wait}ms 后重试: ${apiPath}`);
76+
await delay(wait);
77+
return githubFetch(apiPath, retries + 1);
78+
}
79+
throw new Error(`限流重试次数用尽: ${apiPath}`);
80+
}
81+
82+
if (response.status === 404) {
83+
return null;
84+
}
85+
86+
if (!response.ok) {
87+
throw new Error(`请求失败 ${response.status}: ${apiPath}`);
88+
}
89+
90+
return response.json();
91+
}
92+
93+
// 提取 front-matter,返回 { frontMatter, body };frontMatter 含首尾 --- 分隔符原文
94+
function splitFrontMatter(content) {
95+
const match = content.match(/^---\n[\s\S]*?\n---\n?/);
96+
if (!match) {
97+
return { frontMatter: null, body: content };
98+
}
99+
return { frontMatter: match[0], body: content.slice(match[0].length) };
100+
}
101+
102+
// 判断页面状态:'has-repos'(已有仓库链接)、'rich'(富文本,禁止修改)、'empty'(需要补全)
103+
// 注意:正文非空但无仓库链接(如 AlexV525 的纯介绍页)按 'rich' 保守跳过——
104+
// 「已有实质内容的页面绝不修改」优先级高于补全,避免误伤人工维护的介绍文字。
105+
function classifyBody(body) {
106+
const trimmed = body.trim();
107+
if (!trimmed) return 'empty';
108+
if (/github\.com\/[\w.-]+\/[\w.-]+/.test(trimmed)) return 'has-repos';
109+
return 'rich';
110+
}
111+
112+
// 收集某 login 的候选仓库,返回按 star 排序后的 [{ fullName, stars }]
113+
async function collectCandidateRepos(login, repoStarsCache) {
114+
const candidates = new Map(); // fullName -> stars
115+
116+
// 来源一:本人非 fork 仓库,按 star 排序
117+
const ownRepos = await githubFetch(`/users/${login}/repos?per_page=100&type=owner`);
118+
if (Array.isArray(ownRepos)) {
119+
for (const repo of ownRepos) {
120+
if (repo.fork) continue;
121+
candidates.set(repo.full_name, repo.stargazers_count || 0);
122+
repoStarsCache.set(repo.full_name, repo.stargazers_count || 0);
123+
}
124+
}
125+
126+
// 来源二:公开事件中涉及的「他人仓库」(Push / PullRequest),按出现频次排序后取前 N 查 star。
127+
// 本人名下的仓库(含 fork)不从此来源取:非 fork 仓库已在来源一覆盖,fork 不算主要贡献。
128+
const events = await githubFetch(`/users/${login}/events/public?per_page=100`);
129+
if (Array.isArray(events)) {
130+
const freq = new Map(); // fullName -> count
131+
for (const event of events) {
132+
if (event.type !== 'PushEvent' && event.type !== 'PullRequestEvent') continue;
133+
const name = event.repo && event.repo.name;
134+
if (!name) continue;
135+
if (name.split('/')[0].toLowerCase() === login.toLowerCase()) continue; // 排除本人名下仓库
136+
freq.set(name, (freq.get(name) || 0) + 1);
137+
}
138+
const eventRepos = [...freq.entries()]
139+
.sort((a, b) => b[1] - a[1])
140+
.slice(0, MAX_EVENT_REPO_LOOKUPS)
141+
.map(([name]) => name);
142+
143+
for (const fullName of eventRepos) {
144+
if (candidates.has(fullName)) continue; // 已有 star 数据,去重
145+
let stars = repoStarsCache.get(fullName);
146+
if (stars === undefined) {
147+
const repo = await githubFetch(`/repos/${fullName}`);
148+
if (!repo) continue; // 仓库不存在或不可见
149+
stars = repo.stargazers_count || 0;
150+
repoStarsCache.set(fullName, stars);
151+
}
152+
candidates.set(fullName, stars);
153+
}
154+
}
155+
156+
return [...candidates.entries()]
157+
.map(([fullName, stars]) => ({ fullName, stars }))
158+
.sort((a, b) => b.stars - a.stars || a.fullName.toLowerCase().localeCompare(b.fullName.toLowerCase()))
159+
.slice(0, TOP_N);
160+
}
161+
162+
// 渲染「主要贡献项目」正文,格式对齐 Goooler 页面
163+
function renderBody(repos) {
164+
const lines = repos.map(r => `* <https://github.com/${r.fullName}>`);
165+
return `### 主要贡献项目\n\n${lines.join('\n')}\n`;
166+
}
167+
168+
// 为组织新成员创建 index.md(front-matter + 正文)
169+
async function createNewPage(login, repos) {
170+
const user = await githubFetch(`/users/${login}`);
171+
if (!user) {
172+
console.log(`[${login}] 获取用户信息失败(404),跳过新建页面`);
173+
return false;
174+
}
175+
const dirPath = path.join(SOURCE_DIR, login);
176+
const indexPath = path.join(dirPath, 'index.md');
177+
178+
let frontMatter = `---\nslug: ${login}\n`;
179+
if (user.name) frontMatter += `name: ${user.name}\n`;
180+
if (user.location) frontMatter += `description: "${user.location}"\n`;
181+
frontMatter += `github_id: ${user.id}\ngithub_avatar: ${user.avatar_url}\n---\n\n`;
182+
183+
await fs.mkdir(dirPath, { recursive: true });
184+
await fs.writeFile(indexPath, frontMatter + renderBody(repos), 'utf-8');
185+
console.log(`[${login}] 新建页面,补全 ${repos.length} 个仓库`);
186+
return true;
187+
}
188+
189+
async function processLogin(login, repoStarsCache) {
190+
const indexPath = path.join(SOURCE_DIR, login, 'index.md');
191+
const content = await fs.readFile(indexPath, 'utf-8');
192+
const { frontMatter, body } = splitFrontMatter(content);
193+
194+
const status = classifyBody(body);
195+
if (status === 'has-repos') {
196+
console.log(`[${login}] 跳过:正文已包含仓库链接`);
197+
return;
198+
}
199+
if (status === 'rich') {
200+
console.log(`[${login}] 跳过:正文为富文本介绍,不做修改`);
201+
return;
202+
}
203+
204+
const repos = await collectCandidateRepos(login, repoStarsCache);
205+
if (repos.length === 0) {
206+
console.log(`[${login}] 跳过:未获取到任何候选仓库(可能无公开活动)`);
207+
return;
208+
}
209+
210+
// front-matter 原样保留,仅替换正文
211+
const newContent = `${frontMatter || `---\nslug: ${login}\n---\n`}\n${renderBody(repos)}`;
212+
await fs.writeFile(indexPath, newContent, 'utf-8');
213+
console.log(`[${login}] 补全 ${repos.length} 个仓库`);
214+
}
215+
216+
async function main() {
217+
const repoStarsCache = new Map(); // 跨用户去重缓存:fullName -> stars
218+
219+
// 任务二:先处理需要新建页面的组织成员
220+
for (const login of NEW_LOGINS) {
221+
const indexPath = path.join(SOURCE_DIR, login, 'index.md');
222+
try {
223+
await fs.access(indexPath);
224+
console.log(`[${login}] 页面已存在,转入常规补全流程`);
225+
} catch {
226+
const repos = await collectCandidateRepos(login, repoStarsCache);
227+
if (repos.length === 0) {
228+
console.log(`[${login}] 跳过:未获取到任何候选仓库,不创建空页面`);
229+
continue;
230+
}
231+
await createNewPage(login, repos);
232+
}
233+
}
234+
235+
// 任务一:扫描既有开发者目录
236+
const entries = await fs.readdir(SOURCE_DIR, { withFileTypes: true });
237+
const logins = entries
238+
.filter(e => e.isDirectory() && !EXCLUDE_DIRS.has(e.name) && !e.name.startsWith('.'))
239+
.map(e => e.name)
240+
.sort();
241+
242+
console.log(`共扫描到 ${logins.length} 个开发者目录`);
243+
244+
for (const login of logins) {
245+
try {
246+
await processLogin(login, repoStarsCache);
247+
} catch (error) {
248+
console.error(`[${login}] 处理失败:`, error.message);
249+
}
250+
}
251+
252+
console.log('全部处理完成。');
253+
}
254+
255+
main().catch(error => {
256+
console.error('Error:', error);
257+
process.exit(1);
258+
});

ossheroes/source/2betop/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ github_id: 2698393
66
github_avatar: https://avatars.githubusercontent.com/u/2698393?v=4
77
---
88

9+
### 主要贡献项目
10+
11+
* <https://github.com/2betop/jello-demo>
12+
* <https://github.com/2betop/json-ast-comments>
13+
* <https://github.com/2betop/fis-parser-redcarpet>
14+
* <https://github.com/2betop/fis-preprocessor-css-scale>
15+
* <https://github.com/2betop/jello-command-server>
16+
* <https://github.com/2betop/2betop>
17+
* <https://github.com/2betop/fis-locolstorage-demo>
18+
* <https://github.com/2betop/gmudoc>
19+
* <https://github.com/2betop/2betop.github.com>
20+
* <https://github.com/2betop/components-builder>

ossheroes/source/9aoy/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ github_id: 22373761
66
github_avatar: https://avatars.githubusercontent.com/u/22373761?v=4
77
---
88

9+
### 主要贡献项目
10+
11+
* <https://github.com/youzan/vant>
12+
* <https://github.com/web-infra-dev/rsbuild>
13+
* <https://github.com/web-infra-dev/rspress>
14+
* <https://github.com/web-infra-dev/rstest>
15+
* <https://github.com/rstackjs/agent-skills>
16+
* <https://github.com/9aoy/rsbuild-ssr-example>
17+
* <https://github.com/9aoy/rsbuild-arco-pro-test>
18+
* <https://github.com/9aoy/rsbuild-test>
19+
* <https://github.com/9aoy/rspack-ecosystem-ci-test>
20+
* <https://github.com/9aoy/rspack-issue-4524>

ossheroes/source/ABA2396/index.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ github_id: 99072975
66
github_avatar: https://avatars.githubusercontent.com/u/99072975?v=4
77
---
88

9+
### 主要贡献项目
10+
11+
* <https://github.com/MaaAssistantArknights/MaaAssistantArknights>
12+
* <https://github.com/HandyOrg/HandyControl>

ossheroes/source/Aarebecca/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ github_id: 25787943
66
github_avatar: https://avatars.githubusercontent.com/u/25787943?v=4
77
---
88

9+
### 主要贡献项目
10+
11+
* <https://github.com/antvis/X6>
12+
* <https://github.com/antvis/antvis.github.io>
13+
* <https://github.com/Aarebecca/LaTeX-Guide>
14+
* <https://github.com/Aarebecca/CodeVis>
15+
* <https://github.com/Aarebecca/JCX>
16+
* <https://github.com/Aarebecca/measury>
17+
* <https://github.com/Aarebecca/ParamsRecovery>
18+
* <https://github.com/Aarebecca/performance>
19+
* <https://github.com/Aarebecca/ChinaGeoJson>
20+
* <https://github.com/Aarebecca/ChinaVis2021>

ossheroes/source/Aleksanaa/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ github_id: 42209822
66
github_avatar: https://avatars.githubusercontent.com/u/42209822?v=4
77
---
88

9+
### 主要贡献项目
10+
11+
* <https://github.com/Aleksanaa/qwqfetch>
12+
* <https://github.com/Aleksanaa/polkit-fakesudo>
13+
* <https://github.com/Aleksanaa/alloc.nix>
14+
* <https://github.com/Aleksanaa/by-name-migrate>
15+
* <https://github.com/Aleksanaa/peak>
16+
* <https://github.com/Aleksanaa/qsh-telecom-autologin>
17+
* <https://github.com/Aleksanaa/termux-nix>
18+
* <https://github.com/Aleksanaa/dotfiles>
19+
* <https://github.com/Aleksanaa/dde-archlinux>
20+
* <https://github.com/Aleksanaa/hyphae>

ossheroes/source/ArcturusZhang/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ github_id: 10554446
66
github_avatar: https://avatars.githubusercontent.com/u/10554446?v=4
77
---
88

9+
### 主要贡献项目
10+
11+
* <https://github.com/Azure/azure-sdk-for-net>
12+
* <https://github.com/Azure/azure-rest-api-specs>
13+
* <https://github.com/ArcturusZhang/Classical-Mechanics-Lecture-Notes>
14+
* <https://github.com/ArcturusZhang/Mahjong>
15+
* <https://github.com/Azure/typespec-azure>
16+
* <https://github.com/ArcturusZhang/Field-Theory-Lecture-Note>
17+
* <https://github.com/ArcturusZhang/PlayedByLeetCode>
18+
* <https://github.com/ArcturusZhang/GUISkinVisualizer>
19+
* <https://github.com/ArcturusZhang/Regen>
20+
* <https://github.com/ArcturusZhang/adhoc-sdk-generation>

ossheroes/source/ArgoZhang/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ github_id: 22001478
66
github_avatar: https://avatars.githubusercontent.com/u/22001478?v=4
77
---
88

9+
### 主要贡献项目
10+
11+
* <https://github.com/dotnetcore/BootstrapBlazor>
12+
* <https://github.com/ArgoZhang/BootstrapAdmin>
13+
* <https://github.com/ArgoZhang/SliderCaptcha>
14+
* <https://github.com/BootstrapBlazor/BootstrapBlazor.Extensions>
15+
* <https://github.com/ArgoZhang/ArgoZhang>
16+
* <https://github.com/ArgoZhang/ArgoZhang.github.io>
17+
* <https://github.com/ArgoZhang/BlazorApp1>
18+
* <https://github.com/ArgoZhang/BootstrapBlazorAppSsrLoginDemo>
19+
* <https://github.com/ArgoZhang/BootstrapBlazorVsix>
20+
* <https://github.com/ArgoZhang/IdentitySample>

ossheroes/source/BaiJiangJie/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,9 @@ github_id: 32935519
66
github_avatar: https://avatars.githubusercontent.com/u/32935519?v=4
77
---
88

9+
### 主要贡献项目
10+
11+
* <https://github.com/jumpserver/jumpserver>
12+
* <https://github.com/jumpserver/luna>
13+
* <https://github.com/jumpserver/lina>
14+
* <https://github.com/baijiangjie/baijiangjie.github.io>

ossheroes/source/CH3CHO/index.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,15 @@ github_id: 2909796
66
github_avatar: https://avatars.githubusercontent.com/u/2909796?v=4
77
---
88

9+
### 主要贡献项目
10+
11+
* <https://github.com/higress-group/higress>
12+
* <https://github.com/higress-group/higress-console>
13+
* <https://github.com/CH3CHO/higress-notes>
14+
* <https://github.com/CH3CHO/Archaius.Net>
15+
* <https://github.com/CH3CHO/ch3cho.github.io>
16+
* <https://github.com/Aias00/higress>
17+
* <https://github.com/CH3CHO/All-in-One>
18+
* <https://github.com/CH3CHO/bundler-maven-plugin>
19+
* <https://github.com/CH3CHO/CH3CHO>
20+
* <https://github.com/CH3CHO/env-setup>

0 commit comments

Comments
 (0)