import { env } from "../../lib/env"; import { Suspense } from "react"; import { notFound } from "next/navigation"; import { graphql } from "@octokit/graphql"; import * as cheerio from "cheerio"; import { GitForkIcon, StarIcon } from "lucide-react"; import Calendar from "./calendar"; import PageTitle from "../../components/PageTitle"; import Link from "../../components/Link"; import RelativeTime from "../../components/RelativeTime"; import { addMetadata } from "../../lib/helpers/metadata"; import * as config from "../../lib/config"; import type { Repository, User } from "@octokit/graphql-schema"; import styles from "./page.module.css"; export const metadata = addMetadata({ title: "Projects", description: `Most-starred repositories by @${config.authorSocial?.github} on GitHub`, alternates: { canonical: "/projects", }, }); const getContributions = async (): Promise< Array<{ date: string; count: number; level: number; }> > => { // thanks @grubersjoe! :) https://github.com/grubersjoe/github-contributions-api/blob/main/src/scrape.ts try { const response = await fetch(`https://github.com/users/${config.authorSocial.github}/contributions`, { headers: { referer: `https://github.com/${config.authorSocial.github}`, "x-requested-with": "XMLHttpRequest", }, cache: "force-cache", next: { revalidate: 43200, // 12 hours tags: ["github-contributions"], }, }); const $ = cheerio.load(await response.text()); const days = $(".js-calendar-graph-table .ContributionCalendar-day") .get() .sort((a, b) => { const dateA = a.attribs["data-date"] ?? ""; const dateB = b.attribs["data-date"] ?? ""; return dateA.localeCompare(dateB, "en"); }); const dayTooltips = $(".js-calendar-graph tool-tip") .toArray() // eslint-disable-next-line @typescript-eslint/no-explicit-any .reduce>((map, elem) => { map[elem.attribs["for"]] = elem; return map; }, {}); return days.map((day) => { const attr = { id: day.attribs["id"], date: day.attribs["data-date"], level: day.attribs["data-level"], }; let count = 0; if (dayTooltips[attr.id]) { const text = dayTooltips[attr.id].firstChild; if (text) { const countMatch = text.data.trim().match(/^\d+/); if (countMatch) { count = parseInt(countMatch[0]); } } } const level = parseInt(attr.level); return { date: attr.date, count, level, }; }); } catch (error) { console.error("[/projects] Failed to fetch contributions:", error); return []; } }; const getRepos = async (): Promise => { try { // https://docs.github.com/en/graphql/reference/objects#repository const { user } = await graphql<{ user: User }>( ` query ($username: String!, $sort: RepositoryOrderField!, $limit: Int) { user(login: $username) { repositories( first: $limit isLocked: false isFork: false ownerAffiliations: OWNER privacy: PUBLIC orderBy: { field: $sort, direction: DESC } ) { edges { node { name url description pushedAt stargazerCount forkCount primaryLanguage { name color } } } } } } `, { username: config.authorSocial.github, sort: "STARGAZERS", limit: 12, headers: { accept: "application/vnd.github.v3+json", authorization: `token ${env.GITHUB_TOKEN}`, }, request: { // override fetch() to use next's extension to cache the response // https://nextjs.org/docs/app/api-reference/functions/fetch#fetchurl-options fetch: (url: string | URL | Request, options?: RequestInit) => { return fetch(url, { ...options, cache: "force-cache", next: { revalidate: 1800, // 30 minutes tags: ["github-repos"], }, }); }, }, } ); return user.repositories.edges?.map((edge) => edge!.node as Repository); } catch (error) { console.error("[/projects] Failed to fetch repositories:", error); return []; } }; const Page = async () => { // don't fail the entire site build if the required config for this page is missing, just return a 404 since this page // would be blank anyways if (!env.GITHUB_TOKEN) { console.warn("[/projects] I can't fetch anything from GitHub without 'GITHUB_TOKEN' set!"); notFound(); } if (!config.authorSocial?.github) { console.warn( "[/projects] I can't fetch anything from GitHub without 'authorSocial.github' set in lib/config/index.ts." ); notFound(); } // fetch the repos and contributions in parallel const [contributions, repos] = await Promise.all([getContributions(), getRepos()]); return ( <> Projects

Contribution activity

Popular repositories

{repos?.map((repo) => (
{repo!.name} {repo!.description &&

{repo!.description}

}
{repo!.primaryLanguage && (
{repo!.primaryLanguage.color && ( )} {repo!.primaryLanguage.name}
)} {repo!.stargazerCount > 0 && (
{Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo!.stargazerCount)}
)} {repo!.forkCount > 0 && (
{Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo!.forkCount)}
)}
Updated
))}

View more on{" "} {" "} GitHub.

); }; export default Page;