Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 5 additions & 16 deletions astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,32 +1,19 @@
// @ts-check
import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";
import sitemap from "@astrojs/sitemap";
import react from "@astrojs/react";
import fastGlob from "fast-glob";

import vercel from "@astrojs/vercel";
import { valid } from "semver";
import { astroExpressiveCode } from "astro-expressive-code";

// https://astro.build/config
export default defineConfig({
site: "https://kubespec.dev",
output: "server",
trailingSlash: "never",

integrations: [
sitemap({
filter: (page) => {
const path = new URL(page).pathname;
const parts = path.split("/").filter(Boolean);
if (parts.length >= 2) {
if (parts[1].startsWith("v1.") || valid(parts[1])) {
return false; // Skip versioned pages, only include latest (which doesn't have a version in the path)
}
}

return true;
},
}),
react(),
astroExpressiveCode({
themes: ["github-light", "github-dark"],
Expand All @@ -39,5 +26,7 @@ export default defineConfig({
plugins: [tailwindcss()],
},

adapter: vercel(),
adapter: vercel({
includeFiles: fastGlob.sync("./content/**/*"),
}),
});
53 changes: 32 additions & 21 deletions src/lib/kube/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,35 @@ export async function listProjects(): Promise<Project[]> {
return cachedProjects;
}

const projects: Project[] = [];
for (const project of ALL_PROJECTS) {
const baseDir = `./content/projects/${project.slug}`;
const entries = await readdir(baseDir, { withFileTypes: true });
const tags = entries
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.filter((tag) =>
project.slug === "kubernetes" ? true : semver.valid(tag) !== null
);
// Parallelize directory reads and tag extraction
const projects: Project[] = await Promise.all(
ALL_PROJECTS.map(async (project) => {
const baseDir = `./content/projects/${project.slug}`;
let entries: Awaited<ReturnType<typeof readdir>> = [];
try {
entries = await readdir(baseDir, { withFileTypes: true });
} catch (e) {
// Directory may not exist, skip
entries = [];
}
const tags = entries
.filter((entry) => entry.isDirectory())
.map((entry) => entry.name)
.filter((tag) =>
project.slug === "kubernetes" ? true : semver.valid(tag) !== null
);

projects.push({
name: project.name,
slug: project.slug,
logo: project.logo,
tags:
project.slug === "kubernetes"
? tags.sort(compareVersions).reverse()
: semver.rsort(tags),
});
}
return {
name: project.name,
slug: project.slug,
logo: project.logo,
tags:
project.slug === "kubernetes"
? tags.sort(compareVersions).reverse()
: semver.rsort(tags),
};
})
);
cachedProjects = projects;
return projects;
}
Expand Down Expand Up @@ -74,7 +82,10 @@ export async function listAllResources(

const latestByKind = new Map<string, Resource>();
for (const resource of resources) {
const key = `${resource.gvk.group}/${resource.gvk.version.substring(0, 2)}/${resource.gvk.kind}`;
const key = `${resource.gvk.group}/${resource.gvk.version.substring(
0,
2
)}/${resource.gvk.kind}`;
const existing = latestByKind.get(key);
if (existing) {
if (compareCRDVersion(resource.gvk.version, existing.gvk.version) > 0) {
Expand Down
49 changes: 0 additions & 49 deletions src/pages/[slug]/[...args].astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,10 @@ import SiteLayout from "@layouts/SiteLayout.astro";
import DefinitionPage from "@components/DefinitionPage.astro";
import {
findProject,
listAllResources,
listProjects,
parseGVKRef,
} from "@lib/kube";
import HomePage from "@components/HomePage.astro";

export async function getStaticPaths() {
const paths: Array<{ params: { slug: string; args: string } }> = [];

const projects = await listProjects();
for (const project of projects) {
if (!project.tags || project.tags.length === 0) {
continue;
}

// For each tag, list all resources and create paths
for (const tag of project.tags) {
const isLatest = tag === project.tags[0];

if (!isLatest) {
paths.push({
params: {
slug: project.slug,
args: tag,
},
});
}

const resources = await listAllResources(project.slug, tag);
for (const resource of resources) {
const gvkRef = [
resource.gvk.group,
resource.gvk.version,
resource.gvk.kind,
]
.filter(Boolean)
.join("/");

const args = isLatest ? [gvkRef] : [tag, gvkRef];

paths.push({
params: {
slug: project.slug,
args: args.join("/"),
},
});
}
}
}

return paths;
}

const { slug, args } = Astro.params;

if (!slug || !args) {
Expand Down
15 changes: 1 addition & 14 deletions src/pages/[slug]/index.astro
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
---
import HomePage from "@components/HomePage.astro";
import SiteLayout from "@layouts/SiteLayout.astro";
import { findProject, listProjects } from "@lib/kube";

export async function getStaticPaths() {
const paths: Array<{ params: { slug: string } }> = [];

const projects = await listProjects();
for (const project of projects) {
paths.push({
params: { slug: project.slug },
});
}

return paths;
}
import { findProject } from "@lib/kube";

const { slug } = Astro.params;
if (!slug) {
Expand Down
40 changes: 40 additions & 0 deletions src/pages/sitemap.xml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { APIRoute } from "astro";
import { listProjects, listAllResources } from "@lib/kube";

export const GET: APIRoute = async () => {
const site = "https://kubespec.dev";
const urls: string[] = [site];

const projects = await listProjects();
for (const project of projects) {
if (!project.tags || project.tags.length === 0) continue;

const latestTag = project.tags[0];

urls.push(`${site}/${project.slug}`);

const resources = await listAllResources(project.slug, latestTag);
for (const resource of resources) {
const gvkRef = [
resource.gvk.group,
resource.gvk.version,
resource.gvk.kind,
]
.filter(Boolean)
.join("/");
urls.push(`${site}/${project.slug}/${gvkRef}`);
}
}

const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.map((url) => ` <url><loc>${url}</loc></url>`).join("\n")}
</urlset>`;

return new Response(xml, {
headers: {
"Content-Type": "application/xml",
"Cache-Control": "public, max-age=3600",
},
});
};