diff --git a/apps/docs/blume.config.ts b/apps/docs/blume.config.ts index 56a2e52..bbfbf48 100644 --- a/apps/docs/blume.config.ts +++ b/apps/docs/blume.config.ts @@ -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 — ` 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///issues/ 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//`, - // 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" }], }, });