(
declared: Record<string, DeclaredMarketplace>,
materialized: KnownMarketplacesFile,
opts?: { projectRoot?: string },
)
| 48 | * resolution reads `.git` to canonicalize worktree paths (memoized). |
| 49 | */ |
| 50 | export function diffMarketplaces( |
| 51 | declared: Record<string, DeclaredMarketplace>, |
| 52 | materialized: KnownMarketplacesFile, |
| 53 | opts?: { projectRoot?: string }, |
| 54 | ): MarketplaceDiff { |
| 55 | const missing: string[] = [] |
| 56 | const sourceChanged: MarketplaceDiff['sourceChanged'] = [] |
| 57 | const upToDate: string[] = [] |
| 58 | |
| 59 | for (const [name, intent] of Object.entries(declared)) { |
| 60 | const state = materialized[name] |
| 61 | const normalizedIntent = normalizeSource(intent.source, opts?.projectRoot) |
| 62 | |
| 63 | if (!state) { |
| 64 | missing.push(name) |
| 65 | } else if (intent.sourceIsFallback) { |
| 66 | // Fallback: presence suffices. Don't compare sources — the declared source |
| 67 | // is only a default for the `missing` branch. If seed/prior-install/mirror |
| 68 | // materialized this marketplace under ANY source, leave it alone. Comparing |
| 69 | // would report sourceChanged → re-clone → stomp the materialized content. |
| 70 | upToDate.push(name) |
| 71 | } else if (!isEqual(normalizedIntent, state.source)) { |
| 72 | sourceChanged.push({ |
| 73 | name, |
| 74 | declaredSource: normalizedIntent, |
| 75 | materializedSource: state.source, |
| 76 | }) |
| 77 | } else { |
| 78 | upToDate.push(name) |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | return { missing, sourceChanged, upToDate } |
| 83 | } |
| 84 | |
| 85 | export type ReconcileOptions = { |
| 86 | /** Skip a declared marketplace. Used by zip-cache mode for unsupported source types. */ |
no test coverage detected