Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit c987982bb2a6ffcfe719aaff7f7b8d167c0fc43b
parent a7b97f3252ce9d255657c9066dc66b6ade2e6e94
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Mon, 22 Jun 2026 18:39:56 -0400

Fix homepage export build when no docs pages are authored

`output: "export"` rejects the `[slug]` dynamic route when
generateStaticParams() is empty — which happens for a hub with no docs
pages, or one with only a home (`index`) page. Add a built-in fallback
doc (`about`) so homepageDocSlugs() always yields ≥1 param; it's
superseded as soon as any real non-index page is authored. The hub nav
now derives from the same slug set so the built route always has a link.

Verified: export build passes for an empty data dir (fallback /about)
and for authored pages (real /about body); common + homepage typecheck.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Diffstat:
Mcommon/lib/homepagePages.ts | 41++++++++++++++++++++++++++++++++++++++++-
Mhomepage/app/[slug]/page.tsx | 16+++++++---------
Mhomepage/app/components/Header.tsx | 21+++++++++++++++------
3 files changed, 62 insertions(+), 16 deletions(-)

diff --git a/common/lib/homepagePages.ts b/common/lib/homepagePages.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { getPaths, type Paths } from "./paths"; -import { isValidHomepageSlug } from "./homepage"; +import { HOMEPAGE_INDEX_SLUG, isValidHomepageSlug } from "./homepage"; // On-disk store for the hub's Markdown page bodies, one file per page at // homepagePagesDir/<slug>.md. Page ORDER and link LABEL live in the homepage @@ -18,6 +18,18 @@ export type HomepagePage = { body: string; }; +// Built-in fallback doc. `output: "export"` rejects the `[slug]` dynamic route +// when generateStaticParams() is empty, which happens for a hub with no docs +// pages OR one with only a home (`index`) page. To keep the route valid we +// always build at least this one doc; it's superseded the moment any real non- +// index page is authored. See homepageDocSlugs / getHomepageDoc below. +export const DEFAULT_HOMEPAGE_DOC: HomepagePage = { + slug: "about", + body: + "# About\n\nThis is the Archilyzer hub. Edit this page or add new ones in the " + + "editor under **Manage → Homepage**.\n", +}; + function pageFile(paths: Paths, slug: string): string { return path.join(paths.homepagePagesDir, `${slug}${PAGE_EXT}`); } @@ -59,6 +71,33 @@ export function listHomepagePages(paths: Paths = getPaths()): HomepagePage[] { .filter((p): p is HomepagePage => p !== null); } +// Real, authored non-index doc slugs (the ones that get a /<slug> route). +function realDocSlugs(paths: Paths): string[] { + return listHomepagePageSlugs(paths).filter((s) => s !== HOMEPAGE_INDEX_SLUG); +} + +// Doc slugs to route/build: the authored non-index pages, or the single built-in +// fallback when there are none (so the static `[slug]` route always has ≥1 +// param). Use for generateStaticParams and the hub nav. +export function homepageDocSlugs(paths: Paths = getPaths()): string[] { + const real = realDocSlugs(paths); + return real.length ? real : [DEFAULT_HOMEPAGE_DOC.slug]; +} + +// Resolve a doc for rendering: the authored file if present, else the built-in +// fallback when its slug is requested and no real docs exist. Null otherwise. +export function getHomepageDoc( + slug: string, + paths: Paths = getPaths(), +): HomepagePage | null { + const real = getHomepagePage(slug, paths); + if (real) return real; + if (slug === DEFAULT_HOMEPAGE_DOC.slug && realDocSlugs(paths).length === 0) { + return DEFAULT_HOMEPAGE_DOC; + } + return null; +} + export async function writeHomepagePage( slug: string, body: string, diff --git a/homepage/app/[slug]/page.tsx b/homepage/app/[slug]/page.tsx @@ -1,23 +1,22 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; import { getPaths } from "yt-dlp-transcript-common/lib/paths"; -import { HOMEPAGE_INDEX_SLUG } from "yt-dlp-transcript-common/lib/homepage"; import { - getHomepagePage, - listHomepagePageSlugs, + getHomepageDoc, + homepageDocSlugs, } from "yt-dlp-transcript-common/lib/homepagePages"; import { currentHomepage } from "../lib/homepage"; import PageBody from "../components/PageBody"; // Every Markdown page except "index" (which is the home route) gets a static // "/<slug>" route. With output: "export" the full set is enumerated here at -// build; unknown slugs 404. +// build; unknown slugs 404. homepageDocSlugs() always returns ≥1 slug (a built-in +// fallback when no docs are authored) so the export build never sees an empty +// dynamic route. export const dynamicParams = false; export function generateStaticParams(): { slug: string }[] { - return listHomepagePageSlugs(getPaths()) - .filter((slug) => slug !== HOMEPAGE_INDEX_SLUG) - .map((slug) => ({ slug })); + return homepageDocSlugs(getPaths()).map((slug) => ({ slug })); } function labelFor(slug: string): string { @@ -40,8 +39,7 @@ export default async function HomepageDocPage({ params: Promise<{ slug: string }>; }) { const { slug } = await params; - if (slug === HOMEPAGE_INDEX_SLUG) notFound(); - const page = getHomepagePage(slug, getPaths()); + const page = getHomepageDoc(slug, getPaths()); if (!page) notFound(); return ( <div className="flex flex-col gap-2"> diff --git a/homepage/app/components/Header.tsx b/homepage/app/components/Header.tsx @@ -1,14 +1,23 @@ import Link from "next/link"; -import { HOMEPAGE_INDEX_SLUG } from "yt-dlp-transcript-common/lib/homepage"; +import { getPaths } from "yt-dlp-transcript-common/lib/paths"; +import { homepageDocSlugs } from "yt-dlp-transcript-common/lib/homepagePages"; import { currentHomepage } from "../lib/homepage"; -// Top nav for the hub. The header title links home; nav links come from the -// config's ordered `nav` array (the "index" page is the home route, so it's not -// repeated as a nav link), with a trailing always-on "Stats" link to the -// cross-site dashboard. +function titleCase(slug: string): string { + return slug.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); +} + +// Top nav for the hub. The header title links home; doc links come from the +// built doc routes (homepageDocSlugs — the same set the [slug] route builds, +// including the built-in fallback when nothing is authored), labeled via the +// config nav, with a trailing always-on "Stats" link to the cross-site board. export default function Header() { const config = currentHomepage(); - const links = config.nav.filter((n) => n.slug !== HOMEPAGE_INDEX_SLUG); + const labelFor = new Map(config.nav.map((n) => [n.slug, n.label])); + const links = homepageDocSlugs(getPaths()).map((slug) => ({ + slug, + label: labelFor.get(slug) ?? titleCase(slug), + })); return ( <header className="border-b border-zinc-200 dark:border-zinc-800 bg-white/70 dark:bg-zinc-900/70 backdrop-blur sticky top-0 z-20"> <div className="max-w-6xl mx-auto px-4 py-3 flex items-center justify-between gap-3">