mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 21:22:43 +00:00
chore(config): simplify Blume config and add issue frontmatter schema
This commit is contained in:
1 parent
a20be5182f
commit
65f5965a12
1 file changed
+24
-112
+24
-112
@@ -1,88 +1,8 @@
|
||||
import { defineConfig } from "blume";
|
||||
import { hashText } from "blume/core/sources/cache.ts";
|
||||
import type { ContentSource } from "blume/core/sources/types.ts";
|
||||
import type { StandardSchema } from "blume/core/standard-schema.ts";
|
||||
import { custom, filesystem } from "blume/sources";
|
||||
import { githubReleasesSource } from "blume/sources/github-releases.ts";
|
||||
import { filesystem } from "blume/sources";
|
||||
import redirects from "./redirects";
|
||||
|
||||
/**
|
||||
* GitHub releases → /changelog, dated by the real finish instant of the topic
|
||||
* or `Review <N> — <Phase>` issue each release records.
|
||||
*
|
||||
* Blume dates a release entry by `published_at`, which the GitHub API cannot
|
||||
* backdate — so the releases backfilled on one day (apps/cli/
|
||||
* release-topics.ts) would all read that day. Their tags are timever
|
||||
* (`YYYYMMDD.HHMMSS.NNNNNNNNN`, UTC) of the moment the topic or review
|
||||
* finished, so this fetch rewrites each timever release's `published_at` to
|
||||
* that instant before Blume maps it; anything else passes through untouched.
|
||||
* The config-level `githubReleases()` accepts no `fetchImpl`, hence the
|
||||
* `custom()` wrap of the underlying source (the `blume/sources/*.ts` path the
|
||||
* Blume docs use for custom sources), with the context the built-in adapter
|
||||
* would receive.
|
||||
*/
|
||||
const TIMEVER = /^(\d{4})(\d{2})(\d{2})\.(\d{2})(\d{2})(\d{2})\.\d{9}$/;
|
||||
|
||||
const releasesDatedByTag: typeof fetch = async (input, init) => {
|
||||
const res = await fetch(input, init);
|
||||
if (!res.ok) return res;
|
||||
const releases: unknown = await res.json();
|
||||
if (Array.isArray(releases)) {
|
||||
for (const release of releases) {
|
||||
const m =
|
||||
typeof release?.tag_name === "string"
|
||||
? release.tag_name.match(TIMEVER)
|
||||
: null;
|
||||
if (m)
|
||||
release.published_at = `${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}Z`;
|
||||
}
|
||||
}
|
||||
// Blume reads only `ok` and the JSON body, so the original headers (whose
|
||||
// content-length/encoding describe the upstream bytes) are not carried over.
|
||||
return Response.json(releases, { status: res.status });
|
||||
};
|
||||
|
||||
/**
|
||||
* The changelog index tags every row with `changelog.category`, which the
|
||||
* GitHub source sets to "Release" for all of them. release-topics.ts names
|
||||
* each release after its issue, so the title prefix says which kind finished
|
||||
* — a topic's core set, or a Review day block — and the row is tagged
|
||||
* `Topic` / `Review` instead. The category lives both in `data` and in the
|
||||
* staged `raw` frontmatter Astro re-parses, so both are rewritten (and the
|
||||
* hash with them). Wrapped around the source Blume rebuilds via
|
||||
* `withContext`, so the rebuilt one is tagged too.
|
||||
*/
|
||||
const RELEASE_KIND = /^(Topic|Review) \d+ — /;
|
||||
|
||||
const releasesByKind = (source: ContentSource): ContentSource => ({
|
||||
...source,
|
||||
load: async () => {
|
||||
const result = await source.load();
|
||||
for (const entry of result.entries) {
|
||||
const kind =
|
||||
typeof entry.data.title === "string"
|
||||
? entry.data.title.match(RELEASE_KIND)?.[1]
|
||||
: undefined;
|
||||
const changelog = entry.data.changelog;
|
||||
if (
|
||||
!kind ||
|
||||
!changelog ||
|
||||
typeof changelog !== "object" ||
|
||||
!("category" in changelog)
|
||||
)
|
||||
continue;
|
||||
changelog.category = kind;
|
||||
if (entry.raw) {
|
||||
entry.raw = entry.raw.replace(/^( {2}category: ).*$/m, `$1${kind}`);
|
||||
entry.hash = hashText(entry.raw);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
},
|
||||
withContext:
|
||||
source.withContext && ((ctx) => releasesByKind(source.withContext!(ctx))),
|
||||
});
|
||||
|
||||
// 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
|
||||
@@ -114,8 +34,9 @@ const plexMono = {
|
||||
/**
|
||||
* Custom frontmatter, rendered as the badge row by
|
||||
* components/ProblemBadges.astro. Problem pages: `topics:` is LeetCode's topic
|
||||
* tags (written by apps/cli/sync.ts), `video:` the NeetCode walkthrough's
|
||||
* YouTube id (apps/cli/videos.ts). Guide pages (content/guides/, created by
|
||||
* 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). Guide pages (content/appendix/, created by
|
||||
* sync): `list:` is the tag's LeetCode problem list. 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.
|
||||
@@ -153,6 +74,12 @@ const listSchema = optional(
|
||||
typeof value === "string" &&
|
||||
/^https:\/\/leetcode\.com\/problem-list\/[\w-]+\/$/.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",
|
||||
@@ -168,11 +95,17 @@ export default defineConfig({
|
||||
lastModified: "git",
|
||||
redirects,
|
||||
frontmatter: {
|
||||
extend: { topics: topicsSchema, video: videoSchema, list: listSchema },
|
||||
extend: {
|
||||
topics: topicsSchema,
|
||||
video: videoSchema,
|
||||
list: listSchema,
|
||||
issue: issueSchema,
|
||||
},
|
||||
},
|
||||
seo: {
|
||||
og: { enabled: true },
|
||||
rss: { enabled: true, types: ["changelog"] },
|
||||
// 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,
|
||||
@@ -192,30 +125,11 @@ export default defineConfig({
|
||||
llmsTxt: true,
|
||||
mcp: { enabled: false },
|
||||
},
|
||||
// The only content source. `/changelog` is a custom page (pages/
|
||||
// changelog.astro) reading the GitHub releases itself — one timeline, no
|
||||
// per-release pages, so nothing of it enters the sidebar or prev/next.
|
||||
content: {
|
||||
sources: [
|
||||
filesystem({ root: "content" }),
|
||||
custom(
|
||||
releasesByKind(
|
||||
githubReleasesSource(
|
||||
{
|
||||
name: "changelog",
|
||||
prefix: "changelog",
|
||||
owner: "prdlk",
|
||||
repo: "leetcode",
|
||||
fetchImpl: releasesDatedByTag,
|
||||
},
|
||||
// What resolve.ts hands the built-in adapter: `.blume/cache/<name>/`,
|
||||
// and a refresh on every load (the source reads nothing else).
|
||||
{
|
||||
projectRoot: import.meta.dirname,
|
||||
cacheDir: `${import.meta.dirname}/.blume/cache/changelog`,
|
||||
mode: "build",
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
sources: [filesystem({ root: "content" })],
|
||||
},
|
||||
theme: {
|
||||
fonts: { display: inter, body: inter, mono: plexMono },
|
||||
@@ -225,9 +139,7 @@ export default defineConfig({
|
||||
sidebar: {
|
||||
display: "flat", // "flat" | "group" | "page"
|
||||
},
|
||||
tabs: [
|
||||
{ label: "Guides", path: "/guides" },
|
||||
{ label: "Changelog", path: "/changelog" },
|
||||
],
|
||||
// A plain header link on the right, not a tab.
|
||||
actions: [{ href: "/changelog", label: "Changelog" }],
|
||||
},
|
||||
});
|
||||
Reference in new issue
Block a user