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