eratic / packages / core / src / lib / schema.ts
380 words
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
 * Shared frontmatter fragments.
 *
 * Every collection across the four sites composes its schema from these, so a
 * field called `summary` means the same thing everywhere and the feed builder
 * can treat all four sites identically.
 *
 * Zod comes from `astro/zod` (Astro 7 bundles Zod 4; importing `zod` directly
 * would give you a second, incompatible copy).
 */

import { z } from 'astro/zod';

/**
 * Present on every entry on every site.
 *
 * `summary` is mandatory on purpose. It is the meta description, the listing
 * blurb, and the text that goes out in a feed when the full body cannot — see
 * the note in lib/feeds.ts. Writing it is the small tax for the thing working
 * everywhere.
 */
export const baseEntry = z.object({
	title: z.string().min(1),
	summary: z.string().min(1).max(400),
	/** Accepts "2026-08-05" or a full ISO timestamp. */
	pubDate: z.coerce.date(),
	updatedDate: z.coerce.date().optional(),
	/**
	 * Drafts build locally but are excluded from listings, feeds and the
	 * sitemap, and are never deployed. Nothing dated in the future publishes
	 * either, so you can queue posts by setting pubDate ahead.
	 */
	draft: z.boolean().default(false),
	tags: z.array(z.string()).default([]),
});

/**
 * Per-entry design overrides.
 *
 * Any token from styles/tokens.css can be re-declared for a single article.
 * The layout writes these onto the article element as inline custom
 * properties, so they cascade to everything inside without a stylesheet edit.
 *
 *   theme:
 *     accent: "oklch(62% 0.19 25)"
 *     surface: "oklch(97% 0.02 85)"
 *     font-prose: "'Fraunces Variable', serif"
 *     measure: "40rem"
 */
export const themeOverride = z
	.record(z.string(), z.string())
	.optional()
	.describe('CSS custom property overrides, without the leading --');

/**
 * How much chrome an entry wants around it.
 *
 *   article — the default reading column, header, nav and footer
 *   wide    — same, but the body starts in the wider track (photo essays)
 *   bare    — a blank page: no header, no footer, no prose styles. For pieces
 *             where the article *is* the design. You get tokens and nothing
 *             else.
 */
export const canvas = z.enum(['article', 'wide', 'bare']).default('article');

/** An image with the alt text that makes it publishable. */
export const imageWithAlt = (image: () => z.ZodType) =>
	z.object({
		src: image(),
		alt: z.string().min(1, 'Alt text is required. See docs/06-privacy.md'),
		caption: z.string().optional(),
	});

static mirror of HEAD · about · clone: git clone https://git.makefuture.ro/eratic.git