Files
leetcode/apps/docs/blume.config.ts
T

153 lines
4.9 KiB
TypeScript

import { defineConfig } from "blume";
import type { StandardSchema } from "blume/core/standard-schema.ts";
import { filesystem } from "blume/sources";
import { APPENDIX } from "./appendix";
import redirects from "./redirects";
// Blume's defaults (`inter`, `ibm-plex-mono`) are fetched from fonts.gstatic.com
// on every build, which has timed out on the Pages runner and, fully offline,
// silently ships no fonts at all. The same two families from Fontsource, as
// local files, keep the build network-free. Blume resolves `src` from this
// directory; the workspace links these packages under ./node_modules.
const inter = {
name: "Inter",
variants: [
{
src: "./node_modules/@fontsource-variable/inter/files/inter-latin-wght-normal.woff2",
weight: "100..900",
},
],
};
const plexMono = {
name: "IBM Plex Mono",
variants: [
{
src: "./node_modules/@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-400-normal.woff2",
weight: 400,
},
{
src: "./node_modules/@fontsource/ibm-plex-mono/files/ibm-plex-mono-latin-500-normal.woff2",
weight: 500,
},
],
};
/**
* Custom frontmatter, rendered as the badge row by
* components/ProblemBadges.astro. Problem pages: `topics:` is LeetCode's topic
* tags and `issue:` the problem's GitHub issue URL (both written by
* apps/cli/sync.ts), `video:` the NeetCode walkthrough's
* YouTube id (apps/cli/videos.ts). Blume rejects undeclared keys and takes
* any Standard Schema; these are hand-rolled so the docs workspace needs no
* zod. All optional — every other page carries none.
*/
const optional = <T>(
message: string,
valid: (value: unknown) => value is T,
): StandardSchema<unknown, T | undefined> => ({
"~standard": {
version: 1,
vendor: "prdlk",
validate: (value) =>
value === undefined || valid(value)
? { value }
: { issues: [{ message }] },
},
});
const topicsSchema = optional(
"expected a list of { name, slug } LeetCode topic tags",
(value): value is { name: string; slug: string }[] =>
Array.isArray(value) &&
value.every(
(t) => typeof t?.name === "string" && typeof t?.slug === "string",
),
);
const videoSchema = optional(
"expected an 11-character YouTube video id",
(value): value is string =>
typeof value === "string" && /^[\w-]{11}$/.test(value),
);
const issueSchema = optional(
"expected a https://github.com/<owner>/<repo>/issues/<n> URL",
(value): value is string =>
typeof value === "string" &&
/^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/issues\/\d+$/.test(value),
);
export default defineConfig({
title: "prdlk",
description: "Documentation for my LeetCode solutions",
logo: "./public/logo.svg",
deployment: {
site: "https://lc.prd.lk",
},
github: {
owner: "prdlk",
repo: "leetcode",
},
lastModified: "git",
// Sync's `/<num>` short links, then the Appendix's former per-term pages
// (`/appendix/<slug>`, published until the glossary became one page) onto
// their cards, so old links and search results still land.
redirects: [
...redirects,
...Object.keys(APPENDIX).map((slug) => ({
from: `/appendix/${slug}`,
to: `/appendix#${slug}`,
})),
],
frontmatter: {
extend: {
topics: topicsSchema,
video: videoSchema,
issue: issueSchema,
},
},
seo: {
og: { enabled: true },
// No `rss`: the changelog is one custom page (pages/changelog.astro), not
// `type: changelog` entries, so a changelog feed would have nothing in it.
sitemap: true,
robots: true,
structuredData: true,
x: { handle: "@prdlk" },
},
// Blume's own assistant route is server-rendered (it reads the provider key
// per request), which a static Pages build cannot ship. The SRS Worker
// answers instead (apps/api/src/ask.ts: OpenRouter, grounded on this site's
// llms-full.txt, which `agents.llmsTxt` publishes).
ai: {
assistant: {
enabled: true,
endpoint: "https://srs-api.prdlk.workers.dev/ask",
},
},
agents: {
llmsTxt: true,
mcp: { enabled: false },
},
// The only content source. `/changelog` and `/appendix` are custom pages
// (pages/*.astro): `/changelog` reads the GitHub releases itself — one
// timeline, no per-release pages — and `/appendix` is one glossary page
// built from appendix.ts, so neither enters the sidebar tree or prev/next.
content: {
sources: [filesystem({ root: "content" })],
},
theme: {
fonts: { display: inter, body: inter, mono: plexMono },
},
navigation: {
repo: "https://github.com/prdlk/leetcode",
sidebar: {
display: "flat", // "flat" | "group" | "page"
},
// The two custom pages (pages/*.astro) as header tabs. Neither is a
// content folder, so the tabs scope nothing out of the sidebar tree.
tabs: [
{ label: "Changelog", path: "/changelog" },
{ label: "Appendix", path: "/appendix" },
],
},
});