this repo has no description
at main 170 lines 5.0 kB view raw
1import * as fs from "node:fs/promises"; 2import { existsSync } from "node:fs"; 3import * as path from "node:path"; 4import { command, positional, string } from "cmd-ts"; 5import { intro, outro, text, spinner, log, note } from "@clack/prompts"; 6import { fileURLToPath } from "node:url"; 7import { dirname } from "node:path"; 8import { findConfig, loadConfig } from "../lib/config"; 9import type { PublisherConfig } from "../lib/types"; 10 11const __filename = fileURLToPath(import.meta.url); 12const __dirname = dirname(__filename); 13const COMPONENTS_DIR = path.join(__dirname, "components"); 14 15const DEFAULT_COMPONENTS_PATH = "src/components"; 16 17const AVAILABLE_COMPONENTS: { name: string; notes?: string }[] = [ 18 { 19 name: "sequoia-comments", 20 notes: 21 `The component will automatically read the document URI from:\n` + 22 `<link rel="site.standard.document" href="at://...">`, 23 }, 24 { 25 name: "sequoia-subscribe", 26 }, 27]; 28 29export const addCommand = command({ 30 name: "add", 31 description: "Add a UI component to your project", 32 args: { 33 componentName: positional({ 34 type: string, 35 displayName: "component", 36 description: "The name of the component to add", 37 }), 38 }, 39 handler: async ({ componentName }) => { 40 intro("Add Sequoia Component"); 41 42 // Validate component name 43 const component = AVAILABLE_COMPONENTS.find( 44 (c) => c.name === componentName, 45 ); 46 if (!component) { 47 log.error(`Component '${componentName}' not found`); 48 log.info("Available components:"); 49 for (const comp of AVAILABLE_COMPONENTS) { 50 log.info(` - ${comp.name}`); 51 } 52 process.exit(1); 53 } 54 55 // Try to load existing config 56 const configPath = await findConfig(); 57 let config: PublisherConfig | null = null; 58 let componentsDir = DEFAULT_COMPONENTS_PATH; 59 60 if (configPath) { 61 try { 62 config = await loadConfig(configPath); 63 if (config.ui?.components) { 64 componentsDir = config.ui.components; 65 } 66 } catch { 67 // Config exists but may be incomplete - that's ok for UI components 68 } 69 } 70 71 // If no UI config, prompt for components directory 72 if (!config?.ui?.components) { 73 log.info("No UI configuration found in sequoia.json"); 74 75 const inputPath = await text({ 76 message: "Where would you like to install components?", 77 placeholder: DEFAULT_COMPONENTS_PATH, 78 defaultValue: DEFAULT_COMPONENTS_PATH, 79 }); 80 81 if (inputPath === Symbol.for("cancel")) { 82 outro("Cancelled"); 83 process.exit(0); 84 } 85 86 componentsDir = inputPath as string; 87 88 // Update or create config with UI settings 89 if (configPath) { 90 const s = spinner(); 91 s.start("Updating sequoia.json..."); 92 try { 93 const configContent = await fs.readFile(configPath, "utf-8"); 94 const existingConfig = JSON.parse(configContent); 95 existingConfig.ui = { components: componentsDir }; 96 await fs.writeFile( 97 configPath, 98 JSON.stringify(existingConfig, null, 2), 99 "utf-8", 100 ); 101 s.stop("Updated sequoia.json with UI configuration"); 102 } catch (error) { 103 s.stop("Failed to update sequoia.json"); 104 log.warn(`Could not update config: ${error}`); 105 } 106 } else { 107 // Create minimal config just for UI 108 const s = spinner(); 109 s.start("Creating sequoia.json..."); 110 const minimalConfig = { 111 ui: { components: componentsDir }, 112 }; 113 await fs.writeFile( 114 path.join(process.cwd(), "sequoia.json"), 115 JSON.stringify(minimalConfig, null, 2), 116 "utf-8", 117 ); 118 s.stop("Created sequoia.json with UI configuration"); 119 } 120 } 121 122 // Resolve components directory 123 const resolvedComponentsDir = path.isAbsolute(componentsDir) 124 ? componentsDir 125 : path.join(process.cwd(), componentsDir); 126 127 // Create components directory if it doesn't exist 128 if (!existsSync(resolvedComponentsDir)) { 129 const s = spinner(); 130 s.start(`Creating ${componentsDir} directory...`); 131 await fs.mkdir(resolvedComponentsDir, { recursive: true }); 132 s.stop(`Created ${componentsDir}`); 133 } 134 135 // Copy the component 136 const sourceFile = path.join(COMPONENTS_DIR, `${componentName}.js`); 137 const destFile = path.join(resolvedComponentsDir, `${componentName}.js`); 138 139 if (!existsSync(sourceFile)) { 140 log.error(`Component source file not found: ${sourceFile}`); 141 log.info("This may be a build issue. Try reinstalling sequoia-cli."); 142 process.exit(1); 143 } 144 145 const s = spinner(); 146 s.start(`Installing ${componentName}...`); 147 148 try { 149 const componentCode = await fs.readFile(sourceFile, "utf-8"); 150 await fs.writeFile(destFile, componentCode, "utf-8"); 151 s.stop(`Installed ${componentName}`); 152 } catch (error) { 153 s.stop("Failed to install component"); 154 log.error(`Error: ${error}`); 155 process.exit(1); 156 } 157 158 // Show usage instructions 159 let notes = 160 `Add to your HTML:\n\n` + 161 `<script type="module" src="${componentsDir}/${componentName}.js"></script>\n` + 162 `<${componentName}></${componentName}>\n`; 163 if (component.notes) { 164 notes += `\n${component.notes}`; 165 } 166 note(notes, "Usage"); 167 168 outro(`${componentName} added successfully!`); 169 }, 170});