MCPcopy Index your code
hub / github.com/codeaashu/claude-code / resolveDependencyClosure

Function resolveDependencyClosure

src/utils/plugins/dependencyResolver.ts:95–159  ·  view source on GitHub ↗
(
  rootId: PluginId,
  lookup: (id: PluginId) => Promise<DependencyLookupResult | null>,
  alreadyEnabled: ReadonlySet<PluginId>,
  allowedCrossMarketplaces: ReadonlySet<string> = new Set(),
)

Source from the content-addressed store, hash-verified

93 * @returns Closure to install, or a cycle/not-found/cross-marketplace error
94 */
95export async function resolveDependencyClosure(
96 rootId: PluginId,
97 lookup: (id: PluginId) => Promise<DependencyLookupResult | null>,
98 alreadyEnabled: ReadonlySet<PluginId>,
99 allowedCrossMarketplaces: ReadonlySet<string> = new Set(),
100): Promise<ResolutionResult> {
101 const rootMarketplace = parsePluginIdentifier(rootId).marketplace
102 const closure: PluginId[] = []
103 const visited = new Set<PluginId>()
104 const stack: PluginId[] = []
105
106 async function walk(
107 id: PluginId,
108 requiredBy: PluginId,
109 ): Promise<ResolutionResult | null> {
110 // Skip already-enabled DEPENDENCIES (avoids surprise settings writes),
111 // but NEVER skip the root: installing an already-enabled plugin must
112 // still cache/register it. Without this guard, re-installing a plugin
113 // that's in settings but missing from disk (e.g., cache cleared,
114 // installed_plugins.json stale) would return an empty closure and
115 // `cacheAndRegisterPlugin` would never fire — user sees
116 // "✔ Successfully installed" but nothing materializes.
117 if (id !== rootId && alreadyEnabled.has(id)) return null
118 // Security: block auto-install across marketplace boundaries. Runs AFTER
119 // the alreadyEnabled check — if the user manually installed a cross-mkt
120 // dep, it's in alreadyEnabled and we never reach this.
121 const idMarketplace = parsePluginIdentifier(id).marketplace
122 if (
123 idMarketplace !== rootMarketplace &&
124 !(idMarketplace && allowedCrossMarketplaces.has(idMarketplace))
125 ) {
126 return {
127 ok: false,
128 reason: 'cross-marketplace',
129 dependency: id,
130 requiredBy,
131 }
132 }
133 if (stack.includes(id)) {
134 return { ok: false, reason: 'cycle', chain: [...stack, id] }
135 }
136 if (visited.has(id)) return null
137 visited.add(id)
138
139 const entry = await lookup(id)
140 if (!entry) {
141 return { ok: false, reason: 'not-found', missing: id, requiredBy }
142 }
143
144 stack.push(id)
145 for (const rawDep of entry.dependencies ?? []) {
146 const dep = qualifyDependency(rawDep, id)
147 const err = await walk(dep, id)
148 if (err) return err
149 }
150 stack.pop()
151
152 closure.push(id)

Callers 1

installResolvedPluginFunction · 0.85

Calls 2

parsePluginIdentifierFunction · 0.85
walkFunction · 0.70

Tested by

no test coverage detected