feat: add json schema
da42a8c8
6 file(s) · +247 −23
| 1 | + | import ConfigTable from '../../src/lib/ConfigTable.tsx' |
|
| 2 | + | ||
| 1 | 3 | # Configuration Reference |
|
| 2 | 4 | ||
| 3 | 5 | ## `sequoia.json` |
|
| 4 | 6 | ||
| 5 | - | | Field | Type | Required | Default | Description | |
|
| 6 | - | |-------|------|----------|---------|-------------| |
|
| 7 | - | | `siteUrl` | `string` | Yes | - | Base URL of your website | |
|
| 8 | - | | `contentDir` | `string` | Yes | - | Directory containing blog post files | |
|
| 9 | - | | `publicationUri` | `string` | Yes | - | AT-URI of your publication record | |
|
| 10 | - | | `imagesDir` | `string` | No | - | Directory containing cover images | |
|
| 11 | - | | `publicDir` | `string` | No | `"./public"` | Static folder for `.well-known` files | |
|
| 12 | - | | `outputDir` | `string` | No | - | Built output directory for inject command | |
|
| 13 | - | | `pathPrefix` | `string` | No | `"/posts"` | URL path prefix for posts | |
|
| 14 | - | | `pdsUrl` | `string` | No | `"https://bsky.social"` | PDS server URL, generated automatically | |
|
| 15 | - | | `identity` | `string` | No | - | Which stored identity to use | |
|
| 16 | - | | `frontmatter` | `object` | No | - | Custom frontmatter field mappings | |
|
| 17 | - | | `frontmatter.slugField` | `string` | No | - | Frontmatter field to use for slug (defaults to filepath) | |
|
| 18 | - | | `ignore` | `string[]` | No | - | Glob patterns for files to ignore | |
|
| 19 | - | | `removeIndexFromSlug` | `boolean` | No | `false` | Remove `/index` or `/_index` suffix from slugs | |
|
| 20 | - | | `stripDatePrefix` | `boolean` | No | `false` | Remove `YYYY-MM-DD-` date prefixes from slugs (Jekyll-style) | |
|
| 21 | - | | `pathTemplate` | `string` | No | - | URL path template with tokens (overrides `pathPrefix` + slug) | |
|
| 22 | - | | `bluesky` | `object` | No | - | Bluesky posting configuration | |
|
| 23 | - | | `bluesky.enabled` | `boolean` | No | `false` | Post to Bluesky when publishing documents (also enables [comments](/comments)) | |
|
| 24 | - | | `bluesky.maxAgeDays` | `number` | No | `30` | Only post documents published within this many days | |
|
| 25 | - | | `ui` | `object` | No | - | UI components configuration | |
|
| 26 | - | | `ui.components` | `string` | No | `"src/components"` | Directory where UI components are installed | |
|
| 7 | + | <ConfigTable /> |
|
| 27 | 8 | ||
| 28 | 9 | ### Example |
|
| 29 | 10 |
| 1 | 1 | { |
|
| 2 | + | "$schema": "../sequoia.schema.json", |
|
| 2 | 3 | "siteUrl": "https://sequoia.pub", |
|
| 3 | 4 | "contentDir": "docs/pages/blog", |
|
| 4 | 5 | "imagesDir": "docs/public", |
| 1 | + | import schema from "../../../sequoia.schema.json" with { type: "json" }; |
|
| 2 | + | ||
| 3 | + | type PropertyInfo = { |
|
| 4 | + | path: string; |
|
| 5 | + | type: string; |
|
| 6 | + | required: boolean; |
|
| 7 | + | default?: string | number | boolean; |
|
| 8 | + | description?: string; |
|
| 9 | + | }; |
|
| 10 | + | ||
| 11 | + | function extractProperties( |
|
| 12 | + | properties: Record<string, unknown>, |
|
| 13 | + | required: string[], |
|
| 14 | + | parentPath: string, |
|
| 15 | + | result: PropertyInfo[], |
|
| 16 | + | ): void { |
|
| 17 | + | for (const [key, value] of Object.entries(properties)) { |
|
| 18 | + | const prop = value as Record<string, unknown>; |
|
| 19 | + | const fullPath = parentPath ? `${parentPath}.${key}` : key; |
|
| 20 | + | const isRequired = required.includes(key); |
|
| 21 | + | ||
| 22 | + | if (prop.properties) { |
|
| 23 | + | extractProperties( |
|
| 24 | + | prop.properties as Record<string, unknown>, |
|
| 25 | + | (prop.required as string[]) || [], |
|
| 26 | + | fullPath, |
|
| 27 | + | result, |
|
| 28 | + | ); |
|
| 29 | + | } else { |
|
| 30 | + | result.push({ |
|
| 31 | + | path: fullPath, |
|
| 32 | + | type: prop.type, |
|
| 33 | + | required: isRequired, |
|
| 34 | + | default: prop.default, |
|
| 35 | + | description: prop.description, |
|
| 36 | + | } as PropertyInfo); |
|
| 37 | + | } |
|
| 38 | + | } |
|
| 39 | + | } |
|
| 40 | + | ||
| 41 | + | export default function ConfigTable() { |
|
| 42 | + | const rows: PropertyInfo[] = []; |
|
| 43 | + | extractProperties( |
|
| 44 | + | schema.properties as Record<string, unknown>, |
|
| 45 | + | schema.required as string[], |
|
| 46 | + | "", |
|
| 47 | + | rows, |
|
| 48 | + | ); |
|
| 49 | + | ||
| 50 | + | return ( |
|
| 51 | + | <table className="vocs_Table"> |
|
| 52 | + | <thead> |
|
| 53 | + | <tr className="vocs_TableRow"> |
|
| 54 | + | <th className="vocs_TableHeader">Field</th> |
|
| 55 | + | <th className="vocs_TableHeader">Type</th> |
|
| 56 | + | <th className="vocs_TableHeader">Required</th> |
|
| 57 | + | <th className="vocs_TableHeader">Default</th> |
|
| 58 | + | <th className="vocs_TableHeader">Description</th> |
|
| 59 | + | </tr> |
|
| 60 | + | </thead> |
|
| 61 | + | <tbody> |
|
| 62 | + | {rows.map((row) => ( |
|
| 63 | + | <tr key={row.path} className="vocs_TableRow"> |
|
| 64 | + | <td className="vocs_TableCell"> |
|
| 65 | + | <code className="vocs_Code">{row.path}</code> |
|
| 66 | + | </td> |
|
| 67 | + | <td className="vocs_TableCell"> |
|
| 68 | + | <code className="vocs_Code">{row.type}</code> |
|
| 69 | + | </td> |
|
| 70 | + | <td className="vocs_TableCell">{row.required ? "Yes" : ""}</td> |
|
| 71 | + | <td className="vocs_TableCell"> |
|
| 72 | + | {row.default === undefined ? ( |
|
| 73 | + | "-" |
|
| 74 | + | ) : ( |
|
| 75 | + | <code className="vocs_Code"> |
|
| 76 | + | {typeof row.default === "string" |
|
| 77 | + | ? `"${row.default}"` |
|
| 78 | + | : `${row.default}`} |
|
| 79 | + | </code> |
|
| 80 | + | )} |
|
| 81 | + | </td> |
|
| 82 | + | <td className="vocs_TableCell">{row.description || "—"}</td> |
|
| 83 | + | </tr> |
|
| 84 | + | ))} |
|
| 85 | + | </tbody> |
|
| 86 | + | </table> |
|
| 87 | + | ); |
|
| 88 | + | } |
| 7 | 7 | }, |
|
| 8 | 8 | "files": [ |
|
| 9 | 9 | "dist", |
|
| 10 | - | "README.md" |
|
| 10 | + | "README.md", |
|
| 11 | + | "sequoia.schema.json" |
|
| 11 | 12 | ], |
|
| 12 | 13 | "main": "./dist/index.js", |
|
| 13 | 14 | "exports": { |
| 88 | 88 | bluesky?: BlueskyConfig; |
|
| 89 | 89 | }): string { |
|
| 90 | 90 | const config: Record<string, unknown> = { |
|
| 91 | + | $schema: 'https://tangled.org/stevedylan.dev/sequoia/raw/main/sequoia.schema.json', |
|
| 91 | 92 | siteUrl: options.siteUrl, |
|
| 92 | 93 | contentDir: options.contentDir, |
|
| 93 | 94 | }; |
| 1 | + | { |
|
| 2 | + | "$schema": "http://json-schema.org/draft-07/schema#", |
|
| 3 | + | "title": "PublisherConfig", |
|
| 4 | + | "type": "object", |
|
| 5 | + | "additionalProperties": false, |
|
| 6 | + | "required": ["siteUrl", "contentDir", "publicationUri"], |
|
| 7 | + | "properties": { |
|
| 8 | + | "$schema": { |
|
| 9 | + | "type": "string", |
|
| 10 | + | "description": "JSON schema hint" |
|
| 11 | + | }, |
|
| 12 | + | "siteUrl": { |
|
| 13 | + | "type": "string", |
|
| 14 | + | "format": "uri", |
|
| 15 | + | "description": "Base site URL" |
|
| 16 | + | }, |
|
| 17 | + | "contentDir": { |
|
| 18 | + | "type": "string", |
|
| 19 | + | "description": "Directory containing content" |
|
| 20 | + | }, |
|
| 21 | + | "imagesDir": { |
|
| 22 | + | "type": "string", |
|
| 23 | + | "description": "Directory containing cover images" |
|
| 24 | + | }, |
|
| 25 | + | "publicDir": { |
|
| 26 | + | "type": "string", |
|
| 27 | + | "description": "Static/public folder for `.well-known` files", |
|
| 28 | + | "default": "public" |
|
| 29 | + | }, |
|
| 30 | + | "outputDir": { |
|
| 31 | + | "type": "string", |
|
| 32 | + | "description": "Built output directory for inject command" |
|
| 33 | + | }, |
|
| 34 | + | "pathPrefix": { |
|
| 35 | + | "type": "string", |
|
| 36 | + | "description": "URL path prefix for posts", |
|
| 37 | + | "default": "/posts" |
|
| 38 | + | }, |
|
| 39 | + | "publicationUri": { |
|
| 40 | + | "type": "string", |
|
| 41 | + | "description": "Publication URI" |
|
| 42 | + | }, |
|
| 43 | + | "pdsUrl": { |
|
| 44 | + | "type": "string", |
|
| 45 | + | "format": "uri", |
|
| 46 | + | "description": "Personal data server URL (PDS)", |
|
| 47 | + | "default": "https://bsky.social" |
|
| 48 | + | }, |
|
| 49 | + | "identity": { |
|
| 50 | + | "type": "string", |
|
| 51 | + | "description": "Which stored identity to use (matches identifier)" |
|
| 52 | + | }, |
|
| 53 | + | "frontmatter": { |
|
| 54 | + | "type": "object", |
|
| 55 | + | "additionalProperties": false, |
|
| 56 | + | "description": "Custom frontmatter field mappings", |
|
| 57 | + | "properties": { |
|
| 58 | + | "title": { |
|
| 59 | + | "type": "string", |
|
| 60 | + | "description": "Field name for title", |
|
| 61 | + | "default": "title" |
|
| 62 | + | }, |
|
| 63 | + | "description": { |
|
| 64 | + | "type": "string", |
|
| 65 | + | "description": "Field name for description", |
|
| 66 | + | "default": "description" |
|
| 67 | + | }, |
|
| 68 | + | "publishDate": { |
|
| 69 | + | "type": "string", |
|
| 70 | + | "description": "Field name for publish date (checks \"publishDate\", \"pubDate\", \"date\", \"createdAt\", and \"created_at\" by default)", |
|
| 71 | + | "default": "publishDate" |
|
| 72 | + | }, |
|
| 73 | + | "coverImage": { |
|
| 74 | + | "type": "string", |
|
| 75 | + | "description": "Field name for cover image", |
|
| 76 | + | "default": "ogImage" |
|
| 77 | + | }, |
|
| 78 | + | "tags": { |
|
| 79 | + | "type": "string", |
|
| 80 | + | "description": "Field name for tags", |
|
| 81 | + | "default": "tags" |
|
| 82 | + | }, |
|
| 83 | + | "draft": { |
|
| 84 | + | "type": "string", |
|
| 85 | + | "description": "Field name for draft status", |
|
| 86 | + | "default": "draft" |
|
| 87 | + | }, |
|
| 88 | + | "slugField": { |
|
| 89 | + | "type": "string", |
|
| 90 | + | "description": "Frontmatter field to use for slug (if set, uses frontmatter value; otherwise uses filepath)" |
|
| 91 | + | } |
|
| 92 | + | } |
|
| 93 | + | }, |
|
| 94 | + | "ignore": { |
|
| 95 | + | "type": "array", |
|
| 96 | + | "description": "Glob patterns for files to ignore", |
|
| 97 | + | "items": { |
|
| 98 | + | "type": "string" |
|
| 99 | + | } |
|
| 100 | + | }, |
|
| 101 | + | "removeIndexFromSlug": { |
|
| 102 | + | "type": "boolean", |
|
| 103 | + | "description": "Remove \"/index\" or \"/_index\" suffix from paths", |
|
| 104 | + | "default": false |
|
| 105 | + | }, |
|
| 106 | + | "stripDatePrefix": { |
|
| 107 | + | "type": "boolean", |
|
| 108 | + | "description": "Remove YYYY-MM-DD- prefix from filenames (Jekyll-style)", |
|
| 109 | + | "default": false |
|
| 110 | + | }, |
|
| 111 | + | "pathTemplate": { |
|
| 112 | + | "type": "string", |
|
| 113 | + | "description": "URL path template with tokens like {year}/{month}/{day}/{slug} (overrides pathPrefix + slug)" |
|
| 114 | + | }, |
|
| 115 | + | "textContentField": { |
|
| 116 | + | "type": "string", |
|
| 117 | + | "description": "Frontmatter field to use for textContent instead of markdown body" |
|
| 118 | + | }, |
|
| 119 | + | "bluesky": { |
|
| 120 | + | "type": "object", |
|
| 121 | + | "additionalProperties": false, |
|
| 122 | + | "description": "Optional Bluesky posting configuration", |
|
| 123 | + | "required": ["enabled"], |
|
| 124 | + | "properties": { |
|
| 125 | + | "enabled": { |
|
| 126 | + | "type": "boolean", |
|
| 127 | + | "description": "Whether Bluesky posting is enabled", |
|
| 128 | + | "default": false |
|
| 129 | + | }, |
|
| 130 | + | "maxAgeDays": { |
|
| 131 | + | "type": "integer", |
|
| 132 | + | "minimum": 0, |
|
| 133 | + | "description": "Only post if published within N days", |
|
| 134 | + | "default": 7 |
|
| 135 | + | } |
|
| 136 | + | } |
|
| 137 | + | }, |
|
| 138 | + | "ui": { |
|
| 139 | + | "type": "object", |
|
| 140 | + | "additionalProperties": false, |
|
| 141 | + | "description": "Optional UI components configuration", |
|
| 142 | + | "properties": { |
|
| 143 | + | "components": { |
|
| 144 | + | "type": "string", |
|
| 145 | + | "description": "Directory to install UI components", |
|
| 146 | + | "default": "src/components" |
|
| 147 | + | } |
|
| 148 | + | }, |
|
| 149 | + | "required": ["components"] |
|
| 150 | + | } |
|
| 151 | + | } |
|
| 152 | + | } |