Skip to content

Commit 50811e7

Browse files
authored
chore: deploy docs (#870)
1 parent bfaa5e4 commit 50811e7

File tree

6 files changed

+188
-182
lines changed

6 files changed

+188
-182
lines changed

.github/workflows/deploy-docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
- name: Setup Bun
2828
uses: oven-sh/setup-bun@v2
2929
with:
30-
bun-version: latest
30+
bun-version: '1.3.2'
3131

3232
- name: Setup Pages
3333
uses: actions/configure-pages@v5

docs/app/api/contributors/route.ts

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,46 @@
11
import { NextResponse } from 'next/server';
22

3-
export const revalidate = 86400; // Cache response for 24 hours (Maximum rate-limit friendliness)
3+
export const dynamic = 'force-static';
4+
export const revalidate = false;
45

56
export async function GET() {
6-
try {
7-
console.log('[API] Fetching contributors from GitHub...');
8-
const headers: Record<string, string> = process.env.GITHUB_TOKEN ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : {};
9-
10-
const res = await fetch('https://api.github.com/repos/margelo/react-native-quick-crypto/contributors?per_page=100', {
11-
headers,
12-
next: { revalidate: 86400 }
13-
});
14-
15-
if (!res.ok) {
16-
console.error('[API] GitHub Contributors fetch failed:', res.status, res.statusText);
17-
throw new Error(`GitHub API Error: ${res.status} ${res.statusText}`);
18-
}
7+
try {
8+
console.log('[API] Fetching contributors from GitHub...');
9+
const headers: Record<string, string> = process.env.GITHUB_TOKEN
10+
? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` }
11+
: {};
12+
13+
const res = await fetch(
14+
'https://api.github.com/repos/margelo/react-native-quick-crypto/contributors?per_page=100',
15+
{
16+
headers,
17+
next: { revalidate: 86400 },
18+
},
19+
);
20+
21+
if (!res.ok) {
22+
console.error(
23+
'[API] GitHub Contributors fetch failed:',
24+
res.status,
25+
res.statusText,
26+
);
27+
throw new Error(`GitHub API Error: ${res.status} ${res.statusText}`);
28+
}
1929

20-
const data = await res.json();
30+
const data = await res.json();
2131

22-
if (!Array.isArray(data)) return NextResponse.json([]);
32+
if (!Array.isArray(data)) return NextResponse.json([]);
2333

24-
const humans = data.filter((c: any) => !c.login.toLowerCase().includes('[bot]'));
34+
const humans = data.filter(
35+
(c: any) => !c.login.toLowerCase().includes('[bot]'),
36+
);
2537

26-
return NextResponse.json(humans);
27-
} catch (error) {
28-
console.error('[API] Handler error:', error);
29-
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
30-
}
38+
return NextResponse.json(humans);
39+
} catch (error) {
40+
console.error('[API] Handler error:', error);
41+
return NextResponse.json(
42+
{ error: 'Internal Server Error' },
43+
{ status: 500 },
44+
);
45+
}
3146
}

docs/app/api/releases/route.ts

Lines changed: 132 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,166 +1,155 @@
11
import { NextResponse } from 'next/server';
2-
import { unstable_cache } from 'next/cache';
32

4-
export const revalidate = 86400; // Cache response for 24 hours (releases don't change often)
3+
export const dynamic = 'force-static';
4+
export const revalidate = false;
55

66
function extractContributors(text: string): string[] {
7-
const mentionRegex = /@([a-zA-Z0-9-]+)/g;
8-
const matches = text ? text.match(mentionRegex) : null;
9-
if (!matches) return [];
7+
const mentionRegex = /@([a-zA-Z0-9-]+)/g;
8+
const matches = text ? text.match(mentionRegex) : null;
9+
if (!matches) return [];
1010

11-
// Clean up matches, remove duplicates, and filter out common bots/keywords
12-
const uniqueUsers = Array.from(new Set(matches.map(m => m.substring(1)))); // remove @
13-
const banned = ['dependabot', 'github-actions', 'channel', 'here', 'all']; // common non-user mentions
11+
const uniqueUsers = Array.from(new Set(matches.map(m => m.substring(1))));
12+
const banned = ['dependabot', 'github-actions', 'channel', 'here', 'all'];
1413

15-
return uniqueUsers.filter(u => !u.includes('[bot]') && !banned.includes(u.toLowerCase()));
14+
return uniqueUsers.filter(
15+
u => !u.includes('[bot]') && !banned.includes(u.toLowerCase()),
16+
);
1617
}
1718

1819
interface ContributorDetails {
19-
login: string;
20-
name?: string;
21-
avatar_url: string;
22-
html_url: string;
23-
bio?: string;
24-
location?: string;
25-
company?: string;
20+
login: string;
21+
name?: string;
22+
avatar_url: string;
23+
html_url: string;
24+
bio?: string;
25+
location?: string;
26+
company?: string;
2627
}
2728

28-
// Persistently cache user details forever (or until manually invalidated)
29-
// This fetches the "real name" and other details from GitHub User API
30-
const getContributorDetails = unstable_cache(
31-
async (login: string): Promise<ContributorDetails | null> => {
32-
try {
33-
console.log(`[API] Fetching user details for ${login}...`);
34-
const headers: Record<string, string> = process.env.GITHUB_TOKEN ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : {};
35-
const res = await fetch(`https://api.github.com/users/${login}`, { headers });
36-
37-
if (!res.ok) {
38-
if (res.status === 404) return null;
39-
if (res.status === 403) {
40-
console.warn(`[API] Rate limit hit for user ${login}. Using fallback.`);
41-
return {
42-
login,
43-
avatar_url: `https://github.com/${login}.png`,
44-
html_url: `https://github.com/${login}`
45-
};
46-
}
47-
throw new Error(`GitHub User API Error: ${res.status}`);
48-
}
49-
50-
const data = await res.json();
51-
return {
52-
login: data.login,
53-
name: data.name,
54-
avatar_url: data.avatar_url,
55-
html_url: data.html_url,
56-
bio: data.bio,
57-
location: data.location,
58-
company: data.company
59-
};
60-
} catch (e) {
61-
console.error(`[API] Failed to fetch user ${login}:`, e);
62-
// Fallback to basic details if API fails
63-
return {
64-
login,
65-
avatar_url: `https://github.com/${login}.png`,
66-
html_url: `https://github.com/${login}`
67-
};
68-
}
69-
},
70-
['github-user-details'], // Cache key namespace
71-
{
72-
revalidate: false, // Cache forever (never revalidate automatically)
73-
tags: ['contributors']
29+
async function getContributorDetails(
30+
login: string,
31+
headers: Record<string, string>,
32+
): Promise<ContributorDetails | null> {
33+
try {
34+
const res = await fetch(`https://api.github.com/users/${login}`, {
35+
headers,
36+
});
37+
38+
if (!res.ok) {
39+
if (res.status === 404) return null;
40+
return {
41+
login,
42+
avatar_url: `https://github.com/${login}.png`,
43+
html_url: `https://github.com/${login}`,
44+
};
7445
}
75-
);
46+
47+
const data = await res.json();
48+
return {
49+
login: data.login,
50+
name: data.name,
51+
avatar_url: data.avatar_url,
52+
html_url: data.html_url,
53+
bio: data.bio,
54+
location: data.location,
55+
company: data.company,
56+
};
57+
} catch {
58+
return {
59+
login,
60+
avatar_url: `https://github.com/${login}.png`,
61+
html_url: `https://github.com/${login}`,
62+
};
63+
}
64+
}
7665

7766
export async function GET() {
78-
try {
79-
console.log('[API] Fetching releases from GitHub...');
80-
const headers: Record<string, string> = process.env.GITHUB_TOKEN ? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` } : {};
67+
try {
68+
const headers: Record<string, string> = process.env.GITHUB_TOKEN
69+
? { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` }
70+
: {};
71+
72+
const releasesRes = await fetch(
73+
'https://api.github.com/repos/margelo/react-native-quick-crypto/releases?per_page=10',
74+
{ headers },
75+
);
76+
77+
if (!releasesRes.ok) {
78+
throw new Error(
79+
`GitHub API Error: ${releasesRes.status} ${releasesRes.statusText}`,
80+
);
81+
}
8182

82-
const releasesRes = await fetch('https://api.github.com/repos/margelo/react-native-quick-crypto/releases?per_page=10', {
83-
headers,
84-
next: { revalidate: 86400 }
85-
});
83+
const releases = await releasesRes.json();
84+
if (!Array.isArray(releases)) return NextResponse.json([]);
8685

87-
if (!releasesRes.ok) {
88-
console.error('[API] GitHub Releases fetch failed:', releasesRes.status, releasesRes.statusText);
89-
throw new Error(`GitHub API Error: ${releasesRes.status} ${releasesRes.statusText}`);
90-
}
86+
const enhanced = await Promise.all(
87+
releases.map(async (release: any, index: number) => {
88+
const previousTag = releases[index + 1]?.tag_name;
89+
const contributorsMap = new Map<
90+
string,
91+
{ login: string; commits: number }
92+
>();
93+
94+
extractContributors(release.body).forEach(user => {
95+
contributorsMap.set(user, { login: user, commits: 0 });
96+
});
9197

92-
const releases = await releasesRes.json();
93-
if (!Array.isArray(releases)) return NextResponse.json([]);
94-
95-
// Process releases in parallel to fetch contributors
96-
const enhanced = await Promise.all(releases.map(async (release: any, index: number) => {
97-
const previousTag = releases[index + 1]?.tag_name;
98-
const contributorsMap = new Map<string, { login: string, commits: number }>();
99-
100-
// 1. Text mentions (legacy method, but good for shoutouts)
101-
extractContributors(release.body).forEach(user => {
102-
contributorsMap.set(user, { login: user, commits: 0 });
103-
});
104-
105-
// 2. Compare API (if previous tag exists) to get actual committers
106-
if (previousTag) {
107-
try {
108-
const compareUrl = `https://api.github.com/repos/margelo/react-native-quick-crypto/compare/${previousTag}...${release.tag_name}`;
109-
const compareRes = await fetch(compareUrl, { headers, next: { revalidate: 86400 } });
110-
111-
if (compareRes.ok) {
112-
const data = await compareRes.json();
113-
if (data.commits && Array.isArray(data.commits)) {
114-
data.commits.forEach((commit: any) => {
115-
if (commit.author && commit.author.login) {
116-
if (!commit.author.login.includes('[bot]')) {
117-
const login = commit.author.login;
118-
const current = contributorsMap.get(login) || { login, commits: 0 };
119-
current.commits++;
120-
contributorsMap.set(login, current);
121-
}
122-
}
123-
});
124-
}
125-
} else if (compareRes.status === 403 && process.env.NODE_ENV === 'development') {
126-
// Mock data for development when rate limited
127-
console.warn(`[API] Rate limited. Using mock contributors for ${release.tag_name}.`);
128-
['mrousavy', 'szymonkapala', 'ospfranco'].forEach(login => {
129-
contributorsMap.set(login, { login, commits: Math.floor(Math.random() * 5) + 1 });
130-
});
131-
} else {
132-
console.warn(`[API] Compare fetch failed for ${previousTag}...${release.tag_name}: ${compareRes.status}`);
98+
if (previousTag) {
99+
try {
100+
const compareUrl = `https://api.github.com/repos/margelo/react-native-quick-crypto/compare/${previousTag}...${release.tag_name}`;
101+
const compareRes = await fetch(compareUrl, { headers });
102+
103+
if (compareRes.ok) {
104+
const data = await compareRes.json();
105+
if (data.commits && Array.isArray(data.commits)) {
106+
data.commits.forEach((commit: any) => {
107+
if (commit.author && commit.author.login) {
108+
if (!commit.author.login.includes('[bot]')) {
109+
const login = commit.author.login;
110+
const current = contributorsMap.get(login) || {
111+
login,
112+
commits: 0,
113+
};
114+
current.commits++;
115+
contributorsMap.set(login, current);
133116
}
134-
} catch (e) {
135-
console.error('[API] Compare fetch error:', e);
136-
}
117+
}
118+
});
119+
}
137120
}
121+
} catch {
122+
// Ignore compare errors
123+
}
124+
}
138125

139-
// 3. Hydrate with full user details (Names, Bios, etc.) using persistent cache
140-
const hydratedContributors = await Promise.all(
141-
Array.from(contributorsMap.values()).map(async (c) => {
142-
const details = await getContributorDetails(c.login);
143-
return {
144-
...details,
145-
commits: c.commits
146-
};
147-
})
148-
);
149-
150-
// Sort: High commits first, then by name
151-
const sortedContributors = hydratedContributors
152-
.filter(c => c !== null)
153-
.sort((a: any, b: any) => b.commits - a.commits);
154-
126+
const hydratedContributors = await Promise.all(
127+
Array.from(contributorsMap.values()).map(async c => {
128+
const details = await getContributorDetails(c.login, headers);
155129
return {
156-
...release,
157-
contributors: sortedContributors
130+
...details,
131+
commits: c.commits,
158132
};
159-
}));
160-
161-
return NextResponse.json(enhanced);
162-
} catch (error) {
163-
console.error('[API] Handler error:', error);
164-
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
165-
}
133+
}),
134+
);
135+
136+
const sortedContributors = hydratedContributors
137+
.filter(c => c !== null)
138+
.sort((a: any, b: any) => b.commits - a.commits);
139+
140+
return {
141+
...release,
142+
contributors: sortedContributors,
143+
};
144+
}),
145+
);
146+
147+
return NextResponse.json(enhanced);
148+
} catch (error) {
149+
console.error('[API] Handler error:', error);
150+
return NextResponse.json(
151+
{ error: 'Internal Server Error' },
152+
{ status: 500 },
153+
);
154+
}
166155
}

docs/app/api/search/route.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { source } from '@/lib/source';
22
import { createFromSource } from 'fumadocs-core/search/server';
33

4-
export const { GET } = createFromSource(source, {
5-
// https://docs.orama.com/docs/orama-js/supported-languages
6-
language: 'english',
7-
});
4+
export const revalidate = false;
5+
export const { staticGET: GET } = createFromSource(source);

0 commit comments

Comments
 (0)