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