3.0.0 - Dependency updates, improved typesafe config, improve typing
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m44s

This commit is contained in:
2025-09-22 15:07:03 -06:00
parent 6c9fabe770
commit 75931d4a43
20 changed files with 1353 additions and 1533 deletions

View File

@@ -1,23 +1,23 @@
import type { APIRoute } from "astro";
import * as TOML from "@iarna/toml";
import { siteConfig } from "../../config/data";
import { config } from "../../config";
export const GET: APIRoute = async ({ request }) => {
try {
// Check if resume TOML content is configured
if (!siteConfig.resume.tomlFile || !siteConfig.resume.tomlFile.trim()) {
if (!config.resumeConfig.tomlFile || !config.resumeConfig.tomlFile.trim()) {
return new Response("Resume not configured", { status: 404 });
}
let tomlContent: string;
// Check if tomlFile is a path (starts with /) or raw content
if (siteConfig.resume.tomlFile.startsWith("/")) {
if (config.resumeConfig.tomlFile.startsWith("/")) {
// It's a file path - fetch it
const url = new URL(request.url);
const baseUrl = `${url.protocol}//${url.host}`;
const response = await fetch(`${baseUrl}${siteConfig.resume.tomlFile}`);
const response = await fetch(`${baseUrl}${config.resumeConfig.tomlFile}`);
if (!response.ok) {
throw new Error(
@@ -28,7 +28,7 @@ export const GET: APIRoute = async ({ request }) => {
tomlContent = await response.text();
} else {
// It's raw TOML content
tomlContent = siteConfig.resume.tomlFile;
tomlContent = config.resumeConfig.tomlFile;
}
const resumeData = TOML.parse(tomlContent);

View File

@@ -1,6 +1,6 @@
import type { APIRoute } from "astro";
import { chromium } from "playwright";
import { siteConfig } from "../../../config/data";
import { config } from "../../../config";
import * as TOML from "@iarna/toml";
// Helper function to fetch and return SVG icon from Simple Icons CDN
@@ -300,7 +300,7 @@ const fetchProfileIcons = async (profiles: any[]) => {
};
const generateResumeHTML = async (data: ResumeData): Promise<string> => {
const resumeConfig = siteConfig.resume;
const resumeConfig = config.resumeConfig;
// Use layout from TOML data, fallback to site config, then to default
const layout = data.layout
? {
@@ -405,19 +405,19 @@ async function generatePDFFromToml(tomlContent: string): Promise<Uint8Array> {
export const GET: APIRoute = async ({ request }) => {
try {
if (!siteConfig.resume.tomlFile || !siteConfig.resume.tomlFile.trim()) {
if (!config.resumeConfig.tomlFile || !config.resumeConfig.tomlFile.trim()) {
return new Response("Resume not configured", { status: 404 });
}
let tomlContent: string;
// Check if tomlFile is a path (starts with /) or raw content
if (siteConfig.resume.tomlFile.startsWith("/")) {
if (config.resumeConfig.tomlFile.startsWith("/")) {
// It's a file path - fetch it
const url = new URL(request.url);
const baseUrl = `${url.protocol}//${url.host}`;
const response = await fetch(`${baseUrl}${siteConfig.resume.tomlFile}`);
const response = await fetch(`${baseUrl}${config.resumeConfig.tomlFile}`);
if (!response.ok) {
throw new Error(
@@ -428,7 +428,7 @@ export const GET: APIRoute = async ({ request }) => {
tomlContent = await response.text();
} else {
// It's raw TOML content
tomlContent = siteConfig.resume.tomlFile;
tomlContent = config.resumeConfig.tomlFile;
}
const pdfBuffer = await generatePDFFromToml(tomlContent);

View File

@@ -3,13 +3,13 @@ import { Image } from "astro:assets";
import SocialLinks from "../components/SocialLinks.astro";
import TechLinks from "../components/TechLinks.astro";
import Layout from "../layouts/Layout.astro";
import { personalInfo, homepageSections } from "../config/data";
import { config } from "../config";
---
<Layout>
<Image
src={personalInfo.profileImage.src}
alt={personalInfo.profileImage.alt}
src={config.personalInfo.profileImage.src}
alt={config.personalInfo.profileImage.alt}
width={300}
height={300}
layout="constrained"
@@ -18,24 +18,22 @@ import { personalInfo, homepageSections } from "../config/data";
style="max-width: 12rem; width: 100%;"
/>
<h1
class="text-primary text-4xl sm:text-6xl font-bold text-center"
>
{personalInfo.name}
<h1 class="text-primary text-4xl sm:text-6xl font-bold text-center">
{config.personalInfo.name}
</h1>
<h2 class="text-xl sm:text-3xl font-bold text-center mx-6">
{personalInfo.tagline}
{config.personalInfo.tagline}
</h2>
<h3 class="text-lg sm:text-2xl font-bold">
{homepageSections.socialLinks.title}
{config.homepageSections.socialLinks.title}
</h3>
<SocialLinks />
<h3 class="text-lg sm:text-2xl font-bold">
{homepageSections.techStack.title}
{config.homepageSections.techStack.title}
</h3>
<TechLinks />

View File

@@ -18,7 +18,7 @@ const { Content } = await post.render();
---
<Layout>
<div class="min-h-screen p-4 md:p-8">
<div class="w-full p-4 md:p-8">
<div class="max-w-3xl mx-auto">
<div class="p-4 md:p-8">
<h1 class="text-4xl md:text-5xl font-bold text-primary mb-6">
@@ -45,7 +45,10 @@ const { Content } = await post.render();
</div>
{/* Back button */}
<a href="/posts" class="btn btn-outline btn-primary btn-sm font-bold">
<a
href="/posts"
class="btn btn-outline btn-primary btn-sm font-bold"
>
<Icon name="mdi:arrow-left" class="text-lg" />
Back
</a>

View File

@@ -8,30 +8,30 @@ const posts = await getCollection("posts");
// Sort posts by date, newest first
const sortedPosts = posts.sort(
(a: CollectionEntry<"posts">, b: CollectionEntry<"posts">) =>
new Date(b.data.pubDate).valueOf() - new Date(a.data.pubDate).valueOf(),
(a: CollectionEntry<"posts">, b: CollectionEntry<"posts">) =>
new Date(b.data.pubDate).valueOf() - new Date(a.data.pubDate).valueOf(),
);
---
<Layout>
<div class="min-h-screen min-w-screen p-4 sm:p-8">
<h1
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
>
Posts
</h1>
<div
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
>
{sortedPosts.map((post) => <PostCard post={post} />)}
</div>
<div class="w-full p-4 sm:p-8">
<h1
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
>
Posts
</h1>
<div
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
>
{sortedPosts.map((post) => <PostCard post={post} />)}
</div>
{
sortedPosts.length === 0 && (
<p class="text-center text-gray-500 mt-12">
No posts available yet. Check back soon!
</p>
)
}
</div>
{
sortedPosts.length === 0 && (
<p class="text-center text-gray-500 mt-12">
No posts available yet. Check back soon!
</p>
)
}
</div>
</Layout>

View File

@@ -1,29 +1,32 @@
---
import Layout from "../layouts/Layout.astro";
import ProjectCard from "../components/ProjectCard.astro";
import { projects } from "../config/data";
import { config } from "../config";
---
<Layout>
<div class="min-h-screen min-w-screen p-4 sm:p-8">
<h1
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
>
Projects
</h1>
<div
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
>
{projects.map((project) => <ProjectCard project={project} />)}
<div class="w-full p-4 sm:p-8">
<h1
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
>
Projects
</h1>
<div
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
>
{
config.projects.map((project) => (
<ProjectCard project={project} />
))
}
</div>
{
config.projects.length === 0 && (
<p class="text-center text-gray-500 mt-12">
No projects available yet. Check back soon!
</p>
)
}
</div>
{
projects.length === 0 && (
<p class="text-center text-gray-500 mt-12">
No projects available yet. Check back soon!
</p>
)
}
</div>
</Layout>

View File

@@ -4,7 +4,7 @@ import Layout from "../layouts/Layout.astro";
import ResumeSkills from "../components/ResumeSkills";
import ResumeDownloadButton from "../components/ResumeDownloadButton";
import ResumeSettingsModal from "../components/ResumeSettingsModal";
import { siteConfig } from "../config/data";
import { config } from "../config";
import "../styles/global.css";
import * as TOML from "@iarna/toml";
@@ -57,16 +57,18 @@ interface ResumeData {
let resumeData: ResumeData | undefined = undefined;
let fetchError: string | null = null;
if (!siteConfig.resume.tomlFile || !siteConfig.resume.tomlFile.trim()) {
if (!config.resumeConfig.tomlFile || !config.resumeConfig.tomlFile.trim()) {
return Astro.redirect("/");
}
try {
let tomlContent: string;
if (siteConfig.resume.tomlFile.startsWith("/")) {
if (config.resumeConfig.tomlFile.startsWith("/")) {
const baseUrl = Astro.url.origin;
const response = await fetch(`${baseUrl}${siteConfig.resume.tomlFile}`);
const response = await fetch(
`${baseUrl}${config.resumeConfig.tomlFile}`,
);
if (!response.ok) {
throw new Error(
@@ -76,7 +78,7 @@ try {
tomlContent = await response.text();
} else {
tomlContent = siteConfig.resume.tomlFile;
tomlContent = config.resumeConfig.tomlFile;
}
resumeData = TOML.parse(tomlContent) as unknown as ResumeData;
@@ -86,7 +88,7 @@ try {
}
const data = resumeData;
const resumeConfig = siteConfig.resume;
const resumeConfig = config.resumeConfig;
if (!data) {
return Astro.redirect("/");
@@ -136,26 +138,29 @@ if (!data) {
<ResumeDownloadButton client:load />
{
data.summary && resumeConfig.sections.summary?.enabled && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.summary.title || "Summary"}
</h2>
<div>{data.summary.content}</div>
data.summary &&
resumeConfig.sections.enabled.includes("summary") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.summary?.title ||
"Summary"}
</h2>
<div>{data.summary.content}</div>
</div>
</div>
</div>
)
)
}
{
data.skills &&
data.skills.length > 0 &&
resumeConfig.sections.skills?.enabled && (
resumeConfig.sections.enabled.includes("skills") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.skills.title || "Skills"}
{resumeConfig.sections.skills?.title ||
"Skills"}
</h2>
<ResumeSkills
skills={data.skills.map((skill, index) => ({
@@ -173,11 +178,11 @@ if (!data) {
{
data.experience &&
data.experience.length > 0 &&
resumeConfig.sections.experience?.enabled && (
resumeConfig.sections.enabled.includes("experience") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.experience.title ||
{resumeConfig.sections.experience?.title ||
"Experience"}
</h2>
<div class="space-y-4 sm:space-y-6">
@@ -222,11 +227,11 @@ if (!data) {
{
data.education &&
data.education.length > 0 &&
resumeConfig.sections.education?.enabled && (
resumeConfig.sections.enabled.includes("education") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.education.title ||
{resumeConfig.sections.education?.title ||
"Education"}
</h2>
<div class="space-y-4">
@@ -264,11 +269,11 @@ if (!data) {
{
data.volunteer &&
data.volunteer.length > 0 &&
resumeConfig.sections.volunteer?.enabled && (
resumeConfig.sections.enabled.includes("volunteer") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.volunteer.title ||
{resumeConfig.sections.volunteer?.title ||
"Volunteer Work"}
</h2>
<div class="space-y-4">
@@ -296,11 +301,11 @@ if (!data) {
{
data.awards &&
data.awards.length > 0 &&
resumeConfig.sections.awards?.enabled && (
resumeConfig.sections.enabled.includes("awards") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.awards.title ||
{resumeConfig.sections.awards?.title ||
"Awards & Recognition"}
</h2>
<div class="space-y-4">

View File

@@ -1,11 +1,11 @@
---
import Layout from "../layouts/Layout.astro";
import TalkCard from "../components/TalkCard.astro";
import { talks } from "../config/data";
import { config } from "../config";
---
<Layout>
<div class="min-h-screen min-w-screen p-4 sm:p-8">
<div class="w-full p-4 sm:p-8">
<h1
class="text-3xl sm:text-4xl font-bold text-primary mb-6 sm:mb-8 text-center"
>
@@ -14,11 +14,11 @@ import { talks } from "../config/data";
<div
class="flex flex-row flex-wrap justify-center gap-4 sm:gap-6 max-w-6xl mx-auto"
>
{talks.map((talk) => <TalkCard talk={talk} />)}
{config.talks.map((talk) => <TalkCard talk={talk} />)}
</div>
{
talks.length === 0 && (
config.talks.length === 0 && (
<p class="text-center text-gray-500 mt-12">
No talks available yet. Check back soon!
</p>