Initial commit: Indiekit Eleventy theme
This commit is contained in:
228
_data/githubActivity.js
Normal file
228
_data/githubActivity.js
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* GitHub Activity Data
|
||||
* Fetches from Indiekit's endpoint-github public API
|
||||
* Falls back to direct GitHub API if Indiekit is unavailable
|
||||
*/
|
||||
|
||||
import EleventyFetch from "@11ty/eleventy-fetch";
|
||||
|
||||
const GITHUB_USERNAME = process.env.GITHUB_USERNAME || "";
|
||||
const INDIEKIT_URL = process.env.SITE_URL || "https://example.com";
|
||||
|
||||
// Fallback featured repos if Indiekit API unavailable (from env: comma-separated)
|
||||
const FALLBACK_FEATURED_REPOS = process.env.GITHUB_FEATURED_REPOS?.split(",").filter(Boolean) || [];
|
||||
|
||||
/**
|
||||
* Fetch from Indiekit's public GitHub API endpoint
|
||||
*/
|
||||
async function fetchFromIndiekit(endpoint) {
|
||||
try {
|
||||
const url = `${INDIEKIT_URL}/githubapi/api/${endpoint}`;
|
||||
console.log(`[githubActivity] Fetching from Indiekit: ${url}`);
|
||||
const data = await EleventyFetch(url, {
|
||||
duration: "15m",
|
||||
type: "json",
|
||||
});
|
||||
console.log(`[githubActivity] Indiekit ${endpoint} success`);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.log(
|
||||
`[githubActivity] Indiekit API unavailable for ${endpoint}: ${error.message}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch from GitHub API directly
|
||||
*/
|
||||
async function fetchFromGitHub(endpoint) {
|
||||
const url = `https://api.github.com${endpoint}`;
|
||||
const headers = {
|
||||
Accept: "application/vnd.github.v3+json",
|
||||
"User-Agent": "Eleventy-Site",
|
||||
};
|
||||
|
||||
if (process.env.GITHUB_TOKEN) {
|
||||
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
||||
}
|
||||
|
||||
return await EleventyFetch(url, {
|
||||
duration: "15m",
|
||||
type: "json",
|
||||
fetchOptions: { headers },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate text with ellipsis
|
||||
*/
|
||||
function truncate(text, maxLength = 80) {
|
||||
if (!text || text.length <= maxLength) return text || "";
|
||||
return text.slice(0, maxLength - 1) + "...";
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract commits from push events
|
||||
*/
|
||||
function extractCommits(events) {
|
||||
if (!Array.isArray(events)) return [];
|
||||
|
||||
return events
|
||||
.filter((event) => event.type === "PushEvent")
|
||||
.flatMap((event) =>
|
||||
(event.payload?.commits || []).map((commit) => ({
|
||||
sha: commit.sha.slice(0, 7),
|
||||
message: truncate(commit.message.split("\n")[0]),
|
||||
url: `https://github.com/${event.repo.name}/commit/${commit.sha}`,
|
||||
repo: event.repo.name,
|
||||
repoUrl: `https://github.com/${event.repo.name}`,
|
||||
date: event.created_at,
|
||||
}))
|
||||
)
|
||||
.slice(0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract PRs/Issues from events
|
||||
*/
|
||||
function extractContributions(events) {
|
||||
if (!Array.isArray(events)) return [];
|
||||
|
||||
return events
|
||||
.filter(
|
||||
(event) =>
|
||||
(event.type === "PullRequestEvent" || event.type === "IssuesEvent") &&
|
||||
event.payload?.action === "opened"
|
||||
)
|
||||
.map((event) => {
|
||||
const item = event.payload.pull_request || event.payload.issue;
|
||||
return {
|
||||
type: event.type === "PullRequestEvent" ? "pr" : "issue",
|
||||
title: truncate(item?.title),
|
||||
url: item?.html_url,
|
||||
repo: event.repo.name,
|
||||
repoUrl: `https://github.com/${event.repo.name}`,
|
||||
number: item?.number,
|
||||
date: event.created_at,
|
||||
};
|
||||
})
|
||||
.slice(0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format starred repos
|
||||
*/
|
||||
function formatStarred(repos) {
|
||||
if (!Array.isArray(repos)) return [];
|
||||
|
||||
return repos.map((repo) => ({
|
||||
name: repo.full_name,
|
||||
description: truncate(repo.description, 120),
|
||||
url: repo.html_url,
|
||||
stars: repo.stargazers_count,
|
||||
language: repo.language,
|
||||
topics: repo.topics?.slice(0, 5) || [],
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch featured repos directly from GitHub (fallback)
|
||||
*/
|
||||
async function fetchFeaturedFromGitHub(repoList) {
|
||||
const featured = [];
|
||||
|
||||
for (const repoFullName of repoList) {
|
||||
try {
|
||||
const repo = await fetchFromGitHub(`/repos/${repoFullName}`);
|
||||
let commits = [];
|
||||
try {
|
||||
const commitsData = await fetchFromGitHub(
|
||||
`/repos/${repoFullName}/commits?per_page=5`
|
||||
);
|
||||
commits = commitsData.map((c) => ({
|
||||
sha: c.sha.slice(0, 7),
|
||||
message: truncate(c.commit.message.split("\n")[0]),
|
||||
url: c.html_url,
|
||||
date: c.commit.author.date,
|
||||
}));
|
||||
} catch (e) {
|
||||
console.log(`[githubActivity] Could not fetch commits for ${repoFullName}`);
|
||||
}
|
||||
|
||||
featured.push({
|
||||
fullName: repo.full_name,
|
||||
name: repo.name,
|
||||
description: repo.description,
|
||||
url: repo.html_url,
|
||||
stars: repo.stargazers_count,
|
||||
forks: repo.forks_count,
|
||||
language: repo.language,
|
||||
isPrivate: repo.private,
|
||||
commits,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(`[githubActivity] Could not fetch ${repoFullName}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return featured;
|
||||
}
|
||||
|
||||
export default async function () {
|
||||
try {
|
||||
console.log("[githubActivity] Fetching GitHub data...");
|
||||
|
||||
// Try Indiekit public API first
|
||||
const [indiekitStars, indiekitCommits, indiekitActivity, indiekitFeatured] =
|
||||
await Promise.all([
|
||||
fetchFromIndiekit("stars"),
|
||||
fetchFromIndiekit("commits"),
|
||||
fetchFromIndiekit("activity"),
|
||||
fetchFromIndiekit("featured"),
|
||||
]);
|
||||
|
||||
// Check if Indiekit API is available
|
||||
const hasIndiekitData =
|
||||
indiekitStars?.stars ||
|
||||
indiekitCommits?.commits ||
|
||||
indiekitFeatured?.featured;
|
||||
|
||||
if (hasIndiekitData) {
|
||||
console.log("[githubActivity] Using Indiekit API data");
|
||||
return {
|
||||
stars: indiekitStars?.stars || [],
|
||||
commits: indiekitCommits?.commits || [],
|
||||
activity: indiekitActivity?.activity || [],
|
||||
featured: indiekitFeatured?.featured || [],
|
||||
source: "indiekit",
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback to direct GitHub API
|
||||
console.log("[githubActivity] Falling back to GitHub API");
|
||||
|
||||
const [events, starred, featured] = await Promise.all([
|
||||
fetchFromGitHub(`/users/${GITHUB_USERNAME}/events/public?per_page=50`),
|
||||
fetchFromGitHub(`/users/${GITHUB_USERNAME}/starred?per_page=20&sort=created`),
|
||||
fetchFeaturedFromGitHub(FALLBACK_FEATURED_REPOS),
|
||||
]);
|
||||
|
||||
return {
|
||||
stars: formatStarred(starred || []),
|
||||
commits: extractCommits(events || []),
|
||||
contributions: extractContributions(events || []),
|
||||
featured,
|
||||
source: "github",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error("[githubActivity] Error:", error.message);
|
||||
return {
|
||||
stars: [],
|
||||
commits: [],
|
||||
contributions: [],
|
||||
featured: [],
|
||||
source: "error",
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user