this repo has no description
1#!/usr/bin/env bun
2import { access, mkdir, writeFile } from "node:fs/promises";
3import { dirname, join, resolve } from "node:path";
4import { Command } from "commander";
5import {
6 exportFromConfig,
7 findConfigFile,
8 loadExportConfig,
9} from "@sitebase/core";
10
11const program = new Command();
12
13program
14 .name("sitebase")
15 .description("CLI tools for standard.site publications")
16 .version("0.0.1");
17
18program
19 .command("export")
20 .description("Export a publication to markdown files")
21 .option(
22 "-c, --config <file>",
23 "Path to config file (auto-discovers sitebase.config.{ts,js} if not specified)",
24 )
25 .action(async (options: { config?: string }) => {
26 try {
27 // Find config file
28 let configPath = options.config ? resolve(options.config) : null;
29
30 if (!configPath) {
31 // Auto-discover
32 configPath = await findConfigFile(process.cwd());
33 if (!configPath) {
34 console.error(
35 "No config file found. Create sitebase.config.ts or specify with --config",
36 );
37 process.exit(1);
38 }
39 }
40
41 console.log(`Using config: ${configPath}`);
42 const config = await loadExportConfig(configPath);
43
44 console.log(`Exporting publication: ${config.publicationUri}`);
45 console.log(`Export targets: ${config.exports.length}`);
46
47 const configDir = dirname(configPath);
48 const exportResults = await exportFromConfig(config, configDir);
49
50 // Print results for each target
51 for (const [i, result] of exportResults.entries()) {
52 const target = config.exports[i];
53 console.log(`\nTarget ${i + 1}: ${target?.outputDir}`);
54 console.log(` Documents processed: ${result.documentsProcessed}`);
55 console.log(` Documents skipped: ${result.documentsSkipped}`);
56 console.log(` Files written: ${result.filesWritten.length}`);
57
58 if (result.warnings.length > 0) {
59 console.log(` Warnings:`);
60 for (const warning of result.warnings) {
61 console.log(` - ${warning}`);
62 }
63 }
64
65 if (result.filesWritten.length > 0) {
66 console.log(` Files:`);
67 for (const file of result.filesWritten) {
68 console.log(` - ${file}`);
69 }
70 }
71 }
72 } catch (error) {
73 console.error(
74 `Error: ${error instanceof Error ? error.message : String(error)}`,
75 );
76 process.exit(1);
77 }
78 });
79
80program
81 .command("init")
82 .description("Create a sitebase.config.ts file with starter configuration")
83 .action(async () => {
84 const cwd = process.cwd();
85 const configPath = join(cwd, "sitebase.config.ts");
86 const templatesDir = join(cwd, "templates");
87 const templatePath = join(templatesDir, "post.hbs");
88
89 // Check if config already exists
90 try {
91 await access(configPath);
92 console.error("Error: sitebase.config.ts already exists");
93 process.exit(1);
94 } catch {
95 // File doesn't exist, we can proceed
96 }
97
98 // Config file template with comments
99 const configContent = `// sitebase.config.ts
100import type { ExportConfig } from "@sitebase/core";
101import { slugify } from "@sitebase/core";
102
103/**
104 * Sitebase Export Configuration
105 *
106 * This file configures how your AT Protocol publication is exported to markdown files.
107 * For more information, see: https://github.com/sethetter/sitebase
108 */
109const config: ExportConfig = {
110 // The AT URI of your publication (required)
111 // Format: at://did:plc:xxx/site.standard.publication/rkey
112 // Find this in your PDS or use the standard.site dashboard
113 publicationUri: "at://YOUR_DID/site.standard.publication/YOUR_RKEY",
114
115 // Export targets - each entry exports documents to a different location/format
116 // You can have multiple targets to export the same publication different ways
117 exports: [
118 {
119 // Directory where markdown files will be written (relative to this config file)
120 outputDir: "./content",
121
122 // Optional: Only include documents with at least one of these tags
123 // If not specified, all documents are included (except those matching excludeTags)
124 // includeTags: ["post", "article"],
125
126 // Optional: Exclude documents with any of these tags
127 // Defaults to ["draft"] if includeTags is not specified
128 // excludeTags: ["draft", "private"],
129
130 // Function to generate the filename for each document (required)
131 // Receives an object with: title, path, publishedAt, updatedAt, tags, content, etc.
132 filename: (data) => {
133 // Example: "2024-01-15_my-post-title.md"
134 const date = data.publishedAt?.slice(0, 10) || "undated";
135 return \`\${date}_\${slugify(data.title)}.md\`;
136 },
137
138 // Optional: Path to a Handlebars template for content generation
139 // The template receives the same data object as the filename function
140 contentTemplate: "./templates/post.hbs",
141
142 // Optional: Function to generate content (overrides contentTemplate if both specified)
143 // content: (data) => \`---
144 // title: "\${data.title}"
145 // date: \${data.publishedAt}
146 // ---
147 //
148 // \${data.content}
149 // \`,
150 },
151 ],
152};
153
154export default config;
155`;
156
157 // Handlebars template for posts
158 const templateContent = `---
159title: "{{title}}"
160{{#if description}}
161description: "{{description}}"
162{{/if}}
163{{#if publishedAt}}
164date: {{publishedAt}}
165{{/if}}
166{{#if updatedAt}}
167updated: {{updatedAt}}
168{{/if}}
169{{#if tags.length}}
170tags:
171{{#each tags}}
172 - {{this}}
173{{/each}}
174{{/if}}
175---
176
177{{{content}}}
178`;
179
180 try {
181 // Write config file
182 await writeFile(configPath, configContent, "utf-8");
183 console.log("Created sitebase.config.ts");
184
185 // Create templates directory and template file
186 await mkdir(templatesDir, { recursive: true });
187 await writeFile(templatePath, templateContent, "utf-8");
188 console.log("Created templates/post.hbs");
189
190 console.log("\nNext steps:");
191 console.log(
192 " 1. Update publicationUri in sitebase.config.ts with your publication's AT URI",
193 );
194 console.log(" 2. Customize the export targets as needed");
195 console.log(" 3. Run: sitebase export");
196 } catch (error) {
197 console.error(
198 `Error: ${error instanceof Error ? error.message : String(error)}`,
199 );
200 process.exit(1);
201 }
202 });
203
204program.parse();