source dump of claude code
at main 178 lines 5.7 kB view raw
1import memoize from 'lodash-es/memoize.js' 2import { basename } from 'path' 3import type { OutputStyleConfig } from '../../constants/outputStyles.js' 4import { getPluginErrorMessage } from '../../types/plugin.js' 5import { logForDebugging } from '../debug.js' 6import { 7 coerceDescriptionToString, 8 parseFrontmatter, 9} from '../frontmatterParser.js' 10import { getFsImplementation, isDuplicatePath } from '../fsOperations.js' 11import { extractDescriptionFromMarkdown } from '../markdownConfigLoader.js' 12import { loadAllPluginsCacheOnly } from './pluginLoader.js' 13import { walkPluginMarkdown } from './walkPluginMarkdown.js' 14 15async function loadOutputStylesFromDirectory( 16 outputStylesPath: string, 17 pluginName: string, 18 loadedPaths: Set<string>, 19): Promise<OutputStyleConfig[]> { 20 const styles: OutputStyleConfig[] = [] 21 await walkPluginMarkdown( 22 outputStylesPath, 23 async fullPath => { 24 const style = await loadOutputStyleFromFile( 25 fullPath, 26 pluginName, 27 loadedPaths, 28 ) 29 if (style) styles.push(style) 30 }, 31 { logLabel: 'output-styles' }, 32 ) 33 return styles 34} 35 36async function loadOutputStyleFromFile( 37 filePath: string, 38 pluginName: string, 39 loadedPaths: Set<string>, 40): Promise<OutputStyleConfig | null> { 41 const fs = getFsImplementation() 42 if (isDuplicatePath(fs, filePath, loadedPaths)) { 43 return null 44 } 45 try { 46 const content = await fs.readFile(filePath, { encoding: 'utf-8' }) 47 const { frontmatter, content: markdownContent } = parseFrontmatter( 48 content, 49 filePath, 50 ) 51 52 const fileName = basename(filePath, '.md') 53 const baseStyleName = (frontmatter.name as string) || fileName 54 // Namespace output styles with plugin name, consistent with commands and agents 55 const name = `${pluginName}:${baseStyleName}` 56 const description = 57 coerceDescriptionToString(frontmatter.description, name) ?? 58 extractDescriptionFromMarkdown( 59 markdownContent, 60 `Output style from ${pluginName} plugin`, 61 ) 62 63 // Parse forceForPlugin flag (supports both boolean and string values) 64 const forceRaw = frontmatter['force-for-plugin'] 65 const forceForPlugin = 66 forceRaw === true || forceRaw === 'true' 67 ? true 68 : forceRaw === false || forceRaw === 'false' 69 ? false 70 : undefined 71 72 return { 73 name, 74 description, 75 prompt: markdownContent.trim(), 76 source: 'plugin', 77 forceForPlugin, 78 } 79 } catch (error) { 80 logForDebugging(`Failed to load output style from ${filePath}: ${error}`, { 81 level: 'error', 82 }) 83 return null 84 } 85} 86 87export const loadPluginOutputStyles = memoize( 88 async (): Promise<OutputStyleConfig[]> => { 89 // Only load output styles from enabled plugins 90 const { enabled, errors } = await loadAllPluginsCacheOnly() 91 const allStyles: OutputStyleConfig[] = [] 92 93 if (errors.length > 0) { 94 logForDebugging( 95 `Plugin loading errors: ${errors.map(e => getPluginErrorMessage(e)).join(', ')}`, 96 ) 97 } 98 99 for (const plugin of enabled) { 100 // Track loaded file paths to prevent duplicates within this plugin 101 const loadedPaths = new Set<string>() 102 103 // Load output styles from default output-styles directory 104 if (plugin.outputStylesPath) { 105 try { 106 const styles = await loadOutputStylesFromDirectory( 107 plugin.outputStylesPath, 108 plugin.name, 109 loadedPaths, 110 ) 111 allStyles.push(...styles) 112 113 if (styles.length > 0) { 114 logForDebugging( 115 `Loaded ${styles.length} output styles from plugin ${plugin.name} default directory`, 116 ) 117 } 118 } catch (error) { 119 logForDebugging( 120 `Failed to load output styles from plugin ${plugin.name} default directory: ${error}`, 121 { level: 'error' }, 122 ) 123 } 124 } 125 126 // Load output styles from additional paths specified in manifest 127 if (plugin.outputStylesPaths) { 128 for (const stylePath of plugin.outputStylesPaths) { 129 try { 130 const fs = getFsImplementation() 131 const stats = await fs.stat(stylePath) 132 133 if (stats.isDirectory()) { 134 // Load all .md files from directory 135 const styles = await loadOutputStylesFromDirectory( 136 stylePath, 137 plugin.name, 138 loadedPaths, 139 ) 140 allStyles.push(...styles) 141 142 if (styles.length > 0) { 143 logForDebugging( 144 `Loaded ${styles.length} output styles from plugin ${plugin.name} custom path: ${stylePath}`, 145 ) 146 } 147 } else if (stats.isFile() && stylePath.endsWith('.md')) { 148 // Load single output style file 149 const style = await loadOutputStyleFromFile( 150 stylePath, 151 plugin.name, 152 loadedPaths, 153 ) 154 if (style) { 155 allStyles.push(style) 156 logForDebugging( 157 `Loaded output style from plugin ${plugin.name} custom file: ${stylePath}`, 158 ) 159 } 160 } 161 } catch (error) { 162 logForDebugging( 163 `Failed to load output styles from plugin ${plugin.name} custom path ${stylePath}: ${error}`, 164 { level: 'error' }, 165 ) 166 } 167 } 168 } 169 } 170 171 logForDebugging(`Total plugin output styles loaded: ${allStyles.length}`) 172 return allStyles 173 }, 174) 175 176export function clearPluginOutputStyleCache(): void { 177 loadPluginOutputStyles.cache?.clear?.() 178}