* Shared tail of both loadPluginFromMarketplaceEntry variants. * * Once pluginPath is resolved (via clone, cache, or installPath lookup), * the rest of the load — manifest probe, createPluginFromPath, marketplace * entry supplementation — is identical. Extracted so the cache-only path * doesn't
( entry: PluginMarketplaceEntry, pluginId: string, enabled: boolean, errorsOut: PluginError[], pluginPath: string, )
| 2418 | * doesn't duplicate ~500 lines. |
| 2419 | */ |
| 2420 | async function finishLoadingPluginFromPath( |
| 2421 | entry: PluginMarketplaceEntry, |
| 2422 | pluginId: string, |
| 2423 | enabled: boolean, |
| 2424 | errorsOut: PluginError[], |
| 2425 | pluginPath: string, |
| 2426 | ): Promise<LoadedPlugin | null> { |
| 2427 | const errors: PluginError[] = [] |
| 2428 | |
| 2429 | // Check if plugin.json exists to determine if we should use marketplace manifest |
| 2430 | const manifestPath = join(pluginPath, '.claude-plugin', 'plugin.json') |
| 2431 | const hasManifest = await pathExists(manifestPath) |
| 2432 | |
| 2433 | const { plugin, errors: pluginErrors } = await createPluginFromPath( |
| 2434 | pluginPath, |
| 2435 | pluginId, |
| 2436 | enabled, |
| 2437 | entry.name, |
| 2438 | entry.strict ?? true, // Respect marketplace entry's strict setting |
| 2439 | ) |
| 2440 | errors.push(...pluginErrors) |
| 2441 | |
| 2442 | // Set sha from source if available (for github and url source types) |
| 2443 | if ( |
| 2444 | typeof entry.source === 'object' && |
| 2445 | 'sha' in entry.source && |
| 2446 | entry.source.sha |
| 2447 | ) { |
| 2448 | plugin.sha = entry.source.sha |
| 2449 | } |
| 2450 | |
| 2451 | // If there's no plugin.json, use marketplace entry as manifest (regardless of strict mode) |
| 2452 | if (!hasManifest) { |
| 2453 | plugin.manifest = { |
| 2454 | ...entry, |
| 2455 | id: undefined, |
| 2456 | source: undefined, |
| 2457 | strict: undefined, |
| 2458 | } as PluginManifest |
| 2459 | plugin.name = plugin.manifest.name |
| 2460 | |
| 2461 | // Process commands from marketplace entry |
| 2462 | if (entry.commands) { |
| 2463 | // Check if it's an object mapping |
| 2464 | const firstValue = Object.values(entry.commands)[0] |
| 2465 | if ( |
| 2466 | typeof entry.commands === 'object' && |
| 2467 | !Array.isArray(entry.commands) && |
| 2468 | firstValue && |
| 2469 | typeof firstValue === 'object' && |
| 2470 | ('source' in firstValue || 'content' in firstValue) |
| 2471 | ) { |
| 2472 | // Object mapping format |
| 2473 | const commandsMetadata: Record<string, CommandMetadata> = {} |
| 2474 | const validPaths: string[] = [] |
| 2475 | |
| 2476 | // Parallelize pathExists checks; process results in order. |
| 2477 | const entries = Object.entries(entry.commands) |
no test coverage detected