返回 DeepSeek-Reasonix
update-acknowledgments.mjs
根目录 / scripts / update-acknowledgments.mjs
1 #!/usr/bin/env node
2 import { readFile, writeFile } from 'node:fs/promises';
3
4 const repo = 'esengine/DeepSeek-Reasonix';
5 const api = `https://api.github.com/repos/${repo}/contributors?per_page=20&anon=1`;
6 const startMarker = '<!-- reasonix-top-contributors:start -->';
7 const endMarker = '<!-- reasonix-top-contributors:end -->';
8
9 const headers = {
10 Accept: 'application/vnd.github+json',
11 'User-Agent': 'reasonix-acknowledgments-updater',
12 };
13 if (process.env.GITHUB_TOKEN) {
14 headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
15 }
16
17 const res = await fetch(api, { headers });
18 if (!res.ok) {
19 throw new Error(`GitHub contributors API failed: ${res.status} ${res.statusText}`);
20 }
21
22 const contributors = await res.json();
23 if (!Array.isArray(contributors) || contributors.length === 0) {
24 throw new Error('GitHub contributors API returned no contributors');
25 }
26
27 const top = contributors.slice(0, 20).map((c, index) => ({
28 rank: index + 1,
29 login: typeof c.login === 'string' ? c.login : '',
30 name: typeof c.name === 'string' ? c.name : '',
31 url: typeof c.html_url === 'string' ? c.html_url : '',
32 type: typeof c.type === 'string' ? c.type : '',
33 commits: Number(c.contributions) || 0,
34 }));
35
36 await updateReadme('README.md', renderTable(top, 'en'));
37 await updateReadme('README.zh-CN.md', renderTable(top, 'zh'));
38
39 function renderTable(rows, locale) {
40 const header = '| Contributor | Contributor | Contributor | Contributor |\n| --- | --- | --- | --- |';
41 const cells = rows.map((row) => renderContributor(row, locale));
42 const tableRows = [];
43 for (let i = 0; i < cells.length; i += 4) {
44 tableRows.push(`| ${cells.slice(i, i + 4).join(' | ')} |`);
45 }
46 return [
47 startMarker,
48 header,
49 ...tableRows,
50 endMarker,
51 ].join('\n');
52 }
53
54 function renderContributor(row, locale) {
55 const label = row.login || row.name || `anonymous-${row.rank}`;
56 const escaped = escapeMarkdown(label);
57 if (row.url) {
58 return `[**${escaped}**](${row.url})`;
59 }
60 const anonymous = locale === 'zh' ? '(anonymous)' : ' (anonymous)';
61 return `**${escaped}**${anonymous}`;
62 }
63
64 async function updateReadme(path, replacement) {
65 const original = await readFile(path, 'utf8');
66 const start = original.indexOf(startMarker);
67 const end = original.indexOf(endMarker);
68 if (start === -1 || end === -1 || end < start) {
69 throw new Error(`${path} is missing ${startMarker}/${endMarker}`);
70 }
71 const next = original.slice(0, start) + replacement + original.slice(end + endMarker.length);
72 if (next !== original) {
73 await writeFile(path, next);
74 }
75 }
76
77 function escapeMarkdown(value) {
78 return String(value).replace(/[\\|[\]]/g, '\\$&');
79 }
80
80 lines Plain Text