Back to blogs
Blog
Writing in public
Longer posts — published here first, then cross-posted to Dev.to with a canonical URL back to this site.
Blog
Longer posts — published here first, then cross-posted to Dev.to with a canonical URL back to this site.
Back to blogs
Back to blogs
Postreact, webdev, github, portfolio
Vue and svelte people can't relate to this one as their framework have their own extensions which...
Date published

Why You Might Still Pick Next.js Over TanStack Start From a TanStack Start...

A complete theme management system for React applications with SSR support! ✨ 🌟...

Modify your nextauth client and add a callbacks section that will get the access token from the...
Vue and svelte people can't relate to this one as their framework have their own extensions which makes them show up as languages in Github .
To list how many projects we've used react, Solid or Preact in , we'll need to check that package.json in our Github
first let's create a helper that will determine how we group the libraries
1export interface RequiredDecodedPackageJson {2 name: string;3 private?: boolean;4 version: string;5 type?: string;6 scripts: KeyStringObject;7 dependencies: KeyStringObject;8 devDependencies: KeyStringObject;9 [key: string]: any | undefined;10}11export type TPkgType =12 | "React+Vite"13 | "React+Relay"14 | "Rakkasjs"15 | "Nextjs"16 | "Nodejs"17 | "Deno"18 | "React-native"19 | "Bun"20 | "Others";2122export function pkgTypeCondition(pkg: RequiredDecodedPackageJson): {23 pkg_type: TPkgType;24 condition: boolean;25} {26 const pkgs_string = JSON.stringify({27 ...pkg.dependencies,28 ...pkg.devDependencies,29 });30 if (pkgs_string.includes("rakkas")) {31 return { pkg_type: "Rakkasjs", condition: true };32 }33 if (pkgs_string.includes("react-native")) {34 return { pkg_type: "React-native", condition: true };35 }3637 if (pkg.dependencies?.next) {38 return { pkg_type: "Nextjs", condition: true };39 }4041 if (pkg.dependencies?.react && pkg.dependencies?.["react-relay"]) {42 return { pkg_type: "React+Relay", condition: true };43 }4445 if (pkg.devDependencies?.vite && pkg.dependencies?.react) {46 return { pkg_type: "React+Vite", condition: true };47 }4849 if (50 pkgs_string.includes("nodemon") ||51 pkgs_string.includes("tsup") ||52 pkgs_string.includes("fastify") ||53 pkgs_string.includes("express") ||54 pkgs_string.includes("nestjs") ||55 pkgs_string.includes("mongoose")56 ) {57 return { pkg_type: "Nodejs", condition: true };58 }59 return { pkg_type: "Others", condition: false };60}
now let's setup a function to fetch the package.json for a repository
```ts
// get repository package.json
// # TODO : check the repository language and early return if it's // not JS or TS
export async function getOneRepoPackageJson(
owner_repo: string,
viewer_token: string,
) {
try {
const headersList = {
Authorization: bearer ${viewer_token},
Accept: "application/vnd.github+json",
};
// is nodejs based
const response = await fetch(
https://api.github.com/repos/${owner_repo}/contents/package.json,
{
method: "GET",
headers: headersList,
},
);
const data = await response.json();
// console.log("package.json data ==== ", data);
if (data && data.encoding === "base64" && data.content) {
const stringBuffer = new TextDecoder().decode( base64ToUint8Array(data.content), ); const pkgjson = JSON.parse(stringBuffer) as DecodedPackageJson; return await modifyPackageJson(pkgjson); }
const deno_lock_response = await fetch(
https://api.github.com/repos/${owner_repo}/contents/deno.lock,
{
method: "GET",
headers: headersList,
},
);
const deno_lock_data = await deno_lock_response.json();
if (
!("documentation_url" in deno_lock_data && "message" in deno_lock_data)
) {
return {
name: owner_repo.split("/")[1],
favdeps: ["deno"],
dependencies: { deno: "latest" },
pkg_type: "Deno",
} as any as DecodedPackageJson;
}
const deno_json_response = await fetch(
https://api.github.com/repos/${owner_repo}/contents/deno.json,
{
method: "GET",
headers: headersList,
},
);
const deno_json_data = await deno_json_response.json();
if (
!("documentation_url" in deno_json_data && "message" in deno_json_data)
) {
return {
name: owner_repo.split("/")[1],
favdeps: ["deno"],
dependencies: { deno: "latest" },
pkg_type: "Deno",
} as any as DecodedPackageJson;
}
const bun_lock_response = await fetch(
https://api.github.com/repos/${owner_repo}/contents/bun.lock,
{
method: "GET",
headers: headersList,
},
);
const bun_lock_data = await bun_lock_response.json();
if (!("documentation_url" in bun_lock_data && "message" in bun_lock_data)) {
return {
name: owner_repo.split("/")[1],
favdeps: ["bun"],
dependencies: { bun: "latest" },
pkg_type: "Bun",
} as any as DecodedPackageJson;
}
return data as DecodedPackageJson;
} catch (error) {
logError("error getOneRepoPackageJson >>>>>>>>>>>> ", error);
return error as DecodedPackageJson;
}
}
1The function will also try to check if the repository is a Deno project by checking for a `deno.json` if it can't find a `package.json`23The base64 encoder can vary between runtimes , the one below works on Deno
ts function base64ToUint8Array(base64: string): Uint8Array { const raw = atob(base64); const array = new Uint8Array(raw.length); for (let i = 0; i < raw.length; i++) { array[i] = raw.charCodeAt(i); } return array; }
12once we get our `package.json` , we can now modify it before saving it in a DB for future reference to avoid doing this step too often and grinding up against Github's rate limits.3
ts // modify package.json to addthe pkg_type export function modifyPackageJson(pgkjson: DecodedPackageJson) { if ("name" in pgkjson) { const typeCondition = pkgTypeCondition(pgkjson); pgkjson["pkg_type"] = typeCondition.pkg_type;
const alldeps = Object.keys(pgkjson.dependencies) .map((key) => { return key.split("^")[0]; }) .concat( Object.keys(pgkjson.devDependencies).map((key) => { return key.split("^")[0]; }), );
const favdeps = mostFaveDepsList.filter((key) => { return alldeps.find((dep) => { return dep.includes(key); }); });
pgkjson["favdeps"] = favdeps; return pgkjson; } return pgkjson; }
1234And with that all we need is to run our [recursive repo fetcher](https://gist.github.com/tigawanna/3ae4b14f2820c50f5b3d993c4f8773c6) and for every item execute this function56
ts const viewer_token"ghp_0Bw5w5H5e8u7v9wjVBw5w5H5e8uw7W1e3z5n2vZr4P6qPq"; const repos = await fetchReposRecursivelyWithGQL({ viewer_token: gh_token, }); const reposPkgJson: DecodedPackageJson[] = [];
if (all_repos.data?.data) { const reposList = all_repos.data?.data.viewer.repositories.edges; for await (const repo of reposList) { const pkgjson = await getOneRepoPackageJson( repo.node.nameWithOwner, viewer_token, ); if ("message" in pkgjson && "documentation_url" in pkgjson) { continue; } if (pkgjson) { pkgjson.languages = repo.node.languages.edges; reposPkgJson.push(pkgjson); } } } return reposPkgJson;
123> ⚠ **Warning:** This method will hit the Github API limit very quickly and you might get timed out.4Deno queues could help at this by staggering the requests [example with Deno queues](https://github.com/tigawanna/library-stats/blob/4569d944082d5f0bb8bfe74e19bba6f5b3a93d7e/routes/stats/helpers/computeLibStats.ts)5 writing the response to a DB is probably a good idea so that we don't have to run this every time67> ℹ️ **Info:** the Deno queues are not working on deploy only locally , where you'll have to write to a remote KV deployed on deploy .8If anyone knows how to make that Deno queue work in deploy let me know I haven't been successful with that910Now we can display them on sour site , but wait , mapping over the items and displaying a simple list looks a little not over engineered , so let's fix that11121314we'll be copying from the TanStack website sponsors section where they had this cool visualization of circles that vary in size based on how big a contributor they are.15161718192021We'll be using [VLSX](https://airbnb.io/visx/docs) by the people at Airbnb22
sh npm i @visx/hierarchy @visx/responsive
12In my case , my data will be in this shape3
ts export interface ViewerLibraries { highlighted_library_stats: Record<string, string>; library_stats: Record<string, string>; framework_stats: Record<string, string>; }
12and we'll3
tsx const languages = Object.entries(libs?.highlighted_library_stats ?? {}) .map(([key, value]) => ({ key, value, }));
// initialize the pack const pack = React.useMemo( () => ({ children: languages, name: "root", radius: 0, distance: 0, }), [languages] );
// initialize the root const root = React.useMemo( () => hierarchy(pack) // d:{key:string;value:number} .sum((d: any) => { // // no("sum", d?.tier?.monthlyPriceInDollars) return 1 + d?.value; }) .sort((a, b) => (b.value ?? 0) - (a.value ?? 0)), [pack] );
12Then we'll map over the items to display them in our components.3
tsx
<div className="w-full h-full flex flex-col items-center gap-2 pt-2">
<div className="w-full">
<ParentSize>
{({ width = 600 }) => { return width < 10 ? null : ( <div style={{ width, height: width, position: "relative", }}> <style dangerouslySetInnerHTML={{ __html: `
.spon-link { transition: all .2s ease; transform: translate(-50%, -50%); }
.spon-link:hover { z-index: 10; transform: translate(-50%, -50%) scale(1.1); }
.spon-link:hover .spon-tooltip { opacity: 1; } `, }} />
<Pack root={root} size={[width, width]} padding={width * 0.005}>
{(packData) => { // // no(" ===== PACK DATA ======= ", packData); const circles = packData.descendants().slice(1); // skip first layer // // no("========= CIRCLES DESCENDANT ======== ", circles); return (
<div>
{[...circles].reverse().map((circ, i) => { const circle = circ as any as Circle; const tooltipX = circle.x > width / 2 ? "left" : "right"; const tooltipY = circle.y > width / 2 ? "top" : "bottom";
return ( <a key={circle-${i}} href={https://github.com/${circle.data.key}} className={ spon-link ` + `absolute shadow-lg bg-white rounded-full z-0 } style={{ left: circle.x, top: circle.y, width: circle.r * 2, height: circle.r * 2, }}> <img key={circle-${i}} className={absolute bg-no-repeat bg-center bg-contain rounded-full w-[95%] h-[95%] dark:w-[100.5%] dark:h-[100.5%] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 `} src={https://avatars0.githubusercontent.com/${ circle.data.key }?v=3&s=${Math.round(circle.r * 2)}`} /> <div className={twMerge( `spon-tooltip absolute text-sm bg-gray-800 text-white p-2 pointer-events-none transform opacity-0 shadow-xl rounded-lg flex flex-col items-center `,
tooltipX == "left" ? left-1/4 -translate-x-full : right-1/4 translate-x-full, tooltipY == "top" ? top-1/4 -translate-y-full : bottom-1/4 translate-y-full )}>
<p className={whitespace-nowrap font-bold}>{circle.data.key}</p>
</div>
</a>
); })}
</div>
); }}
</Pack>
</div>
); }}
</ParentSize>
</div>
</div>
12ℹ️ **Info:** the library types are a little off so we hade to typecast to the correct ones34567A little issue that might need some future over engineering is getting the correct libraries logos , the technique am currently using relies on the repository name matching the NPM package name which isn't always the case8
ts src={https://avatars0.githubusercontent.com/${ circle.data.key }?v=3&s=${Math.round(circle.r * 2)}}
```
A more reliable solution might be to search the repository and save it's open-graph image in in the augment package JSON step.
That's enough over engineering for a day,
In the next part we'll adding a contact me form using NextJS server actions and try to make it as progressively enhancable as possible