#!/usr/bin/env bun import { access, mkdir, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { Command } from "commander"; import { exportFromConfig, findConfigFile, loadExportConfig, } from "@sitebase/core"; const program = new Command(); program .name("sitebase") .description("CLI tools for standard.site publications") .version("0.0.1"); program .command("export") .description("Export a publication to markdown files") .option( "-c, --config ", "Path to config file (auto-discovers sitebase.config.{ts,js} if not specified)", ) .action(async (options: { config?: string }) => { try { // Find config file let configPath = options.config ? resolve(options.config) : null; if (!configPath) { // Auto-discover configPath = await findConfigFile(process.cwd()); if (!configPath) { console.error( "No config file found. Create sitebase.config.ts or specify with --config", ); process.exit(1); } } console.log(`Using config: ${configPath}`); const config = await loadExportConfig(configPath); console.log(`Exporting publication: ${config.publicationUri}`); console.log(`Export targets: ${config.exports.length}`); const configDir = dirname(configPath); const exportResults = await exportFromConfig(config, configDir); // Print results for each target for (const [i, result] of exportResults.entries()) { const target = config.exports[i]; console.log(`\nTarget ${i + 1}: ${target?.outputDir}`); console.log(` Documents processed: ${result.documentsProcessed}`); console.log(` Documents skipped: ${result.documentsSkipped}`); console.log(` Files written: ${result.filesWritten.length}`); if (result.warnings.length > 0) { console.log(` Warnings:`); for (const warning of result.warnings) { console.log(` - ${warning}`); } } if (result.filesWritten.length > 0) { console.log(` Files:`); for (const file of result.filesWritten) { console.log(` - ${file}`); } } } } catch (error) { console.error( `Error: ${error instanceof Error ? error.message : String(error)}`, ); process.exit(1); } }); program .command("init") .description("Create a sitebase.config.ts file with starter configuration") .action(async () => { const cwd = process.cwd(); const configPath = join(cwd, "sitebase.config.ts"); const templatesDir = join(cwd, "templates"); const templatePath = join(templatesDir, "post.hbs"); // Check if config already exists try { await access(configPath); console.error("Error: sitebase.config.ts already exists"); process.exit(1); } catch { // File doesn't exist, we can proceed } // Config file template with comments const configContent = `// sitebase.config.ts import type { ExportConfig } from "@sitebase/core"; import { slugify } from "@sitebase/core"; /** * Sitebase Export Configuration * * This file configures how your AT Protocol publication is exported to markdown files. * For more information, see: https://github.com/sethetter/sitebase */ const config: ExportConfig = { // The AT URI of your publication (required) // Format: at://did:plc:xxx/site.standard.publication/rkey // Find this in your PDS or use the standard.site dashboard publicationUri: "at://YOUR_DID/site.standard.publication/YOUR_RKEY", // Export targets - each entry exports documents to a different location/format // You can have multiple targets to export the same publication different ways exports: [ { // Directory where markdown files will be written (relative to this config file) outputDir: "./content", // Optional: Only include documents with at least one of these tags // If not specified, all documents are included (except those matching excludeTags) // includeTags: ["post", "article"], // Optional: Exclude documents with any of these tags // Defaults to ["draft"] if includeTags is not specified // excludeTags: ["draft", "private"], // Function to generate the filename for each document (required) // Receives an object with: title, path, publishedAt, updatedAt, tags, content, etc. filename: (data) => { // Example: "2024-01-15_my-post-title.md" const date = data.publishedAt?.slice(0, 10) || "undated"; return \`\${date}_\${slugify(data.title)}.md\`; }, // Optional: Path to a Handlebars template for content generation // The template receives the same data object as the filename function contentTemplate: "./templates/post.hbs", // Optional: Function to generate content (overrides contentTemplate if both specified) // content: (data) => \`--- // title: "\${data.title}" // date: \${data.publishedAt} // --- // // \${data.content} // \`, }, ], }; export default config; `; // Handlebars template for posts const templateContent = `--- title: "{{title}}" {{#if description}} description: "{{description}}" {{/if}} {{#if publishedAt}} date: {{publishedAt}} {{/if}} {{#if updatedAt}} updated: {{updatedAt}} {{/if}} {{#if tags.length}} tags: {{#each tags}} - {{this}} {{/each}} {{/if}} --- {{{content}}} `; try { // Write config file await writeFile(configPath, configContent, "utf-8"); console.log("Created sitebase.config.ts"); // Create templates directory and template file await mkdir(templatesDir, { recursive: true }); await writeFile(templatePath, templateContent, "utf-8"); console.log("Created templates/post.hbs"); console.log("\nNext steps:"); console.log( " 1. Update publicationUri in sitebase.config.ts with your publication's AT URI", ); console.log(" 2. Customize the export targets as needed"); console.log(" 3. Run: sitebase export"); } catch (error) { console.error( `Error: ${error instanceof Error ? error.message : String(error)}`, ); process.exit(1); } }); program.parse();