commit 1853e1f14be3a7c8a16daf6809a147c168ac249b
parent 338ffcafc6624c96040006f47c3aab5590aa5b5f
Author: Damian Myrda <damian@prime8.dev>
Date: Wed, 21 Jan 2026 15:26:38 -0600
refactor: move readme config to projects.json and improve fetch logic
Diffstat:
7 files changed, 77 insertions(+), 130 deletions(-)
diff --git a/web/config/projects.json b/web/config/projects.json
@@ -3,11 +3,12 @@
"id": "janitor",
"name": "janitor",
"subtitle": "Audio classification with AST",
- "tech": [
- "Rust",
- "CUDA"
- ],
+ "tech": ["Rust", "CUDA"],
"url": "https://git.prime8.dev/moncheeta/janitor",
+ "readmeSource": {
+ "url": "https://git.prime8.dev/moncheeta/janitor/-/raw/master/README.md",
+ "baseUrl": "https://git.prime8.dev/moncheeta/janitor/-/raw/master"
+ },
"dateCreated": "2025-09-15",
"dateModified": "2026-01-10"
},
@@ -15,12 +16,12 @@
"id": "summarizer",
"name": "summarizer",
"subtitle": "Audio transcription microservices",
- "tech": [
- "Python",
- "Docker",
- "Kafka"
- ],
+ "tech": ["Python", "Docker", "Kafka"],
"url": "https://git.prime8.dev/moncheeta/summarizer",
+ "readmeSource": {
+ "url": "https://git.prime8.dev/moncheeta/summarizer/-/raw/master/README.md",
+ "baseUrl": "https://git.prime8.dev/moncheeta/summarizer/-/raw/master"
+ },
"dateCreated": "2025-06-01",
"dateModified": "2025-12-20"
},
@@ -28,12 +29,12 @@
"id": "finger",
"name": "finger",
"subtitle": "Mobile auth app",
- "tech": [
- "TypeScript",
- "React Native",
- "Supabase"
- ],
+ "tech": ["TypeScript", "React Native", "Supabase"],
"url": "https://git.prime8.dev/moncheeta/finger",
+ "readmeSource": {
+ "url": "https://git.prime8.dev/moncheeta/finger/-/raw/master/README.md",
+ "baseUrl": "https://git.prime8.dev/moncheeta/finger/-/raw/master"
+ },
"dateCreated": "2025-03-10",
"dateModified": "2025-08-15"
},
@@ -41,10 +42,12 @@
"id": "computer-science-club",
"name": "computer-science-club",
"subtitle": "CS Club website",
- "tech": [
- "Python"
- ],
+ "tech": ["Python"],
"url": "https://git.prime8.dev/moncheeta/computer-science-club",
+ "readmeSource": {
+ "url": "https://git.prime8.dev/moncheeta/computer-science-club/-/raw/master/README.md",
+ "baseUrl": "https://git.prime8.dev/moncheeta/computer-science-club/-/raw/master"
+ },
"dateCreated": "2024-09-01",
"dateModified": "2025-05-20"
}
diff --git a/web/src/components/pages/Projects.astro b/web/src/components/pages/Projects.astro
@@ -1,15 +1,12 @@
---
import rawProjects from "../../../config/projects.json";
import type Project from "../../lib/types/project";
+import { fetchAllReadmes } from "../../lib/readme/fetch";
import { getTagClass, getTechMeta } from "../../lib/utils/tags";
const projects = rawProjects as Project[];
-interface Props {
- readmes?: Record<string, string>;
-}
-
-const { readmes } = Astro.props;
+const readmes = await fetchAllReadmes();
---
<section id="projects" class="page">
diff --git a/web/src/lib/readme/fetch.ts b/web/src/lib/readme/fetch.ts
@@ -1,66 +1,58 @@
import { parseMarkdown, removeFirstH1 } from "./render";
+import rawProjects from "../../../config/projects.json";
+import type Project from "../types/project";
+import type { ReadmeMap, ReadmeSourceUrl } from "../types/readme";
-export async function fetchAllReadmes(): Promise<Record<string, string>> {
- const readmes: Record<string, string> = {};
+const projects = rawProjects as Project[];
- const results = await Promise.allSettled(
- README_CONFIGS.map(async (config) => {
- const result = await fetchWithFallback(config.sources);
- if (result) {
- const fixed = fixRelativePaths(result.content, result.baseUrl);
- const html = parseMarkdown(removeFirstH1(fixed));
- return { name: config.name, html };
- }
- return { name: config.name, html: "" };
- }),
+export async function fetchAllReadmes(): Promise<ReadmeMap> {
+ const results = await Promise.all(
+ projects.map(async (project) => [
+ project.id,
+ await fetchReadmeForProject(project),
+ ]),
);
- for (const result of results) {
- if (result.status === "fulfilled" && result.value.html) {
- readmes[result.value.name] = result.value.html;
- }
- }
-
- return readmes;
+ return Object.fromEntries(results.filter(([, readme]) => readme !== null));
}
export async function fetchReadmeForProject(
- projectId: string,
+ project: Project,
): Promise<string | null> {
- const config = README_CONFIGS.find((c) => c.name === projectId);
- if (!config) return null;
+ const content = await fetchReadmeContent(project.readmeSource);
+ if (!content) return null;
- const result = await fetchWithFallback(config.sources);
- if (result) {
- const fixed = fixRelativePaths(result.content, result.baseUrl);
- return parseMarkdown(removeFirstH1(fixed));
+ return parseMarkdown(removeFirstH1(content));
+}
+
+async function fetchReadmeContent(
+ source: Project["readmeSource"],
+): Promise<string | null> {
+ if (typeof source === "string") {
+ return source;
}
- return null;
+
+ const content = await fetchUrl(source.url);
+ if (!content) return null;
+
+ return fixRelativePaths(source.baseUrl, content);
}
-async function fetchWithFallback(sources: ReadmeSource[]): Promise<{
- content: string;
- baseUrl: string;
-} | null> {
- for (const source of sources) {
- try {
- const response = await fetch(source.url, {
- headers: { "User-Agent": "prime8dev-portfolio" },
- });
- if (response.ok) {
- return {
- content: await response.text(),
- baseUrl: source.baseUrl,
- };
- }
- } catch {
- // Try next source
+async function fetchUrl(url: string): Promise<string | null> {
+ try {
+ const response = await fetch(url, {
+ headers: { "User-Agent": "prime8dev-portfolio" },
+ });
+ if (response.ok) {
+ return await response.text();
}
+ } catch {
+ // Ignore fetch errors
}
return null;
}
-function fixRelativePaths(markdown: string, baseUrl: string): string {
+function fixRelativePaths(baseUrl: string, markdown: string): string {
// Fix relative image paths:  -> 
let fixed = markdown.replace(
/!\[([^\]]*)\]\(\.\/([^)]+)\)/g,
@@ -75,58 +67,3 @@ function fixRelativePaths(markdown: string, baseUrl: string): string {
return fixed;
}
-
-interface ReadmeConfig {
- name: string;
- sources: ReadmeSource[];
-}
-
-interface ReadmeSource {
- url: string;
- baseUrl: string;
-}
-
-const README_CONFIGS: ReadmeConfig[] = [
- {
- name: "janitor",
- sources: [
- {
- url: "https://git.prime8.dev/moncheeta/janitor/-/raw/master/README.md",
- baseUrl: "https://git.prime8.dev/moncheeta/janitor/-/raw/master",
- },
- ],
- },
- {
- name: "summarizer",
- sources: [
- {
- url: "https://git.prime8.dev/moncheeta/summarizer/-/raw/master/README.md",
- baseUrl: "https://git.prime8.dev/moncheeta/summarizer/-/raw/master",
- },
- ],
- },
- {
- name: "finger",
- sources: [
- {
- url: "https://git.prime8.dev/moncheeta/finger/-/raw/master/README.md",
- baseUrl: "https://git.prime8.dev/moncheeta/finger/-/raw/master",
- },
- ],
- },
- {
- name: "computer-science-club",
- sources: [
- {
- url: "https://raw.githubusercontent.com/moncheeta/computer-science-club/main/README.md",
- baseUrl:
- "https://cdn.jsdelivr.net/gh/moncheeta/computer-science-club@main",
- },
- {
- url: "https://raw.githubusercontent.com/moncheeta/computer-science-club/master/README.md",
- baseUrl:
- "https://cdn.jsdelivr.net/gh/moncheeta/computer-science-club@master",
- },
- ],
- },
-];
diff --git a/web/src/lib/types/project.ts b/web/src/lib/types/project.ts
@@ -1,9 +1,12 @@
+import type { ReadmeSource } from "./readme";
+
export default interface Project {
id: string;
name: string;
subtitle: string;
tech: string[];
url: string;
+ readmeSource: ReadmeSource;
dateCreated?: string;
dateModified?: string;
}
diff --git a/web/src/lib/types/readme.ts b/web/src/lib/types/readme.ts
@@ -0,0 +1,10 @@
+import type Project from "./project";
+
+export type ReadmeSource = string | ReadmeSourceUrl;
+
+export interface ReadmeSourceUrl {
+ url: string;
+ baseUrl: string;
+}
+
+export type ReadmeMap = Partial<Record<Project["id"], string>>;
diff --git a/web/src/pages/index.astro b/web/src/pages/index.astro
@@ -1,7 +1,6 @@
---
import Layout from "../layouts/Layout.astro";
import { fetchWakaTimeData } from "../lib/wakatime";
-import { fetchAllReadmes } from "../lib/readme/fetch";
import about from "../../config/about.json";
import currentJob from "../lib/utils/job";
@@ -16,10 +15,7 @@ import ProjectsPage from "../components/pages/Projects.astro";
import BlogPage from "../components/pages/Blog.astro";
import Footer from "../components/Footer.astro";
-const [wakaTimeData, readmes] = await Promise.all([
- fetchWakaTimeData(),
- fetchAllReadmes(),
-]);
+const [wakaTimeData] = await Promise.all([fetchWakaTimeData()]);
---
<Layout
@@ -44,7 +40,7 @@ const [wakaTimeData, readmes] = await Promise.all([
<main id="content">
<AboutPage wakaTimeData={wakaTimeData} />
<ExperiencePage />
- <ProjectsPage readmes={readmes} />
+ <ProjectsPage />
<BlogPage isStandalone={false} />
</main>
diff --git a/web/src/pages/projects/[id].astro b/web/src/pages/projects/[id].astro
@@ -11,8 +11,8 @@ import {
} from "../../lib/schemas/project";
const projects = rawProjects as Project[];
-const project = projects.find((p) => p.id === Astro.params.id);
+const project = projects.find((p) => p.id === Astro.params.id);
if (!project) {
return new Response(null, {
status: 404,
@@ -20,8 +20,9 @@ if (!project) {
});
}
-const readme = await fetchReadmeForProject(project.id);
const keywords = project.tech.join(", ");
+
+const readme = await fetchReadmeForProject(project);
---
<Layout