1
0
forked from boos/BoosAPI

Preserve the active worktree snapshot on dev for follow-on integration

The root checkout already contained a large in-progress proxy, transformer,
and settings refactor. This commit captures that exact work on a dedicated
local dev branch without disturbing the original dirty worktree, so the
changes can be continued or split from dev instead of living only as
uncommitted state.

Constraint: The source checkout was already dirty on another branch, so the move had to preserve that worktree while excluding local .idea and .omx residue from the commit
Rejected: Switch the dirty root checkout directly to dev | risked disrupting the original branch and in-progress state
Confidence: medium
Scope-risk: broad
Reversibility: clean
Directive: Treat this as a transfer snapshot; fix the failing oauth/accountTokens/normalized/docs test regressions before merge or publication
Tested: git diff --check; npm run typecheck; npm run repo:drift-check
Not-tested: Clean test pass; npm test currently fails in oauth routes, accountTokens sync, shared normalized helpers, gemini route expectations, architecture-boundaries, and docs vitepress config
This commit is contained in:
cita-777
2026-04-17 19:41:14 +08:00
parent 6cca74b039
commit 88f40e91f7
133 changed files with 13780 additions and 5670 deletions
+7
View File
@@ -34,6 +34,13 @@ describe('docker workflows', () => {
expect(dockerfile).toContain('FROM node:22-bookworm-slim');
});
it('avoids buildkit-only frontend syntax so managed docker builders can parse it reliably', () => {
const dockerfile = readFileSync(resolve(process.cwd(), 'docker/Dockerfile'), 'utf8');
expect(dockerfile).not.toContain('# syntax=docker/dockerfile:');
expect(dockerfile).not.toContain('RUN --mount=type=cache');
});
it('keeps server docker builds isolated from desktop packaging dependencies', () => {
const dockerfile = readFileSync(resolve(process.cwd(), 'docker/Dockerfile'), 'utf8');
@@ -0,0 +1,111 @@
import { describe, expect, it } from 'vitest';
import {
normalizeContributors,
replaceContributorsSection,
renderContributorsBlock,
} from './update-readme-contributors.js';
describe('README contributors updater', () => {
it('filters anonymous and bot contributors before rendering', () => {
const contributors = normalizeContributors([
{
login: 'cita-777',
html_url: 'https://github.com/cita-777',
avatar_url: 'https://avatars.githubusercontent.com/u/177306803?v=4',
contributions: 483,
},
{
login: 'dependabot[bot]',
html_url: 'https://github.com/apps/dependabot',
avatar_url: 'https://avatars.githubusercontent.com/in/29110?v=4',
contributions: 12,
},
{
login: null,
html_url: null,
avatar_url: null,
contributions: 22,
},
{
login: 'Babylonehy',
html_url: 'https://github.com/Babylonehy',
avatar_url: 'https://avatars.githubusercontent.com/u/30937892?v=4',
contributions: 3,
},
]);
expect(contributors).toEqual([
{
login: 'cita-777',
htmlUrl: 'https://github.com/cita-777',
avatarUrl: 'https://avatars.githubusercontent.com/u/177306803?v=4&s=48',
},
{
login: 'Babylonehy',
htmlUrl: 'https://github.com/Babylonehy',
avatarUrl: 'https://avatars.githubusercontent.com/u/30937892?v=4&s=48',
},
]);
});
it('replaces the marked README section with an avatar wall', () => {
const readme = [
'# Metapi',
'',
'<!-- metapi-contributors:start -->',
'old block',
'<!-- metapi-contributors:end -->',
'',
'tail',
].join('\n');
const next = replaceContributorsSection(readme, [
{
login: 'cita-777',
htmlUrl: 'https://github.com/cita-777',
avatarUrl: 'https://avatars.githubusercontent.com/u/177306803?v=4&s=48',
},
{
login: 'Hureru',
htmlUrl: 'https://github.com/Hureru',
avatarUrl: 'https://avatars.githubusercontent.com/u/121702350?v=4&s=48',
},
{
login: 'Babylonehy',
htmlUrl: 'https://github.com/Babylonehy',
avatarUrl: 'https://avatars.githubusercontent.com/u/30937892?v=4&s=48',
},
], 2);
expect(next).toContain('<p align="left">');
expect(next).toContain('https://github.com/cita-777');
expect(next).toContain('https://github.com/Hureru');
expect(next).toContain('https://github.com/Babylonehy');
expect(next).not.toContain('old block');
expect(next).toContain('tail');
});
it('renders contributors across multiple rows', () => {
const html = renderContributorsBlock([
{
login: 'cita-777',
htmlUrl: 'https://github.com/cita-777',
avatarUrl: 'https://avatars.githubusercontent.com/u/177306803?v=4&s=48',
},
{
login: 'Hureru',
htmlUrl: 'https://github.com/Hureru',
avatarUrl: 'https://avatars.githubusercontent.com/u/121702350?v=4&s=48',
},
{
login: 'Babylonehy',
htmlUrl: 'https://github.com/Babylonehy',
avatarUrl: 'https://avatars.githubusercontent.com/u/30937892?v=4&s=48',
},
], 2);
expect(html).toContain('\n <a href="https://github.com/cita-777"');
expect(html).toContain('\n <a href="https://github.com/Babylonehy"');
expect((html.match(/\n <a href=/g) || []).length).toBe(2);
});
});
+174
View File
@@ -0,0 +1,174 @@
import { execFileSync } from 'node:child_process';
import { readFileSync, writeFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const REPO = 'cita-777/metapi';
const README_FILES = ['README.md', 'README_EN.md'] as const;
const START_MARKER = '<!-- metapi-contributors:start -->';
const END_MARKER = '<!-- metapi-contributors:end -->';
const PER_LINE = 10;
const EXCLUDED_LOGINS = new Set(['dependabot[bot]']);
type GitHubContributor = {
login: string | null;
html_url: string | null;
avatar_url: string | null;
contributions?: number;
};
export type ReadmeContributor = {
login: string;
htmlUrl: string;
avatarUrl: string;
};
export function normalizeContributors(
contributors: GitHubContributor[],
): ReadmeContributor[] {
const seen = new Set<string>();
return contributors
.filter((contributor) => {
const login = contributor.login?.trim();
const htmlUrl = contributor.html_url?.trim();
const avatarUrl = contributor.avatar_url?.trim();
if (!login || !htmlUrl || !avatarUrl) {
return false;
}
if (EXCLUDED_LOGINS.has(login.toLowerCase()) || login.toLowerCase().endsWith('[bot]')) {
return false;
}
return true;
})
.sort((left, right) => (right.contributions ?? 0) - (left.contributions ?? 0))
.filter((contributor) => {
const key = contributor.login!.toLowerCase();
if (seen.has(key)) {
return false;
}
seen.add(key);
return true;
})
.map((contributor) => ({
login: contributor.login!.trim(),
htmlUrl: contributor.html_url!.trim(),
avatarUrl: normalizeAvatarUrl(contributor.avatar_url!.trim()),
}));
}
export function renderContributorsBlock(
contributors: ReadmeContributor[],
perLine = PER_LINE,
): string {
if (contributors.length === 0) {
return '<p align="left">\n <sub>No public contributors found yet.</sub>\n</p>';
}
const lines: string[] = [];
for (let index = 0; index < contributors.length; index += perLine) {
const chunk = contributors.slice(index, index + perLine);
lines.push(
` ${chunk
.map(
(contributor) =>
`<a href="${contributor.htmlUrl}"><img src="${contributor.avatarUrl}" width="48" height="48" alt="${contributor.login}" title="${contributor.login}"/></a>`,
)
.join(' ')}`,
);
}
return `<p align="left">\n${lines.join('\n')}\n</p>`;
}
export function replaceContributorsSection(
readme: string,
contributors: ReadmeContributor[],
perLine = PER_LINE,
): string {
const start = readme.indexOf(START_MARKER);
const end = readme.indexOf(END_MARKER);
if (start === -1 || end === -1 || end < start) {
throw new Error('README contributors markers are missing');
}
const before = readme.slice(0, start + START_MARKER.length);
const after = readme.slice(end);
return `${before}\n${renderContributorsBlock(contributors, perLine)}\n${after}`;
}
export function updateReadmeFile(
readmePath: string,
contributors: ReadmeContributor[],
perLine = PER_LINE,
): boolean {
const current = readFileSync(readmePath, 'utf8');
const next = replaceContributorsSection(current, contributors, perLine);
if (current === next) {
return false;
}
writeFileSync(readmePath, next);
return true;
}
export function fetchContributors(repo = REPO): ReadmeContributor[] {
const raw = execFileSync(
'gh',
['api', `repos/${repo}/contributors?per_page=100&anon=1`, '--paginate'],
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] },
);
return normalizeContributors(parsePaginatedJson(raw) as GitHubContributor[]);
}
function parsePaginatedJson(raw: string): unknown[] {
const items: unknown[] = [];
for (const line of raw.split('\n')) {
const trimmed = line.trim();
if (!trimmed) {
continue;
}
const parsed = JSON.parse(trimmed);
if (Array.isArray(parsed)) {
items.push(...parsed);
} else {
items.push(parsed);
}
}
return items;
}
function normalizeAvatarUrl(url: string): string {
if (!/^https?:/i.test(url)) {
return url;
}
const lower = url.toLowerCase();
if (lower.includes('s=') || lower.includes('size=')) {
return url;
}
return `${url}${url.includes('?') ? '&' : '?'}s=48`;
}
function main(): void {
const contributors = fetchContributors(REPO);
const updatedFiles = README_FILES.filter((path) =>
updateReadmeFile(resolve(process.cwd(), path), contributors),
);
const touched = updatedFiles.length > 0 ? updatedFiles.join(', ') : 'none';
console.log(`Updated README contributors for ${contributors.length} people: ${touched}`);
}
if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
main();
}