sitebase#
Export content from standard.site publications to markdown files.
Sitebase connects to ATProto Personal Data Servers (PDS) to fetch documents from site.standard.publication collections and exports them as markdown files with configurable templates and filtering.
Installation#
bun install
Packages#
This is a monorepo with three packages:
- @sitebase/core - Library for exporting publications and template utilities
- @sitebase/cli - Command-line interface for running exports
- @sitebase/web - Web UI for managing publications and documents
CLI Usage#
# Auto-discover sitebase.config.ts in current directory
sitebase export
# Specify a config file
sitebase export --config ./my-config.ts
The CLI looks for sitebase.config.ts or sitebase.config.js in the current directory. Use -c or --config to specify a different path.
Configuration#
Create a sitebase.config.ts file in your project root:
import type { ExportConfig } from "@sitebase/core";
import { slugify } from "@sitebase/core";
const config: ExportConfig = {
// AT URI of your publication
publicationUri: "at://did:plc:xyz/site.standard.publication/rkey",
// One or more export targets
exports: [
{
outputDir: "./content/posts",
includeTags: ["post"],
excludeTags: ["draft"],
filename: (data) => {
const date = data.publishedAt?.slice(0, 10) || "undated";
return `${date}_${slugify(data.title)}.md`;
},
contentTemplate: "./templates/post.hbs",
},
],
};
export default config;
Config Reference#
ExportConfig#
| Field | Type | Description |
|---|---|---|
publicationUri |
string |
AT URI of the publication (at://did:plc:.../site.standard.publication/rkey) |
exports |
ExportTarget[] |
Array of export targets |
ExportTarget#
| Field | Type | Required | Description |
|---|---|---|---|
outputDir |
string |
Yes | Directory to write output files |
filename |
(data: TemplateData) => string |
Yes | Function to generate filename |
includeTags |
string[] |
No | Only include documents with ANY of these tags |
excludeTags |
string[] |
No | Exclude documents with ANY of these tags |
contentTemplate |
string |
No | Path to Handlebars template file |
content |
(data: TemplateData) => string |
No | Function to generate content (overrides contentTemplate) |
Template Data#
Both filename and content functions receive a TemplateData object:
interface TemplateData {
title: string;
path?: string;
description?: string;
content: string; // Markdown content
tags: string[];
publishedAt?: string; // ISO 8601 date
updatedAt?: string; // ISO 8601 date
publication: {
name: string;
url: string;
description?: string;
};
}
Tag Filtering#
- includeTags: Only documents with at least one matching tag are included
- excludeTags: Documents with any matching tag are excluded
- When neither is specified, documents tagged "draft" are excluded by default
Handlebars Templates#
Content templates use Handlebars with these custom helpers:
| Helper | Usage | Description |
|---|---|---|
slug |
{{slug text}} |
Convert text to URL-safe slug |
dateFormat |
{{dateFormat date "YYYY-MM-DD"}} |
Format date (supports YYYY, MM, DD) |
default |
{{default value fallback}} |
Use fallback if value is empty |
Example template (templates/post.hbs):
---
title: "{{title}}"
date: {{publishedAt}}
slug: {{slug (default path title)}}
{{#if tags.length}}
tags:
{{#each tags}}
- {{this}}
{{/each}}
{{/if}}
---
{{content}}
Using the Content Function#
For full control, use a content function instead of a template:
{
outputDir: "./content",
filename: (data) => `${slugify(data.title)}.md`,
content: (data) => {
return [
"---",
`title: "${data.title}"`,
`date: ${data.publishedAt}`,
"---",
"",
data.content,
].join("\n");
},
}
Multiple Export Targets#
Export the same publication to different locations with different filters:
export default {
publicationUri: "at://did:plc:xyz/site.standard.publication/rkey",
exports: [
{
outputDir: "./content/notes",
includeTags: ["note"],
filename: (data) => `${slugify(data.title)}.md`,
},
{
outputDir: "./content/posts",
includeTags: ["post"],
excludeTags: ["draft"],
filename: (data) => `${data.publishedAt?.slice(0, 10)}_${slugify(data.title)}.md`,
contentTemplate: "./templates/post.hbs",
},
],
} as ExportConfig;
Core Library#
Use @sitebase/core directly in your own scripts:
import { exportPublication, slugify, createHandlebars } from "@sitebase/core";
const result = await exportPublication({
publicationUri: "at://did:plc:xyz/site.standard.publication/rkey",
outputDir: "./output",
filename: (data) => `${slugify(data.title)}.md`,
});
console.log(`Wrote ${result.filesWritten.length} files`);
Exports#
exportPublication(options)- Export a publication to markdown filesexportFromConfig(config, baseDir)- Export using a config objectfindConfigFile(dir)- Findsitebase.config.{ts,js}in directoryloadExportConfig(path)- Load and validate a config fileslugify(text)- Convert text to URL-safe slugcreateHandlebars()- Create Handlebars instance with helpers registeredgenerateContent(hbs, template, data)- Render a Handlebars template
Web UI#
The web package provides a management interface for publications and documents with ATProto OAuth authentication.
cd packages/web
bun run dev
See packages/web/CLAUDE.md for web package details.
License#
MIT