Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion scripts/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
*
* Runs against a live server (default http://localhost:3000, override with
* `BASE`). Designed to be run after `bun run start` — checks sitemap, robots,
* RSS feeds, OG images, canonicals, JSON-LD on posts, and internal links.
* RSS feeds, OG images, canonicals, JSON-LD on posts, internal links, and
* theme-token parity between `src/lib/theme.ts` and `src/app/globals.css`.
*
* Usage:
* bun run start &
Expand All @@ -13,6 +14,10 @@
* Exits 1 if any check fails.
*/

import { readFile } from "node:fs/promises";
import path from "node:path";
import { THEME } from "../src/lib/theme";

const BASE = process.env.BASE ?? "http://localhost:3000";

type CheckResult = {
Expand Down Expand Up @@ -369,6 +374,41 @@ async function checkInternalLinks(
pass("internal links", `${targets.size} checked`);
}

// Asserts that the TS-side palette in `src/lib/theme.ts` matches the
// CSS-side declarations in `globals.css`. Drift here would mean OG image
// generation paints with one shade and the site renders with another —
// exactly the bug this check is here to catch. Currently only
// `THEME.background` has a corresponding CSS variable (`--background` in
// `:root`); `THEME.foreground` is white-on-dark and uses Tailwind's
// built-in `text-white`, so there's nothing to assert against in CSS.
async function checkThemeParity(): Promise<void> {
let css: string;
try {
css = await readFile(
path.join(process.cwd(), "src", "app", "globals.css"),
"utf-8"
);
} catch (err) {
fail(
"theme parity",
`failed to read globals.css: ${err instanceof Error ? err.message : String(err)}`
);
return;
}
const re = new RegExp(
`--background:\\s*${THEME.background.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&")};`,
"i"
);
if (!re.test(css)) {
fail(
"theme parity",
`--background in globals.css does not match THEME.background (${THEME.background})`
);
return;
}
pass("theme parity", `--background = ${THEME.background}`);
}

// The /api/subscribe handler instantiates `new Resend(...)` *inside* the
// request handler so the build doesn't fail when RESEND_API_KEY is unset.
// When the env var is missing it returns 503 with a JSON error body. We
Expand Down Expand Up @@ -435,6 +475,7 @@ async function main(): Promise<void> {
await checkRssXml();
await checkRssJson();
await checkRssAtom();
await checkThemeParity();
await checkSubscribe();

const htmlByPath = new Map<string, string>();
Expand Down
7 changes: 4 additions & 3 deletions src/lib/og.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ImageResponse } from "next/og";
import { siteName } from "@/lib/constants";
import { THEME } from "@/lib/theme";

export const OG_SIZE = { width: 1200, height: 630 } as const;
export const OG_CONTENT_TYPE = "image/png";
Expand Down Expand Up @@ -37,8 +38,8 @@ function OgCard({
flexDirection: "column",
justifyContent: "space-between",
padding: "80px",
backgroundColor: "#0a0a0a",
color: "#ffffff",
backgroundColor: THEME.background,
color: THEME.foreground,
fontFamily: "sans-serif"
}}
>
Expand Down Expand Up @@ -69,7 +70,7 @@ function OgCard({
fontWeight: 300,
lineHeight: 1.08,
letterSpacing: "-0.02em",
color: "#ffffff"
color: THEME.foreground
}}
>
{title}
Expand Down
22 changes: 22 additions & 0 deletions src/lib/theme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Palette source of truth for TS-side consumers (OG image generation,
* scripts). The CSS-side source lives in `src/app/globals.css` under
* `:root` (and `@theme inline` aliases). The two MUST stay in sync —
* `scripts/verify.ts` enforces this in CI for the values that exist on
* both sides.
*
* `foreground` is currently white-on-dark and shows up in the site as the
* built-in Tailwind `text-white` (no `--color-foreground` CSS variable).
* We track it here anyway so the OG components have a single name to
* import, and so a future move to a CSS-side `--color-foreground` token
* has an obvious destination.
*
* If you change a palette value here, change it in `globals.css` too and
* update the verify check's expected values.
*/
export const THEME = {
background: "#101010",
foreground: "#ffffff"
} as const;

export type ThemeKey = keyof typeof THEME;
Loading