* Shared body of loadAllPlugins and loadAllPluginsCacheOnly. * * The only difference between the two is which marketplace loader runs — * session plugins, builtins, merge, verifyAndDemote, and cachePluginSettings * are identical (invariants 1-3).
(
marketplaceLoader: () => Promise<{
plugins: LoadedPlugin[]
errors: PluginError[]
}>,
)
| 3153 | * are identical (invariants 1-3). |
| 3154 | */ |
| 3155 | async function assemblePluginLoadResult( |
| 3156 | marketplaceLoader: () => Promise<{ |
| 3157 | plugins: LoadedPlugin[] |
| 3158 | errors: PluginError[] |
| 3159 | }>, |
| 3160 | ): Promise<PluginLoadResult> { |
| 3161 | // Load marketplace plugins and session-only plugins in parallel. |
| 3162 | // getInlinePlugins() is a synchronous state read with no dependency on |
| 3163 | // marketplace loading, so these two sources can be fetched concurrently. |
| 3164 | const inlinePlugins = getInlinePlugins() |
| 3165 | const [marketplaceResult, sessionResult] = await Promise.all([ |
| 3166 | marketplaceLoader(), |
| 3167 | inlinePlugins.length > 0 |
| 3168 | ? loadSessionOnlyPlugins(inlinePlugins) |
| 3169 | : Promise.resolve({ plugins: [], errors: [] }), |
| 3170 | ]) |
| 3171 | // 3. Load built-in plugins that ship with the CLI |
| 3172 | const builtinResult = getBuiltinPlugins() |
| 3173 | |
| 3174 | // Session plugins (--plugin-dir) override installed ones by name, |
| 3175 | // UNLESS the installed plugin is locked by managed settings |
| 3176 | // (policySettings). See mergePluginSources() for details. |
| 3177 | const { plugins: allPlugins, errors: mergeErrors } = mergePluginSources({ |
| 3178 | session: sessionResult.plugins, |
| 3179 | marketplace: marketplaceResult.plugins, |
| 3180 | builtin: [...builtinResult.enabled, ...builtinResult.disabled], |
| 3181 | managedNames: getManagedPluginNames(), |
| 3182 | }) |
| 3183 | const allErrors = [ |
| 3184 | ...marketplaceResult.errors, |
| 3185 | ...sessionResult.errors, |
| 3186 | ...mergeErrors, |
| 3187 | ] |
| 3188 | |
| 3189 | // Verify dependencies. Runs AFTER the parallel load — deps are presence |
| 3190 | // checks, not load-order, so no topological sort needed. Demotion is |
| 3191 | // session-local: does NOT write settings (user fixes intent via /doctor). |
| 3192 | const { demoted, errors: depErrors } = verifyAndDemote(allPlugins) |
| 3193 | for (const p of allPlugins) { |
| 3194 | if (demoted.has(p.source)) p.enabled = false |
| 3195 | } |
| 3196 | allErrors.push(...depErrors) |
| 3197 | |
| 3198 | const enabledPlugins = allPlugins.filter(p => p.enabled) |
| 3199 | logForDebugging( |
| 3200 | `Found ${allPlugins.length} plugins (${enabledPlugins.length} enabled, ${allPlugins.length - enabledPlugins.length} disabled)`, |
| 3201 | ) |
| 3202 | |
| 3203 | // 3. Cache plugin settings for synchronous access by the settings cascade |
| 3204 | cachePluginSettings(enabledPlugins) |
| 3205 | |
| 3206 | return { |
| 3207 | enabled: enabledPlugins, |
| 3208 | disabled: allPlugins.filter(p => !p.enabled), |
| 3209 | errors: allErrors, |
| 3210 | } |
| 3211 | } |
| 3212 |
no test coverage detected