( opts?: ReconcileOptions, )
| 112 | * Idempotent. Additive only (never deletes). Does not touch AppState. |
| 113 | */ |
| 114 | export async function reconcileMarketplaces( |
| 115 | opts?: ReconcileOptions, |
| 116 | ): Promise<ReconcileResult> { |
| 117 | const declared = getDeclaredMarketplaces() |
| 118 | if (Object.keys(declared).length === 0) { |
| 119 | return { installed: [], updated: [], failed: [], upToDate: [], skipped: [] } |
| 120 | } |
| 121 | |
| 122 | let materialized: KnownMarketplacesFile |
| 123 | try { |
| 124 | materialized = await loadKnownMarketplacesConfig() |
| 125 | } catch (e) { |
| 126 | logError(e) |
| 127 | materialized = {} |
| 128 | } |
| 129 | |
| 130 | const diff = diffMarketplaces(declared, materialized, { |
| 131 | projectRoot: getOriginalCwd(), |
| 132 | }) |
| 133 | |
| 134 | type WorkItem = { |
| 135 | name: string |
| 136 | source: MarketplaceSource |
| 137 | action: 'install' | 'update' |
| 138 | } |
| 139 | const work: WorkItem[] = [ |
| 140 | ...diff.missing.map( |
| 141 | (name): WorkItem => ({ |
| 142 | name, |
| 143 | source: normalizeSource(declared[name]!.source), |
| 144 | action: 'install', |
| 145 | }), |
| 146 | ), |
| 147 | ...diff.sourceChanged.map( |
| 148 | ({ name, declaredSource }): WorkItem => ({ |
| 149 | name, |
| 150 | source: declaredSource, |
| 151 | action: 'update', |
| 152 | }), |
| 153 | ), |
| 154 | ] |
| 155 | |
| 156 | const skipped: string[] = [] |
| 157 | const toProcess: WorkItem[] = [] |
| 158 | for (const item of work) { |
| 159 | if (opts?.skip?.(item.name, item.source)) { |
| 160 | skipped.push(item.name) |
| 161 | continue |
| 162 | } |
| 163 | // For sourceChanged local-path entries, skip if the declared path doesn't |
| 164 | // exist. Guards multi-checkout scenarios where normalizeSource can't |
| 165 | // canonicalize and produces a dead path — the materialized entry may still |
| 166 | // be valid; addMarketplaceSource would fail anyway, so skipping avoids a |
| 167 | // noisy "failed" event and preserves the working entry. Missing entries |
| 168 | // are NOT skipped (nothing to preserve; the user should see the error). |
| 169 | if ( |
| 170 | item.action === 'update' && |
| 171 | isLocalMarketplaceSource(item.source) && |
no test coverage detected