diff --git a/docs/docs/pages/config.mdx b/docs/docs/pages/config.mdx index 4acc872..3aaadec 100644 --- a/docs/docs/pages/config.mdx +++ b/docs/docs/pages/config.mdx @@ -1,29 +1,10 @@ +import ConfigTable from '../../src/lib/ConfigTable.tsx' + # Configuration Reference ## `sequoia.json` -| Field | Type | Required | Default | Description | -|-------|------|----------|---------|-------------| -| `siteUrl` | `string` | Yes | - | Base URL of your website | -| `contentDir` | `string` | Yes | - | Directory containing blog post files | -| `publicationUri` | `string` | Yes | - | AT-URI of your publication record | -| `imagesDir` | `string` | No | - | Directory containing cover images | -| `publicDir` | `string` | No | `"./public"` | Static folder for `.well-known` files | -| `outputDir` | `string` | No | - | Built output directory for inject command | -| `pathPrefix` | `string` | No | `"/posts"` | URL path prefix for posts | -| `pdsUrl` | `string` | No | `"https://bsky.social"` | PDS server URL, generated automatically | -| `identity` | `string` | No | - | Which stored identity to use | -| `frontmatter` | `object` | No | - | Custom frontmatter field mappings | -| `frontmatter.slugField` | `string` | No | - | Frontmatter field to use for slug (defaults to filepath) | -| `ignore` | `string[]` | No | - | Glob patterns for files to ignore | -| `removeIndexFromSlug` | `boolean` | No | `false` | Remove `/index` or `/_index` suffix from slugs | -| `stripDatePrefix` | `boolean` | No | `false` | Remove `YYYY-MM-DD-` date prefixes from slugs (Jekyll-style) | -| `pathTemplate` | `string` | No | - | URL path template with tokens (overrides `pathPrefix` + slug) | -| `bluesky` | `object` | No | - | Bluesky posting configuration | -| `bluesky.enabled` | `boolean` | No | `false` | Post to Bluesky when publishing documents (also enables [comments](/comments)) | -| `bluesky.maxAgeDays` | `number` | No | `30` | Only post documents published within this many days | -| `ui` | `object` | No | - | UI components configuration | -| `ui.components` | `string` | No | `"src/components"` | Directory where UI components are installed | + ### Example diff --git a/docs/sequoia.json b/docs/sequoia.json index b098486..110a7f8 100644 --- a/docs/sequoia.json +++ b/docs/sequoia.json @@ -1,4 +1,5 @@ { + "$schema": "../sequoia.schema.json", "siteUrl": "https://sequoia.pub", "contentDir": "docs/pages/blog", "imagesDir": "docs/public", diff --git a/docs/src/lib/ConfigTable.tsx b/docs/src/lib/ConfigTable.tsx new file mode 100644 index 0000000..0e9f79a --- /dev/null +++ b/docs/src/lib/ConfigTable.tsx @@ -0,0 +1,88 @@ +import schema from "../../../sequoia.schema.json" with { type: "json" }; + +type PropertyInfo = { + path: string; + type: string; + required: boolean; + default?: string | number | boolean; + description?: string; +}; + +function extractProperties( + properties: Record, + required: string[], + parentPath: string, + result: PropertyInfo[], +): void { + for (const [key, value] of Object.entries(properties)) { + const prop = value as Record; + const fullPath = parentPath ? `${parentPath}.${key}` : key; + const isRequired = required.includes(key); + + if (prop.properties) { + extractProperties( + prop.properties as Record, + (prop.required as string[]) || [], + fullPath, + result, + ); + } else { + result.push({ + path: fullPath, + type: prop.type, + required: isRequired, + default: prop.default, + description: prop.description, + } as PropertyInfo); + } + } +} + +export default function ConfigTable() { + const rows: PropertyInfo[] = []; + extractProperties( + schema.properties as Record, + schema.required as string[], + "", + rows, + ); + + return ( + + + + + + + + + + + + {rows.map((row) => ( + + + + + + + + ))} + +
FieldTypeRequiredDefaultDescription
+ {row.path} + + {row.type} + {row.required ? "Yes" : ""} + {row.default === undefined ? ( + "-" + ) : ( + + {typeof row.default === "string" + ? `"${row.default}"` + : `${row.default}`} + + )} + {row.description || "—"}
+ ); +} diff --git a/packages/cli/package.json b/packages/cli/package.json index ff7203c..a0e457f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -7,7 +7,8 @@ }, "files": [ "dist", - "README.md" + "README.md", + "sequoia.schema.json" ], "main": "./dist/index.js", "exports": { diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index 44c6a6c..59d4b6f 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -88,6 +88,7 @@ export function generateConfigTemplate(options: { bluesky?: BlueskyConfig; }): string { const config: Record = { + $schema: 'https://tangled.org/stevedylan.dev/sequoia/raw/main/sequoia.schema.json', siteUrl: options.siteUrl, contentDir: options.contentDir, }; diff --git a/sequoia.schema.json b/sequoia.schema.json new file mode 100644 index 0000000..ca152df --- /dev/null +++ b/sequoia.schema.json @@ -0,0 +1,152 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "PublisherConfig", + "type": "object", + "additionalProperties": false, + "required": ["siteUrl", "contentDir", "publicationUri"], + "properties": { + "$schema": { + "type": "string", + "description": "JSON schema hint" + }, + "siteUrl": { + "type": "string", + "format": "uri", + "description": "Base site URL" + }, + "contentDir": { + "type": "string", + "description": "Directory containing content" + }, + "imagesDir": { + "type": "string", + "description": "Directory containing cover images" + }, + "publicDir": { + "type": "string", + "description": "Static/public folder for `.well-known` files", + "default": "public" + }, + "outputDir": { + "type": "string", + "description": "Built output directory for inject command" + }, + "pathPrefix": { + "type": "string", + "description": "URL path prefix for posts", + "default": "/posts" + }, + "publicationUri": { + "type": "string", + "description": "Publication URI" + }, + "pdsUrl": { + "type": "string", + "format": "uri", + "description": "Personal data server URL (PDS)", + "default": "https://bsky.social" + }, + "identity": { + "type": "string", + "description": "Which stored identity to use (matches identifier)" + }, + "frontmatter": { + "type": "object", + "additionalProperties": false, + "description": "Custom frontmatter field mappings", + "properties": { + "title": { + "type": "string", + "description": "Field name for title", + "default": "title" + }, + "description": { + "type": "string", + "description": "Field name for description", + "default": "description" + }, + "publishDate": { + "type": "string", + "description": "Field name for publish date (checks \"publishDate\", \"pubDate\", \"date\", \"createdAt\", and \"created_at\" by default)", + "default": "publishDate" + }, + "coverImage": { + "type": "string", + "description": "Field name for cover image", + "default": "ogImage" + }, + "tags": { + "type": "string", + "description": "Field name for tags", + "default": "tags" + }, + "draft": { + "type": "string", + "description": "Field name for draft status", + "default": "draft" + }, + "slugField": { + "type": "string", + "description": "Frontmatter field to use for slug (if set, uses frontmatter value; otherwise uses filepath)" + } + } + }, + "ignore": { + "type": "array", + "description": "Glob patterns for files to ignore", + "items": { + "type": "string" + } + }, + "removeIndexFromSlug": { + "type": "boolean", + "description": "Remove \"/index\" or \"/_index\" suffix from paths", + "default": false + }, + "stripDatePrefix": { + "type": "boolean", + "description": "Remove YYYY-MM-DD- prefix from filenames (Jekyll-style)", + "default": false + }, + "pathTemplate": { + "type": "string", + "description": "URL path template with tokens like {year}/{month}/{day}/{slug} (overrides pathPrefix + slug)" + }, + "textContentField": { + "type": "string", + "description": "Frontmatter field to use for textContent instead of markdown body" + }, + "bluesky": { + "type": "object", + "additionalProperties": false, + "description": "Optional Bluesky posting configuration", + "required": ["enabled"], + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether Bluesky posting is enabled", + "default": false + }, + "maxAgeDays": { + "type": "integer", + "minimum": 0, + "description": "Only post if published within N days", + "default": 7 + } + } + }, + "ui": { + "type": "object", + "additionalProperties": false, + "description": "Optional UI components configuration", + "properties": { + "components": { + "type": "string", + "description": "Directory to install UI components", + "default": "src/components" + } + }, + "required": ["components"] + } + } +}