* Load a plugin from a marketplace entry based on its source configuration. * * Handles different source types: * - Relative path: Loads from marketplace repo directory * - npm/github/url: Caches then loads from cache * * @param installedVersion - Version from installed_plugins.json, used as a
( entry: PluginMarketplaceEntry, marketplaceInstallLocation: string, pluginId: string, enabled: boolean, errorsOut: PluginError[], installedVersion?: string, )
| 2189 | * Errors include missing component files and hook load failures. |
| 2190 | */ |
| 2191 | async function loadPluginFromMarketplaceEntry( |
| 2192 | entry: PluginMarketplaceEntry, |
| 2193 | marketplaceInstallLocation: string, |
| 2194 | pluginId: string, |
| 2195 | enabled: boolean, |
| 2196 | errorsOut: PluginError[], |
| 2197 | installedVersion?: string, |
| 2198 | ): Promise<LoadedPlugin | null> { |
| 2199 | logForDebugging( |
| 2200 | `Loading plugin ${entry.name} from source: ${jsonStringify(entry.source)}`, |
| 2201 | ) |
| 2202 | let pluginPath: string |
| 2203 | |
| 2204 | if (typeof entry.source === 'string') { |
| 2205 | // Relative path - resolve relative to marketplace install location |
| 2206 | const marketplaceDir = ( |
| 2207 | await stat(marketplaceInstallLocation) |
| 2208 | ).isDirectory() |
| 2209 | ? marketplaceInstallLocation |
| 2210 | : join(marketplaceInstallLocation, '..') |
| 2211 | const sourcePluginPath = join(marketplaceDir, entry.source) |
| 2212 | |
| 2213 | if (!(await pathExists(sourcePluginPath))) { |
| 2214 | const error = new Error(`Plugin path not found: ${sourcePluginPath}`) |
| 2215 | logForDebugging(`Plugin path not found: ${sourcePluginPath}`, { |
| 2216 | level: 'error', |
| 2217 | }) |
| 2218 | logError(error) |
| 2219 | errorsOut.push({ |
| 2220 | type: 'generic-error', |
| 2221 | source: pluginId, |
| 2222 | error: `Plugin directory not found at path: ${sourcePluginPath}. Check that the marketplace entry has the correct path.`, |
| 2223 | }) |
| 2224 | return null |
| 2225 | } |
| 2226 | |
| 2227 | // Always copy local plugins to versioned cache |
| 2228 | try { |
| 2229 | // Try to load manifest from plugin directory to check for version field first |
| 2230 | const manifestPath = join( |
| 2231 | sourcePluginPath, |
| 2232 | '.claude-plugin', |
| 2233 | 'plugin.json', |
| 2234 | ) |
| 2235 | let pluginManifest: PluginManifest | undefined |
| 2236 | try { |
| 2237 | pluginManifest = await loadPluginManifest( |
| 2238 | manifestPath, |
| 2239 | entry.name, |
| 2240 | entry.source, |
| 2241 | ) |
| 2242 | } catch { |
| 2243 | // Manifest loading failed - will fall back to provided version or git SHA |
| 2244 | } |
| 2245 | |
| 2246 | // Calculate version with fallback order: |
| 2247 | // 1. Plugin manifest version, 2. Marketplace entry version, 3. Git SHA, 4. 'unknown' |
| 2248 | const version = await calculatePluginVersion( |
no test coverage detected