2026-10-05 14:22:28 -04:00
|
|
|
import { defineConfig } from "blume";
|
|
|
|
|
import type { StandardSchema } from "blume/core/standard-schema.ts";
|
|
|
|
|
import { filesystem } from "blume/sources";
|
|
|
|
|
import { APPENDIX } from "./appendix";
|
2026-10-14 15:52:02 -04:00
|
|
|
import { RECENCY, type Company } from "./companies";
|
2026-10-05 14:22:28 -04:00
|
|
|
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,
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
2026-10-14 15:52:02 -04:00
|
|
|
* Custom frontmatter, rendered between the description and the body by
|
2026-10-05 14:22:28 -04:00
|
|
|
* components/ProblemBadges.astro. Problem pages: `topics:` is LeetCode's topic
|
2026-10-14 15:52:02 -04:00
|
|
|
* tags, `issue:` the problem's GitHub issue URL and `companies:` the companies
|
|
|
|
|
* that asked it in the last six months (`companies.ts`; the table under the
|
|
|
|
|
* badge row) — all three written by apps/cli/sync.ts — and `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.
|
2026-10-05 14:22:28 -04:00
|
|
|
*/
|
|
|
|
|
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),
|
|
|
|
|
);
|
2026-10-14 15:52:02 -04:00
|
|
|
const companiesSchema = optional(
|
|
|
|
|
`expected a list of { name, period, frequency } with period one of ${RECENCY.join(" | ")}`,
|
|
|
|
|
(value): value is Company[] =>
|
|
|
|
|
Array.isArray(value) &&
|
|
|
|
|
value.every(
|
|
|
|
|
(c) =>
|
|
|
|
|
typeof c?.name === "string" &&
|
|
|
|
|
RECENCY.includes(c?.period) &&
|
|
|
|
|
typeof c?.frequency === "number",
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-10-05 14:22:28 -04:00
|
|
|
|
|
|
|
|
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,
|
2026-10-14 15:52:02 -04:00
|
|
|
companies: companiesSchema,
|
2026-10-05 14:22:28 -04:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
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 },
|
|
|
|
|
},
|
2026-10-13 09:16:30 -04:00
|
|
|
// The only content source. `/changelog`, `/appendix` and `/progress` are
|
|
|
|
|
// custom pages (pages/*.astro): `/changelog` reads the GitHub releases
|
|
|
|
|
// itself — one timeline, no per-release pages — `/appendix` is one glossary
|
|
|
|
|
// page built from appendix.ts, and `/progress` is the live stats cards, so
|
|
|
|
|
// none enters the sidebar tree or prev/next.
|
2026-10-05 14:22:28 -04:00
|
|
|
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"
|
|
|
|
|
},
|
2026-10-13 09:16:30 -04:00
|
|
|
// The custom pages (pages/*.astro) as header tabs. None is a content
|
|
|
|
|
// folder, so the tabs scope nothing out of the sidebar tree.
|
2026-10-07 13:45:21 -04:00
|
|
|
tabs: [
|
2026-10-07 14:17:38 -04:00
|
|
|
{ label: "Changelog", path: "/changelog" },
|
2026-10-13 09:16:30 -04:00
|
|
|
{ label: "Progress", path: "/progress" },
|
2026-10-07 14:17:38 -04:00
|
|
|
{ label: "Appendix", path: "/appendix" },
|
2026-10-07 13:45:21 -04:00
|
|
|
],
|
2026-10-05 14:22:28 -04:00
|
|
|
},
|
|
|
|
|
});
|