Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/update-contributors.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Update contributors

on:
push:
branches:
- main
- master
workflow_dispatch:
schedule:
# 每天北京时间 0:00(UTC 16:00)同步贡献者列表
- cron: "0 16 * * *"

permissions:
contents: write

jobs:
update-contributors:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Generate contributors.json
run: node scripts/generate-contributors.js
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Commit and push if changed
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add assets/contributors.json
if git diff --staged --quiet; then
echo "contributors.json unchanged"
exit 0
fi
git commit -m "chore: update contributors.json [skip ci]"
git push
112 changes: 95 additions & 17 deletions docs.js
Original file line number Diff line number Diff line change
Expand Up @@ -900,40 +900,119 @@
});
}

var buddyContributorsState = { data: null, promise: null };
var contributorsState = { data: null, promise: null };

function fetchAllBuddyContributors(repo) {
if (buddyContributorsState.data) {
return Promise.resolve(buddyContributorsState.data);
}
if (buddyContributorsState.promise) {
return buddyContributorsState.promise;
}
var CONTRIBUTOR_REPOS = [
"buddy-compiler/buddy-mlir",
"RuyiAI-Stack/ruyiai-stack.github.io",
"RuyiAI-Stack/triton-riscv",
"RuyiAI-Stack/tilelang-riscv",
"RuyiAI-Stack/pytorch",
"RuyiAI-Stack/llvm-project"
];

var CONTRIBUTORS_JSON_URL = "./assets/contributors.json";

function fetchAllContributorsForRepo(repo) {
var all = [];
function fetchPage(page) {
return fetch("https://api.github.com/repos/" + repo + "/contributors?per_page=100&anon=1&page=" + page)
.then(function (r) {
if (r.status === 403) {
return Promise.reject(new Error("rate_limit"));
}
if (!r.ok) throw new Error(String(r.status));
return r.json();
})
.then(function (list) {
if (!Array.isArray(list)) throw new Error("invalid_response");
if (!list.length) return all;
all = all.concat(list);
if (list.length < 100) return all;
return fetchPage(page + 1);
});
}
buddyContributorsState.promise = fetchPage(1)
.then(function (data) {
buddyContributorsState.data = data;
buddyContributorsState.promise = null;
return data;
return fetchPage(1);
}

function contributorKey(c) {
if (c.login) return "login:" + String(c.login).toLowerCase();
if (c.id != null) return "id:" + c.id;
return "anon:" + (c.avatar_url || "");
}

/** 按仓库顺序合并贡献者,login/id 去重,保留首次出现顺序 */
function mergeContributorsDedup(repoLists) {
var seen = {};
var merged = [];
repoLists.forEach(function (list) {
(list || []).forEach(function (c) {
var key = contributorKey(c);
if (seen[key]) return;
seen[key] = true;
merged.push(c);
});
});
return merged;
}

function loadContributorsFromStaticJson() {
return fetch(resolveDocUrl(CONTRIBUTORS_JSON_URL), { cache: "no-store" })
.then(function (r) {
if (!r.ok) throw new Error(String(r.status));
return r.json();
})
.then(function (payload) {
if (payload && Array.isArray(payload.contributors)) return payload.contributors;
if (Array.isArray(payload)) return payload;
throw new Error("invalid_json");
});
}

/** 逐仓库拉取,单个失败不影响其余仓库 */
function fetchMergedContributorsFromApi(repos) {
function fetchRepoAt(idx, acc) {
if (idx >= repos.length) return Promise.resolve(acc);
return fetchAllContributorsForRepo(repos[idx])
.then(function (list) {
acc.push(list);
return fetchRepoAt(idx + 1, acc);
})
.catch(function () {
acc.push([]);
return fetchRepoAt(idx + 1, acc);
});
}
return fetchRepoAt(0, []).then(function (repoLists) {
var merged = mergeContributorsDedup(repoLists);
if (!merged.length) {
return Promise.reject(new Error("all_failed"));
}
return merged;
});
}

function fetchMergedContributors(repos) {
if (contributorsState.data) {
return Promise.resolve(contributorsState.data);
}
if (contributorsState.promise) {
return contributorsState.promise;
}
contributorsState.promise = loadContributorsFromStaticJson()
.catch(function () {
return fetchMergedContributorsFromApi(repos);
})
.then(function (merged) {
contributorsState.data = merged;
contributorsState.promise = null;
return merged;
})
.catch(function (err) {
buddyContributorsState.promise = null;
contributorsState.promise = null;
throw err;
});
return buddyContributorsState.promise;
return contributorsState.promise;
}

function contributorStatsText(count, lang) {
Expand Down Expand Up @@ -977,10 +1056,9 @@
function loadBuddyContributors(bodyEl) {
var container = bodyEl ? bodyEl.querySelector("#buddyContributors") : null;
if (!container) return;
var repo = container.getAttribute("data-repo") || "buddy-compiler/buddy-mlir";
var lang = getCurrentLang();
container.innerHTML = '<p class="buddy-contributors__status">' + (lang === "en" ? "Loading contributors…" : "加载贡献者中…") + "</p>";
fetchAllBuddyContributors(repo)
fetchMergedContributors(CONTRIBUTOR_REPOS)
.then(function (contributors) {
if (!bodyEl.querySelector("#buddyContributors")) return;
renderBuddyContributorsGrid(container, contributors);
Expand Down
2 changes: 1 addition & 1 deletion docs/about/about-en.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@ Upholding the philosophy of open collaboration, we are committed to working with

## Contributors

<div id="buddyContributors" class="buddy-contributors" data-repo="buddy-compiler/buddy-mlir"></div>
<div id="buddyContributors" class="buddy-contributors"></div>
2 changes: 1 addition & 1 deletion docs/about/about.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ RuyiAI 由 **中国科学院软件研究所(ISCAS)智能软件研究中心**

## 贡献者

<div id="buddyContributors" class="buddy-contributors" data-repo="buddy-compiler/buddy-mlir"></div>
<div id="buddyContributors" class="buddy-contributors"></div>
122 changes: 122 additions & 0 deletions scripts/generate-contributors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/env node
/**
* 拉取各仓库贡献者并生成 assets/contributors.json(去重、保留首次出现顺序)。
* 用法:node scripts/generate-contributors.js
* 未认证 GitHub API 限流 60 次/小时;若失败请稍后重试或设置 GITHUB_TOKEN。
*/
"use strict";

var fs = require("fs");
var path = require("path");
var https = require("https");

var REPOS = [
"buddy-compiler/buddy-mlir",
"RuyiAI-Stack/ruyiai-stack.github.io",
"RuyiAI-Stack/triton-riscv",
"RuyiAI-Stack/tilelang-riscv",
"RuyiAI-Stack/pytorch",
"RuyiAI-Stack/llvm-project"
];

var OUT = path.join(__dirname, "..", "assets", "contributors.json");
var TOKEN = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "";

function ghGet(url) {
return new Promise(function (resolve, reject) {
var headers = { "User-Agent": "ruyiai-stack-contributors-script", Accept: "application/vnd.github+json" };
if (TOKEN) headers.Authorization = "Bearer " + TOKEN;
https.get(url, { headers: headers }, function (res) {
var body = "";
res.on("data", function (c) { body += c; });
res.on("end", function () {
if (res.statusCode === 403 && body.indexOf("rate limit") !== -1) {
reject(new Error("GitHub API rate limit exceeded. Set GITHUB_TOKEN or retry later."));
return;
}
if (res.statusCode < 200 || res.statusCode >= 300) {
reject(new Error("HTTP " + res.statusCode + " for " + url + ": " + body.slice(0, 200)));
return;
}
try {
resolve(JSON.parse(body));
} catch (e) {
reject(e);
}
});
}).on("error", reject);
});
}

function fetchRepoContributors(repo) {
var all = [];
function page(n) {
var url = "https://api.github.com/repos/" + repo + "/contributors?per_page=100&anon=1&page=" + n;
return ghGet(url).then(function (list) {
if (!list.length) return all;
all = all.concat(list);
if (list.length < 100) return all;
return page(n + 1);
});
}
return page(1);
}

function contributorKey(c) {
if (c.login) return "login:" + String(c.login).toLowerCase();
if (c.id != null) return "id:" + c.id;
return "anon:" + (c.avatar_url || "");
}

function mergeDedup(repoLists) {
var seen = {};
var merged = [];
repoLists.forEach(function (list) {
list.forEach(function (c) {
var key = contributorKey(c);
if (seen[key]) return;
seen[key] = true;
merged.push({
login: c.login || null,
id: c.id,
avatar_url: c.avatar_url,
html_url: c.html_url || (c.login ? "https://github.com/" + c.login : null)
});
});
});
return merged;
}

function fetchAllSequential(repos) {
var results = [];
function next(i) {
if (i >= repos.length) return Promise.resolve(results);
var repo = repos[i];
process.stderr.write("Fetching " + repo + "…\n");
return fetchRepoContributors(repo)
.then(function (list) {
process.stderr.write(" " + list.length + " contributors\n");
results.push(list);
return next(i + 1);
});
}
return next(0);
}

fetchAllSequential(REPOS)
.then(function (repoLists) {
var contributors = mergeDedup(repoLists);
var payload = {
generatedAt: new Date().toISOString(),
repos: REPOS,
count: contributors.length,
contributors: contributors
};
fs.mkdirSync(path.dirname(OUT), { recursive: true });
fs.writeFileSync(OUT, JSON.stringify(payload, null, 2) + "\n");
process.stderr.write("Wrote " + contributors.length + " contributors to " + OUT + "\n");
})
.catch(function (err) {
process.stderr.write(String(err.message || err) + "\n");
process.exit(1);
});
Loading