@softarc/native-federation-orchestrator 4.5.3 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/README.md +2 -2
  2. package/fesm2022/@softarc/native-federation-orchestrator.mjs +1087 -250
  3. package/fesm2022/@softarc/native-federation-orchestrator.mjs.map +4 -4
  4. package/fesm2022/module-federation.mjs +34 -20
  5. package/fesm2022/module-federation.mjs.map +3 -3
  6. package/fesm2022/node.mjs +1084 -247
  7. package/fesm2022/node.mjs.map +4 -4
  8. package/package.json +1 -1
  9. package/quickstart.mjs +15 -15
  10. package/types/lib/core/1.domain/externals/basis.d.ts +17 -2
  11. package/types/lib/core/1.domain/externals/version.contract.d.ts +1 -0
  12. package/types/lib/core/1.domain/remote-entry/remote-entry.contract.d.ts +1 -0
  13. package/types/lib/core/2.app/driver-ports/init/drivers.contract.d.ts +2 -0
  14. package/types/lib/core/2.app/driver-ports/init/for-determining-shared-externals.port.d.ts +8 -1
  15. package/types/lib/core/2.app/driver-ports/init/for-marking-pools-for-reelection.port.d.ts +6 -0
  16. package/types/lib/core/2.app/driver-ports/init/for-pooling-shared-externals.port.d.ts +3 -1
  17. package/types/lib/core/2.app/driving-ports/for-shared-chunks-storage.port.d.ts +1 -0
  18. package/types/lib/core/2.app/driving-ports/for-shared-externals-storage.port.d.ts +1 -2
  19. package/types/lib/core/2.app/steps/apply-winner.d.ts +18 -0
  20. package/types/lib/core/2.app/steps/pooling/anchoring.d.ts +69 -0
  21. package/types/lib/core/2.app/steps/pooling/mark-pools-for-reelection.d.ts +5 -0
  22. package/types/lib/core/2.app/steps/pooling/pool-dynamic-externals.d.ts +3 -1
  23. package/types/lib/core/2.app/steps/pooling/pool-graph.d.ts +10 -5
  24. package/types/lib/core/2.app/steps/pooling/pool-shared-externals.d.ts +30 -1
  25. package/types/lib/core/2.app/steps/pooling/pool-views.d.ts +63 -0
  26. package/types/lib/core/2.app/steps/pooling/pool.types.d.ts +30 -1
  27. package/types/lib/core/2.app/steps/pooling/pool.util.d.ts +15 -0
  28. package/types/lib/core/2.app/steps/store-remote-entry.d.ts +1 -1
  29. package/types/lib/testing/pooling/no-tear.d.ts +37 -0
package/fesm2022/node.mjs CHANGED
@@ -1574,14 +1574,18 @@ var createSharedExternalsRepository = (config) => {
1574
1574
  );
1575
1575
  if (config.clearStorage) STORAGE.clear();
1576
1576
  const _cache = STORAGE.get() ?? { [GLOBAL_SCOPE]: {} };
1577
- let _sawPoolTag = false;
1578
1577
  let _dirty = false;
1579
1578
  return {
1580
- markPoolTagPresent: function() {
1581
- _sawPoolTag = true;
1582
- },
1583
- hasPoolTag: function() {
1584
- return _sawPoolTag;
1579
+ // Read from the cache rather than remembered from this init's entries: a warm init may not refetch
1580
+ // the tagged remote at all, and pooling has to coordinate its pool anyway. Exits on the first hit.
1581
+ // Per share scope, because a pool never spans one: a tag in another scope is no reason to pool here.
1582
+ hasPoolTag: function(shareScope) {
1583
+ const scope = _cache[shareScope ?? GLOBAL_SCOPE];
1584
+ if (!scope) return false;
1585
+ for (const external of Object.values(scope))
1586
+ for (const version of external.versions)
1587
+ for (const remote of version.remotes) if (remote.pool?.trim()) return true;
1588
+ return false;
1585
1589
  },
1586
1590
  getFromScope: function(shareScope) {
1587
1591
  return { ..._cache[shareScope ?? GLOBAL_SCOPE] };
@@ -1602,9 +1606,11 @@ var createSharedExternalsRepository = (config) => {
1602
1606
  Object.values(_cache).forEach((scope) => {
1603
1607
  const removeExternals = [];
1604
1608
  Object.entries(scope).forEach(([name, external]) => {
1605
- let removedVersion = false;
1609
+ let removedCopy = false;
1606
1610
  for (let i = external.versions.length - 1; i >= 0; i--) {
1607
- const remotes = external.versions[i].remotes;
1611
+ const version = external.versions[i];
1612
+ const remotes = version.remotes;
1613
+ const host = version.host ? remotes[0]?.name : void 0;
1608
1614
  let keep = 0;
1609
1615
  for (let r = 0; r < remotes.length; r++) {
1610
1616
  if (remoteNames.has(remotes[r].name)) continue;
@@ -1612,15 +1618,16 @@ var createSharedExternalsRepository = (config) => {
1612
1618
  }
1613
1619
  if (keep !== remotes.length) {
1614
1620
  remotes.length = keep;
1621
+ removedCopy = true;
1615
1622
  _dirty = true;
1623
+ if (host !== void 0 && remoteNames.has(host)) version.host = false;
1616
1624
  }
1617
1625
  if (remotes.length === 0) {
1618
1626
  external.versions.splice(i, 1);
1619
- removedVersion = true;
1620
1627
  _dirty = true;
1621
1628
  }
1622
1629
  }
1623
- if (removedVersion) {
1630
+ if (removedCopy) {
1624
1631
  external.dirty = true;
1625
1632
  if (external.versions.length === 0) removeExternals.push(name);
1626
1633
  }
@@ -1665,6 +1672,14 @@ var createChunkRepository = (config) => {
1665
1672
  _dirty = true;
1666
1673
  return this;
1667
1674
  },
1675
+ // Whole-remote: a build that stops chunking a bundle omits the key, so `addOrReplace` cannot clear it.
1676
+ remove: function(remoteName) {
1677
+ if (remoteName in _cache) {
1678
+ delete _cache[remoteName];
1679
+ _dirty = true;
1680
+ }
1681
+ return this;
1682
+ },
1668
1683
  tryGet: function(remoteName, bundleName) {
1669
1684
  return Optional.of(_cache[remoteName]?.[bundleName]);
1670
1685
  },
@@ -1934,7 +1949,7 @@ var createInitFlow = ({
1934
1949
  adapters,
1935
1950
  config
1936
1951
  }) => {
1937
- return (remotesOrManifestUrl) => flow.getRemoteEntries(remotesOrManifestUrl).then(flow.processRemoteEntries).then(flow.determineSharedExternals).then(flow.poolSharedExternals).then(flow.generateImportMap).then((importMap) => flow.commitChanges(importMap, { override: true })).then(flow.exposeModuleLoader).then((loadRemoteModule) => ({
1952
+ return (remotesOrManifestUrl) => flow.getRemoteEntries(remotesOrManifestUrl).then(flow.processRemoteEntries).then(flow.markPoolsForReelection).then(flow.determineSharedExternals).then(flow.poolSharedExternals).then(flow.generateImportMap).then((importMap) => flow.commitChanges(importMap, { override: true })).then(flow.exposeModuleLoader).then((loadRemoteModule) => ({
1938
1953
  config,
1939
1954
  adapters,
1940
1955
  loadRemoteModule
@@ -2090,13 +2105,33 @@ function findVersionForTag(versions, tag) {
2090
2105
  }
2091
2106
  return scoped;
2092
2107
  }
2093
- function uncoveredEntrypoints(remote, basis) {
2094
- return Object.keys(remote.entries).filter((entrypoint) => !(entrypoint in basis));
2108
+ function versionEntries(version) {
2109
+ return collectEntries(version, void 0);
2110
+ }
2111
+ function committedEntries(version) {
2112
+ return collectEntries(version, (remote) => remote.cached);
2113
+ }
2114
+ function forEachVersionEntry(version, accepts, visit) {
2115
+ for (const remote of version.remotes) {
2116
+ if (remote.servedBy) continue;
2117
+ if (accepts && !accepts(remote)) continue;
2118
+ for (const entrypoint in remote.entries) visit(entrypoint, remote);
2119
+ }
2120
+ }
2121
+ function collectEntries(version, accepts) {
2122
+ const entries = /* @__PURE__ */ new Map();
2123
+ forEachVersionEntry(version, accepts, (entrypoint, remote) => {
2124
+ if (!entries.has(entrypoint)) entries.set(entrypoint, remote);
2125
+ });
2126
+ return entries;
2127
+ }
2128
+ function uncoveredEntrypoints(remote, covered) {
2129
+ return Object.keys(remote.entries).filter((entrypoint) => !covered.has(entrypoint));
2095
2130
  }
2096
- function countUncoveredEntrypoints(remote, basis) {
2131
+ function countUncoveredEntrypoints(remote, covered) {
2097
2132
  let uncovered = 0;
2098
2133
  for (const entrypoint in remote.entries) {
2099
- if (!(entrypoint in basis)) uncovered++;
2134
+ if (!covered.has(entrypoint)) uncovered++;
2100
2135
  }
2101
2136
  return uncovered;
2102
2137
  }
@@ -2108,6 +2143,7 @@ function createRemoveCachedRemoteEntries(ports) {
2108
2143
  for (const remoteName of remoteNames) {
2109
2144
  ports.remoteInfoRepo.remove(remoteName);
2110
2145
  ports.scopedExternalsRepo.remove(remoteName);
2146
+ ports.sharedChunksRepo.remove(remoteName);
2111
2147
  }
2112
2148
  ports.sharedExternalsRepo.removeFromAllScopes(remoteNames);
2113
2149
  };
@@ -2133,7 +2169,6 @@ function createStoreRemoteEntry(config, ports, logStep) {
2133
2169
  const tag = resolveVersion(remoteEntry, external);
2134
2170
  if (tag === null) return;
2135
2171
  if (external.singleton) {
2136
- if (external.pool?.trim()) ports.sharedExternalsRepo.markPoolTagPresent();
2137
2172
  onSharedExternal(remoteEntry, external, sharedExternalContext(remoteEntry, external, tag));
2138
2173
  } else {
2139
2174
  addScopedExternal(remoteEntry, external, tag);
@@ -2271,8 +2306,129 @@ function createProcessRemoteEntries(config, ports) {
2271
2306
  }
2272
2307
  }
2273
2308
 
2309
+ // src/lib/core/2.app/steps/apply-winner.ts
2310
+ function versionAcceptance(external, isCompatible) {
2311
+ const demands = new Map(
2312
+ external.versions.map((v) => [v, versionDemands(v)])
2313
+ );
2314
+ return {
2315
+ accepts: (version, tag) => demands.get(version).every((d) => isCompatible(tag, d.requiredVersion)),
2316
+ objector: (version, tag) => demands.get(version).find((d) => d.strictVersion && !isCompatible(tag, d.requiredVersion))
2317
+ };
2318
+ }
2319
+ function createApplyWinner(config) {
2320
+ return function applyWinner(externalName, external, winner, isCompatible, acceptance) {
2321
+ if (external.versions.length > 1) {
2322
+ const { accepts, objector } = acceptance ?? versionAcceptance(external, isCompatible);
2323
+ const rebuilt = [];
2324
+ for (const v of external.versions) {
2325
+ rebuilt.push(v);
2326
+ if (accepts(v, winner.tag)) {
2327
+ v.action = "skip";
2328
+ continue;
2329
+ }
2330
+ const strict = objector(v, winner.tag);
2331
+ if (config.strict.strictExternalCompatibility && strict) {
2332
+ config.log.error(
2333
+ 3,
2334
+ `[${strict.name}][${externalName}@${v.tag}] Is not compatible with requiredRange '${strict.requiredVersion}' of shared ${externalName}@${winner.tag}.`
2335
+ );
2336
+ throw new NFError(`External ${externalName}@${v.tag} could not be shared.`);
2337
+ }
2338
+ if (v === winner) continue;
2339
+ if (!strict) {
2340
+ v.action = "skip";
2341
+ continue;
2342
+ }
2343
+ const objecting = new Set(
2344
+ v.remotes.filter((r) => r.strictVersion && !isCompatible(winner.tag, r.requiredVersion))
2345
+ );
2346
+ if (objecting.size === v.remotes.length) {
2347
+ v.action = "scope";
2348
+ continue;
2349
+ }
2350
+ v.remotes = v.remotes.filter((r) => !objecting.has(r));
2351
+ v.action = "skip";
2352
+ rebuilt.push({ tag: v.tag, host: false, action: "scope", remotes: [...objecting] });
2353
+ }
2354
+ const merged = /* @__PURE__ */ new Map();
2355
+ external.versions = rebuilt.filter((v) => {
2356
+ const first = merged.get(`${v.tag}|${v.action}`);
2357
+ if (!first) {
2358
+ merged.set(`${v.tag}|${v.action}`, v);
2359
+ return true;
2360
+ }
2361
+ first.remotes.push(...v.remotes);
2362
+ return false;
2363
+ });
2364
+ }
2365
+ winner.action = "share";
2366
+ applyEntrypointCoveragePolicy(externalName, external);
2367
+ external.dirty = false;
2368
+ return external;
2369
+ };
2370
+ function applyEntrypointCoveragePolicy(externalName, external) {
2371
+ const { strictEntryPointCoverage } = config.strict;
2372
+ if (!strictEntryPointCoverage && !config.profile.scopeUncoveredEntrypoints) return;
2373
+ const tears = findTears(external);
2374
+ if (tears.length === 0) return;
2375
+ if (strictEntryPointCoverage) {
2376
+ const { version, remote, uncovered } = tears[0];
2377
+ config.log.error(
2378
+ 3,
2379
+ `[${externalName}@${version.tag}][${remote.name}] Entrypoints not covered by the shared version: ${uncovered.join(", ")}.`
2380
+ );
2381
+ throw new NFError(
2382
+ `External ${externalName} could not be shared without tearing entrypoints.`
2383
+ );
2384
+ }
2385
+ scopeTornRemotes(externalName, external, tears);
2386
+ }
2387
+ function findTears(external) {
2388
+ const shared = external.versions.find((v) => v.action === "share");
2389
+ if (!shared) return [];
2390
+ const basis = versionEntries(shared);
2391
+ const tears = [];
2392
+ for (const version of external.versions) {
2393
+ if (version.action === "scope") continue;
2394
+ if (version === shared) continue;
2395
+ version.remotes.forEach((remote) => {
2396
+ if (remote.servedBy) return;
2397
+ const uncovered = uncoveredEntrypoints(remote, basis);
2398
+ if (uncovered.length > 0) tears.push({ version, remote, uncovered });
2399
+ });
2400
+ }
2401
+ return tears;
2402
+ }
2403
+ function scopeTornRemotes(externalName, external, tears) {
2404
+ const torn = new Set(tears.map((t) => t.remote));
2405
+ const demotedByTag = /* @__PURE__ */ new Map();
2406
+ for (const { version, remote, uncovered } of tears) {
2407
+ const group = demotedByTag.get(version.tag);
2408
+ if (group) group.push(remote);
2409
+ else demotedByTag.set(version.tag, [remote]);
2410
+ config.log.debug(
2411
+ 3,
2412
+ `[${externalName}@${version.tag}][${remote.name}] Scoped: entrypoints not covered by the shared version: ${uncovered.join(", ")}.`
2413
+ );
2414
+ }
2415
+ for (const version of external.versions) {
2416
+ if (version.remotes.some((r) => torn.has(r))) {
2417
+ version.remotes = version.remotes.filter((r) => !torn.has(r));
2418
+ }
2419
+ }
2420
+ external.versions = external.versions.filter((v) => v.remotes.length > 0);
2421
+ for (const [tag, remotes] of demotedByTag) {
2422
+ const scoped = external.versions.find((v) => v.tag === tag && v.action === "scope");
2423
+ if (scoped) scoped.remotes.push(...remotes);
2424
+ else external.versions.push({ tag, host: false, action: "scope", remotes });
2425
+ }
2426
+ }
2427
+ }
2428
+
2274
2429
  // src/lib/core/2.app/steps/determine-shared-externals.ts
2275
2430
  function createDetermineSharedExternals(config, ports) {
2431
+ const applyWinner = createApplyWinner(config);
2276
2432
  return () => {
2277
2433
  const memo = /* @__PURE__ */ new Map();
2278
2434
  const isCompatible = (tag, requiredVersion) => {
@@ -2284,16 +2440,20 @@ function createDetermineSharedExternals(config, ports) {
2284
2440
  }
2285
2441
  return hit;
2286
2442
  };
2443
+ const touched = /* @__PURE__ */ new Map();
2287
2444
  for (const shareScope of ports.sharedExternalsRepo.getScopes()) {
2288
2445
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
2289
2446
  try {
2290
- Object.entries(sharedExternals).filter(([_, e]) => e.dirty).forEach(
2291
- ([name, external]) => ports.sharedExternalsRepo.addOrUpdate(
2447
+ const elected = /* @__PURE__ */ new Set();
2448
+ Object.entries(sharedExternals).filter(([_, e]) => e.dirty).forEach(([name, external]) => {
2449
+ ports.sharedExternalsRepo.addOrUpdate(
2292
2450
  name,
2293
2451
  setVersionActions(name, external, isCompatible),
2294
2452
  shareScope
2295
- )
2296
- );
2453
+ );
2454
+ elected.add(name);
2455
+ });
2456
+ if (elected.size > 0) touched.set(shareScope, elected);
2297
2457
  } catch (error) {
2298
2458
  config.log.error(
2299
2459
  3,
@@ -2311,28 +2471,25 @@ function createDetermineSharedExternals(config, ports) {
2311
2471
  );
2312
2472
  }
2313
2473
  }
2314
- return Promise.resolve();
2474
+ return Promise.resolve(touched);
2315
2475
  };
2316
2476
  function uncoveredTears(external, winner, accepts) {
2317
- const basis = winner.remotes[0].entries;
2477
+ const basis = versionEntries(winner);
2318
2478
  return external.versions.reduce((sum, v) => {
2319
2479
  if (v === winner) return sum;
2320
2480
  if (!accepts(v, winner.tag)) return sum;
2321
- return sum + v.remotes.reduce((n, r) => n + countUncoveredEntrypoints(r, basis), 0);
2481
+ return v.remotes.reduce(
2482
+ (n, r) => r.servedBy ? n : n + countUncoveredEntrypoints(r, basis),
2483
+ sum
2484
+ );
2322
2485
  }, 0);
2323
2486
  }
2324
2487
  function setVersionActions(externalName, external, isCompatible) {
2325
2488
  if (external.versions.length === 1) {
2326
- external.versions[0].action = "share";
2327
- applyEntrypointCoveragePolicy(externalName, external);
2328
- external.dirty = false;
2329
- return external;
2489
+ return applyWinner(externalName, external, external.versions[0], isCompatible);
2330
2490
  }
2331
- const demands = new Map(
2332
- external.versions.map((v) => [v, versionDemands(v)])
2333
- );
2334
- const accepts = (version, tag) => demands.get(version).every((d) => isCompatible(tag, d.requiredVersion));
2335
- const objector = (version, tag) => demands.get(version).find((d) => d.strictVersion && !isCompatible(tag, d.requiredVersion));
2491
+ const acceptance = versionAcceptance(external, isCompatible);
2492
+ const { accepts } = acceptance;
2336
2493
  let sharedVersion = external.versions.find((v) => v.host);
2337
2494
  if (!sharedVersion && config.profile.latestSharedExternal) {
2338
2495
  sharedVersion = external.versions[0];
@@ -2340,10 +2497,30 @@ function createDetermineSharedExternals(config, ports) {
2340
2497
  if (!sharedVersion) {
2341
2498
  let leastExtraDownloads = Number.MAX_VALUE;
2342
2499
  let leastTears = Number.MAX_VALUE;
2500
+ const selfServing = new Map(
2501
+ external.versions.map((v) => {
2502
+ const groups = /* @__PURE__ */ new Map();
2503
+ for (const remote of v.remotes) {
2504
+ if (remote.cached || !remote.strictVersion) continue;
2505
+ const group = groups.get(remote.requiredVersion);
2506
+ if (group) group.copies++;
2507
+ else
2508
+ groups.set(remote.requiredVersion, {
2509
+ requiredVersion: remote.requiredVersion,
2510
+ copies: 1
2511
+ });
2512
+ }
2513
+ return [v, [...groups.values()]];
2514
+ })
2515
+ );
2516
+ const costOf = (version, tag) => selfServing.get(version).reduce((n, g) => isCompatible(tag, g.requiredVersion) ? n : n + g.copies, 0);
2343
2517
  external.versions.forEach((vA) => {
2344
- const extraDownloads = external.versions.filter(
2345
- (vB) => demands.get(vB).some((d) => !d.cached && d.strictVersion && !isCompatible(vA.tag, d.requiredVersion))
2346
- ).length;
2518
+ const extraDownloads = external.versions.reduce(
2519
+ // A copy of the winner is never redirected, so it never self-serves however its own range
2520
+ // reads — see `applyWinner`, which does not split the winner either.
2521
+ (sum, vB) => vB === vA ? sum : sum + costOf(vB, vA.tag),
2522
+ 0
2523
+ );
2347
2524
  if (extraDownloads < leastExtraDownloads) {
2348
2525
  leastExtraDownloads = extraDownloads;
2349
2526
  leastTears = uncoveredTears(external, vA, accepts);
@@ -2361,84 +2538,304 @@ function createDetermineSharedExternals(config, ports) {
2361
2538
  if (!sharedVersion) {
2362
2539
  throw new NFError(`[${externalName}] Could not determine shared version!`);
2363
2540
  }
2364
- external.versions.forEach((v) => {
2365
- if (accepts(v, sharedVersion.tag)) {
2366
- v.action = "skip";
2367
- return;
2541
+ return applyWinner(externalName, external, sharedVersion, isCompatible, acceptance);
2542
+ }
2543
+ }
2544
+
2545
+ // src/lib/core/2.app/steps/pooling/pool-views.ts
2546
+ function liveBuilds(members, islanded) {
2547
+ return walkBuilds(members, islanded, true);
2548
+ }
2549
+ function committedView(members) {
2550
+ const global = /* @__PURE__ */ new Map();
2551
+ forEachGlobalClaim(members, void 0, (specifier, tag, meta) => {
2552
+ if (!global.has(specifier))
2553
+ global.set(specifier, { tag, remote: meta.name, file: meta.entries[specifier] });
2554
+ });
2555
+ return { builds: walkBuilds(members, void 0, false), global };
2556
+ }
2557
+ function walkBuilds(members, islanded, skipScoped) {
2558
+ const builds = /* @__PURE__ */ new Map();
2559
+ for (const member of members) {
2560
+ const versions = member.external.versions;
2561
+ for (let v = 0; v < versions.length; v++) {
2562
+ const version = versions[v];
2563
+ if (skipScoped && version.action === "scope") continue;
2564
+ const remotes = version.remotes;
2565
+ for (let r = 0; r < remotes.length; r++) {
2566
+ const meta = remotes[r];
2567
+ if (islanded?.has(meta.name)) continue;
2568
+ let own = builds.get(meta.name);
2569
+ if (!own) {
2570
+ builds.set(
2571
+ meta.name,
2572
+ own = { coverage: /* @__PURE__ */ new Map(), tags: /* @__PURE__ */ new Map(), instance: /* @__PURE__ */ new Map() }
2573
+ );
2574
+ }
2575
+ if (!own.instance.has(member.name)) own.instance.set(member.name, version.tag);
2576
+ for (const specifier in meta.entries) {
2577
+ own.coverage.set(specifier, meta.entries[specifier]);
2578
+ if (!own.tags.has(specifier)) own.tags.set(specifier, version.tag);
2579
+ }
2368
2580
  }
2369
- const strict = objector(v, sharedVersion.tag);
2370
- if (config.strict.strictExternalCompatibility && strict) {
2371
- config.log.error(
2372
- 3,
2373
- `[${strict.name}][${externalName}@${v.tag}] Is not compatible with requiredRange '${strict.requiredVersion}' of shared ${externalName}@${sharedVersion.tag}.`
2374
- );
2375
- throw new NFError(`External ${externalName}@${v.tag} could not be shared.`);
2581
+ }
2582
+ }
2583
+ return builds;
2584
+ }
2585
+ function ownCopies(members, only) {
2586
+ const own = /* @__PURE__ */ new Map();
2587
+ for (const member of members) {
2588
+ for (const version of member.external.versions) {
2589
+ for (const meta of version.remotes) {
2590
+ if (only && !only.has(meta.name)) continue;
2591
+ let copies = own.get(meta.name);
2592
+ if (!copies) own.set(meta.name, copies = []);
2593
+ if (!copies.some((c) => c.member === member.name))
2594
+ copies.push({ member: member.name, tag: version.tag, entries: meta.entries });
2376
2595
  }
2377
- v.action = strict ? "scope" : "skip";
2378
- });
2379
- sharedVersion.action = "share";
2380
- applyEntrypointCoveragePolicy(externalName, external);
2381
- external.dirty = false;
2382
- return external;
2596
+ }
2383
2597
  }
2384
- function applyEntrypointCoveragePolicy(externalName, external) {
2385
- const { strictEntryPointCoverage } = config.strict;
2386
- if (!strictEntryPointCoverage && !config.profile.scopeUncoveredEntrypoints) return;
2387
- const tears = findTears(external);
2388
- if (tears.length === 0) return;
2389
- if (strictEntryPointCoverage) {
2390
- const { version, remote, uncovered } = tears[0];
2391
- config.log.error(
2392
- 3,
2393
- `[${externalName}@${version.tag}][${remote.name}] Entrypoints not covered by the shared version: ${uncovered.join(", ")}.`
2394
- );
2395
- throw new NFError(
2396
- `External ${externalName} could not be shared without tearing entrypoints.`
2397
- );
2598
+ return own;
2599
+ }
2600
+ function consumedMembers(members) {
2601
+ const consumed = /* @__PURE__ */ new Map();
2602
+ for (const member of members) {
2603
+ const versions = member.external.versions;
2604
+ for (let v = 0; v < versions.length; v++) {
2605
+ const remotes = versions[v].remotes;
2606
+ for (let r = 0; r < remotes.length; r++) {
2607
+ const name = remotes[r].name;
2608
+ const list = consumed.get(name);
2609
+ if (!list) consumed.set(name, [member.name]);
2610
+ else if (list[list.length - 1] !== member.name) list.push(member.name);
2611
+ }
2398
2612
  }
2399
- scopeTornRemotes(externalName, external, tears);
2400
2613
  }
2401
- function findTears(external) {
2402
- const shared = external.versions.find((v) => v.action === "share");
2403
- if (!shared) return [];
2404
- const basis = shared.remotes[0].entries;
2405
- const tears = [];
2406
- for (const version of external.versions) {
2407
- if (version.action === "scope") continue;
2408
- version.remotes.forEach((remote, index) => {
2409
- if (version === shared && index === 0) return;
2410
- const uncovered = uncoveredEntrypoints(remote, basis);
2411
- if (uncovered.length > 0) tears.push({ version, remote, uncovered });
2412
- });
2614
+ return consumed;
2615
+ }
2616
+ function consumedSpecifiers(members) {
2617
+ const consumed = /* @__PURE__ */ new Map();
2618
+ for (const member of members) {
2619
+ const versions = member.external.versions;
2620
+ for (let v = 0; v < versions.length; v++) {
2621
+ const remotes = versions[v].remotes;
2622
+ for (let r = 0; r < remotes.length; r++) {
2623
+ const meta = remotes[r];
2624
+ let own = consumed.get(meta.name);
2625
+ if (!own) consumed.set(meta.name, own = /* @__PURE__ */ new Set());
2626
+ for (const specifier in meta.entries) own.add(specifier);
2627
+ }
2413
2628
  }
2414
- return tears;
2415
2629
  }
2416
- function scopeTornRemotes(externalName, external, tears) {
2417
- const torn = new Set(tears.map((t) => t.remote));
2418
- const demotedByTag = /* @__PURE__ */ new Map();
2419
- for (const { version, remote, uncovered } of tears) {
2420
- const group = demotedByTag.get(version.tag);
2421
- if (group) group.push(remote);
2422
- else demotedByTag.set(version.tag, [remote]);
2423
- config.log.debug(
2424
- 3,
2425
- `[${externalName}@${version.tag}][${remote.name}] Scoped: entrypoints not covered by the shared version: ${uncovered.join(", ")}.`
2426
- );
2630
+ return consumed;
2631
+ }
2632
+ function coversWholePool(members, build, islanded) {
2633
+ const served = /* @__PURE__ */ new Set();
2634
+ const wanted = /* @__PURE__ */ new Set();
2635
+ for (const member of members) {
2636
+ for (const version of member.external.versions) {
2637
+ for (const meta of version.remotes) {
2638
+ if (islanded.has(meta.name)) continue;
2639
+ const into = meta.name === build ? served : wanted;
2640
+ for (const specifier in meta.entries) into.add(specifier);
2641
+ }
2427
2642
  }
2428
- for (const version of external.versions) {
2429
- if (version.remotes.some((r) => torn.has(r))) {
2430
- version.remotes = version.remotes.filter((r) => !torn.has(r));
2643
+ }
2644
+ for (const specifier of wanted) if (!served.has(specifier)) return false;
2645
+ return true;
2646
+ }
2647
+ function servingBuilds(members, islanded) {
2648
+ const serving = /* @__PURE__ */ new Map();
2649
+ for (const member of members) {
2650
+ const shared = member.external.versions.find((v) => v.action === "share");
2651
+ const basis = shared?.remotes.find((r) => !islanded.has(r.name));
2652
+ if (basis) serving.set(member.name, basis.name);
2653
+ }
2654
+ return serving;
2655
+ }
2656
+ function basisPerMember(members, islanded, dedupsElsewhere) {
2657
+ const basis = /* @__PURE__ */ new Map();
2658
+ for (const member of members) {
2659
+ const winner = member.external.versions.find((v) => v.action === "share");
2660
+ if (!winner) continue;
2661
+ const own = winner.remotes.find(
2662
+ (r) => !islanded?.has(r.name) && !dedupsElsewhere?.(r.name)
2663
+ );
2664
+ if (own) basis.set(member.name, own.name);
2665
+ }
2666
+ return basis;
2667
+ }
2668
+ function hostRemotes(members) {
2669
+ const hosts = /* @__PURE__ */ new Set();
2670
+ for (const member of members) {
2671
+ for (const version of member.external.versions) {
2672
+ if (version.host && version.remotes.length > 0) hosts.add(version.remotes[0].name);
2673
+ }
2674
+ }
2675
+ return hosts;
2676
+ }
2677
+ function arrivalOrder(members) {
2678
+ const arrival = /* @__PURE__ */ new Map();
2679
+ for (const member of members) {
2680
+ for (const version of member.external.versions) {
2681
+ for (const meta of version.remotes) {
2682
+ if (!arrival.has(meta.name)) arrival.set(meta.name, arrival.size);
2431
2683
  }
2432
2684
  }
2433
- external.versions = external.versions.filter((v) => v.remotes.length > 0);
2434
- for (const [tag, remotes] of demotedByTag) {
2435
- const scoped = external.versions.find((v) => v.tag === tag && v.action === "scope");
2436
- if (scoped) scoped.remotes.push(...remotes);
2437
- else external.versions.push({ tag, host: false, action: "scope", remotes });
2685
+ }
2686
+ return arrival;
2687
+ }
2688
+ function sharedTagPerSpecifier(members, islanded) {
2689
+ const shared = /* @__PURE__ */ new Map();
2690
+ forEachGlobalClaim(members, islanded, (specifier, tag) => {
2691
+ if (!shared.has(specifier)) shared.set(specifier, tag);
2692
+ });
2693
+ return shared;
2694
+ }
2695
+ function forEachGlobalClaim(members, islanded, visit) {
2696
+ const accepts = islanded ? (meta) => !islanded.has(meta.name) : void 0;
2697
+ const claim = (version) => forEachVersionEntry(version, accepts, (specifier, meta) => visit(specifier, version.tag, meta));
2698
+ for (const member of members) {
2699
+ const versions = member.external.versions;
2700
+ const winner = versions.find((v) => v.action === "share");
2701
+ if (winner) claim(winner);
2702
+ for (let v = 0; v < versions.length; v++) {
2703
+ if (versions[v].action === "skip") claim(versions[v]);
2438
2704
  }
2439
2705
  }
2440
2706
  }
2441
2707
 
2708
+ // src/lib/core/2.app/steps/pooling/anchoring.ts
2709
+ function acceptanceTable(members, isCompatible) {
2710
+ const table = /* @__PURE__ */ new Map();
2711
+ for (const member of members) {
2712
+ const tags = [];
2713
+ for (const version of member.external.versions) tags.push(version.tag);
2714
+ for (const version of member.external.versions) {
2715
+ for (const meta of version.remotes) {
2716
+ let byMember = table.get(meta.name);
2717
+ if (!byMember) table.set(meta.name, byMember = /* @__PURE__ */ new Map());
2718
+ if (byMember.has(member.name)) continue;
2719
+ const accepted = /* @__PURE__ */ new Set();
2720
+ for (let t = 0; t < tags.length; t++) {
2721
+ if (isCompatible(tags[t], meta.requiredVersion)) accepted.add(tags[t]);
2722
+ }
2723
+ byMember.set(member.name, accepted);
2724
+ }
2725
+ }
2726
+ }
2727
+ return table;
2728
+ }
2729
+ function covers(coverage2, consumed) {
2730
+ for (const specifier of consumed) if (!coverage2.has(specifier)) return false;
2731
+ return true;
2732
+ }
2733
+ function acceptsAll(acceptance, build, consumer, consumed) {
2734
+ const byMember = acceptance.get(consumer);
2735
+ if (!byMember) return false;
2736
+ for (let i = 0; i < consumed.length; i++) {
2737
+ const offered = build.get(consumed[i]);
2738
+ if (offered === void 0) return false;
2739
+ if (!byMember.get(consumed[i])?.has(offered)) return false;
2740
+ }
2741
+ return true;
2742
+ }
2743
+ function isWitnessed(specifiers, shared, builds) {
2744
+ const wanted = [...specifiers];
2745
+ for (const specifier of wanted) if (!shared.has(specifier)) return false;
2746
+ for (const [, build] of builds) {
2747
+ let matches2 = true;
2748
+ for (let i = 0; i < wanted.length; i++) {
2749
+ if (build.tags.get(wanted[i]) !== shared.get(wanted[i])) {
2750
+ matches2 = false;
2751
+ break;
2752
+ }
2753
+ }
2754
+ if (matches2) return true;
2755
+ }
2756
+ return false;
2757
+ }
2758
+ function explainSelfServe(remote, wants, specifiers, pool) {
2759
+ let closest;
2760
+ let fewest = Number.MAX_SAFE_INTEGER;
2761
+ let gap = wants[0] ?? "";
2762
+ for (const [build, view] of pool.builds) {
2763
+ if (build === remote) continue;
2764
+ let missing;
2765
+ let count = 0;
2766
+ for (const specifier of specifiers) {
2767
+ if (view.coverage.has(specifier)) continue;
2768
+ count++;
2769
+ missing ??= specifier;
2770
+ }
2771
+ if (count >= fewest) continue;
2772
+ fewest = count;
2773
+ closest = build;
2774
+ gap = missing ?? rejectedMember(view.instance, remote, wants, pool.acceptance) ?? gap;
2775
+ }
2776
+ return { gap, closest };
2777
+ }
2778
+ function rejectedMember(offered, consumer, wants, acceptance) {
2779
+ const accepts = acceptance.get(consumer);
2780
+ if (!accepts) return void 0;
2781
+ for (const member of wants) {
2782
+ const tag = offered.get(member);
2783
+ if (tag === void 0) return member;
2784
+ if (!accepts.get(member)?.has(tag)) return `${member}@${tag}`;
2785
+ }
2786
+ return void 0;
2787
+ }
2788
+ function assignAnchors(input) {
2789
+ const { builds, acceptance, consumedMembers: consumedMembers2, hosts, arrival } = input;
2790
+ const assignment = /* @__PURE__ */ new Map();
2791
+ const byPriority = (a, b) => (arrival.get(a) ?? Number.MAX_SAFE_INTEGER) - (arrival.get(b) ?? Number.MAX_SAFE_INTEGER) || a.localeCompare(b);
2792
+ const candidates = [...builds.keys()].sort(byPriority);
2793
+ const pending = new Set([...consumedMembers2.keys()].sort(byPriority));
2794
+ const anchored = /* @__PURE__ */ new Set();
2795
+ const canServe = (candidate, consumer) => {
2796
+ const build = builds.get(candidate);
2797
+ if (!covers(build.coverage, input.consumedSpecifiers.get(consumer) ?? [])) return false;
2798
+ return acceptsAll(acceptance, build.instance, consumer, consumedMembers2.get(consumer) ?? []);
2799
+ };
2800
+ const electAnchor = (anchor, served) => {
2801
+ pending.delete(anchor);
2802
+ assignment.set(anchor, void 0);
2803
+ for (const consumer of served) {
2804
+ pending.delete(consumer);
2805
+ anchored.add(consumer);
2806
+ assignment.set(consumer, anchor);
2807
+ }
2808
+ };
2809
+ for (const host of [...hosts].sort(byPriority)) {
2810
+ const served = [];
2811
+ if (builds.has(host)) {
2812
+ for (const consumer of pending) {
2813
+ if (consumer !== host && canServe(host, consumer)) served.push(consumer);
2814
+ }
2815
+ }
2816
+ electAnchor(host, served);
2817
+ }
2818
+ for (; ; ) {
2819
+ let best;
2820
+ let bestServed = [];
2821
+ for (const candidate of candidates) {
2822
+ if (anchored.has(candidate)) continue;
2823
+ const served = [];
2824
+ for (const consumer of pending) {
2825
+ if (consumer !== candidate && canServe(candidate, consumer)) served.push(consumer);
2826
+ }
2827
+ if (served.length > bestServed.length) {
2828
+ best = candidate;
2829
+ bestServed = served;
2830
+ }
2831
+ }
2832
+ if (best === void 0) break;
2833
+ electAnchor(best, bestServed);
2834
+ }
2835
+ for (const consumer of pending) assignment.set(consumer, void 0);
2836
+ return assignment;
2837
+ }
2838
+
2442
2839
  // src/lib/core/2.app/steps/pooling/pool-graph.ts
2443
2840
  var SCOPED_PACKAGE = /^@([^/]+)\//;
2444
2841
  function createDSU() {
@@ -2479,34 +2876,56 @@ function createDSU() {
2479
2876
  }
2480
2877
  var extNode = (name) => `ext\0${name}`;
2481
2878
  var tagNode = (remote, tag) => `tag\0${remote}\0${tag}`;
2482
- var scopeNode = (scope) => `scope\0${scope}`;
2879
+ var scopeNode = (remote, scope) => `scope\0${remote}\0${scope}`;
2483
2880
  function autoScope(name, useAutoExternalPooling) {
2484
2881
  return useAutoExternalPooling ? SCOPED_PACKAGE.exec(name)?.[1] : void 0;
2485
2882
  }
2883
+ function owningPackage(name) {
2884
+ const depth = name.startsWith("@") ? 2 : 1;
2885
+ let cut = -1;
2886
+ for (let seen = 0; seen < depth; seen++) {
2887
+ cut = name.indexOf("/", cut + 1);
2888
+ if (cut === -1) return void 0;
2889
+ }
2890
+ return name.slice(0, cut);
2891
+ }
2486
2892
  function groupByMembership(candidates, log) {
2487
2893
  const dsu = createDSU();
2488
2894
  const tagged = /* @__PURE__ */ new Set();
2489
- const poolable = [];
2895
+ const joined = /* @__PURE__ */ new Set();
2896
+ const tagRules = /* @__PURE__ */ new Set();
2897
+ for (const candidate of candidates) {
2898
+ if (candidate.scope === void 0) continue;
2899
+ for (const edge of candidate.tags) tagRules.add(`${edge.remote}\0${candidate.scope}`);
2900
+ }
2490
2901
  for (const candidate of candidates) {
2491
- let joined = false;
2492
- if (candidate.scope !== void 0) {
2493
- dsu.union(extNode(candidate.name), scopeNode(candidate.scope));
2494
- joined = true;
2495
- }
2496
2902
  for (const edge of candidate.tags) {
2497
2903
  dsu.union(extNode(candidate.name), tagNode(edge.remote, edge.tag));
2498
2904
  tagged.add(candidate.name);
2499
- joined = true;
2905
+ joined.add(candidate.name);
2906
+ }
2907
+ if (candidate.scope === void 0) continue;
2908
+ for (const remote of candidate.remotes) {
2909
+ if (tagRules.has(`${remote}\0${candidate.scope}`)) continue;
2910
+ dsu.union(extNode(candidate.name), scopeNode(remote, candidate.scope));
2911
+ joined.add(candidate.name);
2500
2912
  }
2501
- if (joined) poolable.push({ name: candidate.name, value: candidate.value });
2913
+ }
2914
+ const declared = new Set(candidates.map((c) => c.name));
2915
+ for (const candidate of candidates) {
2916
+ const owner = owningPackage(candidate.name);
2917
+ if (owner !== void 0 && declared.has(owner))
2918
+ dsu.union(extNode(candidate.name), extNode(owner));
2502
2919
  }
2503
2920
  const byComponent = /* @__PURE__ */ new Map();
2504
- for (const entry of poolable) {
2505
- const root = dsu.component(extNode(entry.name));
2506
- (byComponent.get(root) ?? byComponent.set(root, []).get(root)).push(entry);
2921
+ for (const candidate of candidates) {
2922
+ const root = dsu.component(extNode(candidate.name));
2923
+ const members = byComponent.get(root) ?? byComponent.set(root, []).get(root);
2924
+ members.push({ name: candidate.name, value: candidate.value });
2507
2925
  }
2508
2926
  const pools = [];
2509
2927
  for (const members of byComponent.values()) {
2928
+ if (!members.some((m) => joined.has(m.name))) continue;
2510
2929
  members.sort((a, b) => a.name.localeCompare(b.name));
2511
2930
  if (members.length < 2) {
2512
2931
  const only = members[0];
@@ -2534,6 +2953,7 @@ function buildPools(sharedExternals, useAutoExternalPooling, log) {
2534
2953
  return tag ? [{ remote: r.name, tag }] : [];
2535
2954
  })
2536
2955
  ),
2956
+ remotes: external.versions.flatMap((v) => v.remotes.map((r) => r.name)),
2537
2957
  value: { name, external }
2538
2958
  })
2539
2959
  );
@@ -2541,28 +2961,131 @@ function buildPools(sharedExternals, useAutoExternalPooling, log) {
2541
2961
  }
2542
2962
 
2543
2963
  // src/lib/core/2.app/steps/pooling/pool.util.ts
2964
+ function lazy(make) {
2965
+ let value;
2966
+ return () => value ??= make();
2967
+ }
2544
2968
  function remotesInPool(members) {
2545
2969
  return [
2546
2970
  ...new Set(members.flatMap((m) => m.external.versions.flatMap((v) => v.remotes.map((r) => r.name))))
2547
2971
  ];
2548
2972
  }
2973
+ function poolableScopes(config, repo) {
2974
+ const { useAutoExternalPooling } = config.feature;
2975
+ return {
2976
+ useAutoExternalPooling,
2977
+ scopes: repo.getScopes().filter(
2978
+ (scope) => repo.scopeType(scope) !== "strict" && (useAutoExternalPooling || repo.hasPoolTag(scope))
2979
+ )
2980
+ };
2981
+ }
2549
2982
 
2550
2983
  // src/lib/core/2.app/steps/pooling/pool-shared-externals.ts
2551
2984
  function islandedRemotes(members) {
2552
- const islanded = /* @__PURE__ */ new Set();
2985
+ const islanded = /* @__PURE__ */ new Map();
2553
2986
  for (const member of members)
2554
2987
  for (const version of member.external.versions)
2555
- if (version.action === "scope") for (const remote of version.remotes) islanded.add(remote.name);
2988
+ if (version.action === "scope") {
2989
+ for (const remote of version.remotes)
2990
+ if (!islanded.has(remote.name))
2991
+ islanded.set(remote.name, {
2992
+ kind: "incompatible",
2993
+ member: member.name,
2994
+ tag: version.tag
2995
+ });
2996
+ }
2556
2997
  return islanded;
2557
2998
  }
2558
- function createPoolSharedExternals(config, ports) {
2559
- return () => {
2560
- const { useAutoExternalPooling } = config.feature;
2561
- if (!useAutoExternalPooling && !ports.sharedExternalsRepo.hasPoolTag()) {
2562
- return Promise.resolve();
2999
+ var basisFor = (members, islanded, serving) => basisPerMember(members, islanded, (remote) => {
3000
+ const build = serving.get(remote);
3001
+ return build !== void 0 && build !== remote;
3002
+ });
3003
+ function servedPerRemote(consumed, serving, basis) {
3004
+ const served = /* @__PURE__ */ new Map();
3005
+ for (const [remote, build] of serving) {
3006
+ const byMember = /* @__PURE__ */ new Map();
3007
+ for (const name of consumed.get(remote) ?? []) {
3008
+ if (basis.get(name) !== build) byMember.set(name, build);
3009
+ }
3010
+ if (byMember.size > 0) served.set(remote, byMember);
3011
+ }
3012
+ return served;
3013
+ }
3014
+ var anyAnchorStored = (members) => members.some((m) => m.external.versions.some((v) => v.remotes.some((r) => r.servedBy !== void 0)));
3015
+ function findTornRemotes(members, islanded, basis, served, hosts) {
3016
+ const { suspect, selfServed } = movedRemotes(members, islanded, basis, served, hosts);
3017
+ if (suspect.size === 0) return [];
3018
+ const builds = liveBuilds(members, islanded);
3019
+ const shared = sharedTagPerSpecifier(members, islanded);
3020
+ const own = ownCopies(members, suspect);
3021
+ const torn = [];
3022
+ for (const remote of suspect) {
3023
+ const copies = own.get(remote) ?? [];
3024
+ const mine = selfServed.get(remote);
3025
+ const anchors = served.get(remote);
3026
+ const ownTags = /* @__PURE__ */ new Map();
3027
+ const resolved = /* @__PURE__ */ new Map();
3028
+ for (const copy of copies) {
3029
+ const from = mine?.has(copy.member) ? remote : anchors?.get(copy.member);
3030
+ for (const specifier in copy.entries) {
3031
+ if (!ownTags.has(specifier)) ownTags.set(specifier, copy.tag);
3032
+ const tag = from === remote ? copy.tag : from !== void 0 ? builds.get(from)?.tags.get(specifier) : shared.get(specifier);
3033
+ if (tag !== void 0 && !resolved.has(specifier)) resolved.set(specifier, tag);
3034
+ }
3035
+ }
3036
+ if (resolved.size === 0) continue;
3037
+ if (ships(ownTags, resolved)) continue;
3038
+ let witnessed = false;
3039
+ for (const [, build] of builds) {
3040
+ if (ships(build.tags, resolved)) {
3041
+ witnessed = true;
3042
+ break;
3043
+ }
3044
+ }
3045
+ if (!witnessed) {
3046
+ torn.push({
3047
+ remote,
3048
+ combination: [...resolved].map(([specifier, tag]) => `${specifier}@${tag}`).join(", ")
3049
+ });
3050
+ }
3051
+ }
3052
+ return torn;
3053
+ }
3054
+ var ships = (build, resolved) => {
3055
+ if (!build) return false;
3056
+ for (const [specifier, tag] of resolved) if (build.get(specifier) !== tag) return false;
3057
+ return true;
3058
+ };
3059
+ function movedRemotes(members, islanded, basis, served, hosts) {
3060
+ const suspect = /* @__PURE__ */ new Set();
3061
+ const selfServed = /* @__PURE__ */ new Map();
3062
+ const add = (remote) => {
3063
+ if (!islanded.has(remote) && !hosts.has(remote)) suspect.add(remote);
3064
+ };
3065
+ for (const remote of served.keys()) add(remote);
3066
+ for (const member of members) {
3067
+ const unpublished = !basis.has(member.name);
3068
+ for (const version of member.external.versions) {
3069
+ const scoped = version.action === "scope";
3070
+ if (!unpublished && !scoped) continue;
3071
+ for (const meta of version.remotes) {
3072
+ add(meta.name);
3073
+ const servesItself = scoped || unpublished && served.get(meta.name)?.get(member.name) === void 0;
3074
+ if (!servesItself) continue;
3075
+ let mine = selfServed.get(meta.name);
3076
+ if (!mine) selfServed.set(meta.name, mine = /* @__PURE__ */ new Set());
3077
+ mine.add(member.name);
3078
+ }
2563
3079
  }
2564
- for (const scope of ports.sharedExternalsRepo.getScopes()) {
2565
- if (ports.sharedExternalsRepo.scopeType(scope) === "strict") continue;
3080
+ }
3081
+ return { suspect, selfServed };
3082
+ }
3083
+ function createPoolSharedExternals(config, ports) {
3084
+ return (touched) => {
3085
+ const { useAutoExternalPooling, scopes } = poolableScopes(config, ports.sharedExternalsRepo);
3086
+ for (const scope of scopes) {
3087
+ const touchedInScope = touched?.get(scope);
3088
+ if (touched && !touchedInScope) continue;
2566
3089
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(scope);
2567
3090
  try {
2568
3091
  for (const [poolName, members] of buildPools(
@@ -2570,6 +3093,7 @@ function createPoolSharedExternals(config, ports) {
2570
3093
  useAutoExternalPooling,
2571
3094
  config.log
2572
3095
  )) {
3096
+ if (touchedInScope && !members.some((m) => touchedInScope.has(m.name))) continue;
2573
3097
  poolFamily(poolName, members, scope);
2574
3098
  }
2575
3099
  } catch (error) {
@@ -2590,45 +3114,138 @@ function createPoolSharedExternals(config, ports) {
2590
3114
  const allRemotes = remotesInPool(members);
2591
3115
  if (allRemotes.length < 2) return;
2592
3116
  const islanded = islandedRemotes(members);
3117
+ const consumed = consumedMembers(members);
3118
+ const hosts = hostRemotes(members);
3119
+ const views = {
3120
+ consumedSpecifiers: lazy(() => consumedSpecifiers(members)),
3121
+ acceptance: lazy(() => acceptanceTable(members, ports.versionCheck.isCompatible)),
3122
+ arrival: lazy(() => arrivalOrder(members))
3123
+ };
2593
3124
  config.log.debug(
2594
3125
  3,
2595
- `[${scope}][pool:${poolName}] ${members.length} members across ${allRemotes.length} remotes, islanded={${[...islanded].join(", ") || "\u2205"}}
3126
+ `[${scope}][pool:${poolName}] ${members.length} members across ${allRemotes.length} remotes, incompatible={${[...islanded.keys()].join(", ") || "\u2205"}}
2596
3127
  ` + members.map((m) => ` - ${m.name}`).join("\n")
2597
3128
  );
2598
- if (islanded.size > 0 && config.strict.strictExternalCompatibility) {
3129
+ const assign = () => {
3130
+ const serving2 = assignServingBuilds(members, islanded, consumed, hosts, views);
3131
+ const basis2 = basisFor(members, islanded, serving2);
3132
+ return { serving: serving2, basis: basis2, served: servedPerRemote(consumed, serving2, basis2) };
3133
+ };
3134
+ let { serving, basis, served } = assign();
3135
+ if (islanded.size === 0 && serving.size === 0 && !anyAnchorStored(members)) return;
3136
+ for (; ; ) {
3137
+ const torn = findTornRemotes(members, islanded, basis, served, hosts);
3138
+ if (torn.length === 0) break;
3139
+ for (const { remote, combination } of torn)
3140
+ islanded.set(remote, { kind: "torn", combination });
3141
+ ({ serving, basis, served } = assign());
3142
+ }
3143
+ const incompatible = [...islanded].filter(([, cause]) => cause.kind === "incompatible").map(([remote]) => remote);
3144
+ if (config.strict.strictExternalCompatibility && incompatible.length > 0) {
2599
3145
  config.log.error(
2600
3146
  3,
2601
- `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${[...islanded].join(", ")}}.`
3147
+ `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${incompatible.join(", ")}}.`
2602
3148
  );
2603
3149
  throw new NFError(`Could not pool '${poolName}' in scope ${scope}.`);
2604
3150
  }
3151
+ for (const [remote, cause] of islanded) {
3152
+ const scoped = consumed.get(remote)?.length ?? 0;
3153
+ config.log.warn(3, `[${scope}][pool:${poolName}] ${islandWarning(remote, cause, scoped)}`);
3154
+ }
2605
3155
  for (const member of members) {
2606
- const rebuilt = rebuildMember(member, islanded);
2607
- warnIfScopedOnly(poolName, member.name, rebuilt, scope);
3156
+ const rebuilt = rebuildMember(member, islanded, served, basis.get(member.name));
3157
+ warnIfScopedOnly(poolName, member, rebuilt, islanded, scope);
2608
3158
  ports.sharedExternalsRepo.addOrUpdate(member.name, rebuilt, scope);
2609
3159
  }
2610
3160
  }
2611
- function warnIfScopedOnly(poolName, memberName, rebuilt, scope) {
3161
+ function assignServingBuilds(members, islanded, consumed, hosts, views) {
3162
+ for (; ; ) {
3163
+ const bases = servingBuilds(members, islanded);
3164
+ const distinct = new Set(bases.values());
3165
+ if (distinct.size === 1 && bases.size === members.length) {
3166
+ const sole = [...distinct][0];
3167
+ if (coversWholePool(members, sole, islanded)) return /* @__PURE__ */ new Map();
3168
+ }
3169
+ const builds = liveBuilds(members, islanded);
3170
+ const shared = sharedTagPerSpecifier(members, islanded);
3171
+ const consumedSpec = views.consumedSpecifiers();
3172
+ const needAnchor = /* @__PURE__ */ new Map();
3173
+ const needSpecifiers = /* @__PURE__ */ new Map();
3174
+ for (const [remote, wants] of consumed) {
3175
+ if (islanded.has(remote)) continue;
3176
+ if (isWitnessed(consumedSpec.get(remote) ?? [], shared, builds)) continue;
3177
+ needAnchor.set(remote, wants);
3178
+ needSpecifiers.set(remote, consumedSpec.get(remote) ?? /* @__PURE__ */ new Set());
3179
+ }
3180
+ if (needAnchor.size === 0) return /* @__PURE__ */ new Map();
3181
+ const acceptance = views.acceptance();
3182
+ const assignment = assignAnchors({
3183
+ builds,
3184
+ acceptance,
3185
+ consumedSpecifiers: needSpecifiers,
3186
+ consumedMembers: needAnchor,
3187
+ hosts,
3188
+ arrival: views.arrival()
3189
+ });
3190
+ const anchors = /* @__PURE__ */ new Set();
3191
+ for (const build of assignment.values()) if (build !== void 0) anchors.add(build);
3192
+ const serving = /* @__PURE__ */ new Map();
3193
+ let scoped = false;
3194
+ for (const [remote, wants] of needAnchor) {
3195
+ const assigned = assignment.get(remote);
3196
+ const build = assigned ?? (anchors.has(remote) || hosts.has(remote) ? remote : void 0);
3197
+ if (build === void 0) {
3198
+ islanded.set(remote, {
3199
+ kind: "uncovered",
3200
+ ...explainSelfServe(remote, wants, needSpecifiers.get(remote), { builds, acceptance })
3201
+ });
3202
+ scoped = true;
3203
+ continue;
3204
+ }
3205
+ serving.set(remote, build);
3206
+ }
3207
+ if (!scoped) return serving;
3208
+ }
3209
+ }
3210
+ function warnIfScopedOnly(poolName, member, rebuilt, islanded, scope) {
2612
3211
  if (rebuilt.versions.some((v) => v.action === "share")) return;
2613
- const consumers = new Set(rebuilt.versions.flatMap((v) => v.remotes.map((r) => r.name))).size;
3212
+ const consumers = new Set(
3213
+ rebuilt.versions.filter((v) => v.action === "scope").flatMap((v) => v.remotes.map((r) => r.name))
3214
+ ).size;
2614
3215
  if (consumers < 2) return;
3216
+ const provider = member.external.versions.find((v) => v.action === "share");
3217
+ if (provider?.remotes.some((r) => islanded.has(r.name))) return;
2615
3218
  config.log.warn(
2616
3219
  3,
2617
- `[${scope}][pool:${poolName}] '${memberName}' is scoped-only \u2014 no coherent shared build provides it; ${consumers} remotes download their own copy.`
3220
+ `[${scope}][pool:${poolName}] '${member.name}' is scoped-only \u2014 no coherent shared build provides it; ${consumers} remotes download their own copy.`
2618
3221
  );
2619
3222
  }
2620
- function rebuildMember(member, islanded) {
2621
- const entries = member.external.versions.flatMap(
2622
- (v) => v.remotes.map((meta) => ({ remote: meta.name, tag: v.tag, host: v.host, action: v.action, meta }))
2623
- );
2624
- const isScoped = (e) => islanded.has(e.remote) || e.action === "scope";
2625
- let scoped = entries.filter(isScoped);
2626
- let clean = entries.filter((e) => !isScoped(e));
2627
- if (!clean.some((e) => e.action === "share")) {
2628
- scoped = [...scoped, ...clean];
2629
- clean = [];
2630
- }
2631
- const shareEntries = clean.filter((e) => e.action === "share");
3223
+ function rebuildMember(member, islanded, served, basis) {
3224
+ let scoped = [];
3225
+ let clean = [];
3226
+ for (const version of member.external.versions) {
3227
+ for (const meta of version.remotes) {
3228
+ const entry = {
3229
+ remote: meta.name,
3230
+ tag: version.tag,
3231
+ host: version.host,
3232
+ action: version.action,
3233
+ meta
3234
+ };
3235
+ (islanded.has(entry.remote) || entry.action === "scope" ? scoped : clean).push(entry);
3236
+ }
3237
+ }
3238
+ for (const entry of scoped) delete entry.meta.servedBy;
3239
+ for (const entry of clean) {
3240
+ const build = served.get(entry.remote)?.get(member.name);
3241
+ if (build !== void 0) entry.meta.servedBy = build;
3242
+ else delete entry.meta.servedBy;
3243
+ }
3244
+ if (basis === void 0) {
3245
+ scoped = [...scoped, ...clean.filter((e) => e.meta.servedBy === void 0)];
3246
+ clean = clean.filter((e) => e.meta.servedBy !== void 0).map((e) => ({ ...e, action: "skip" }));
3247
+ }
3248
+ const shareEntries = clean.filter((e) => e.action === "share").sort((a, b) => Number(b.remote === basis) - Number(a.remote === basis));
2632
3249
  const shareVersion = shareEntries.length > 0 ? [
2633
3250
  {
2634
3251
  tag: shareEntries[0].tag,
@@ -2637,37 +3254,70 @@ function createPoolSharedExternals(config, ports) {
2637
3254
  remotes: shareEntries.map((e) => e.meta)
2638
3255
  }
2639
3256
  ] : [];
2640
- const skipByTag = /* @__PURE__ */ new Map();
2641
- for (const e of clean) {
2642
- if (e.action !== "skip") continue;
2643
- const version = skipByTag.get(e.tag) ?? {
2644
- tag: e.tag,
2645
- host: e.host,
2646
- action: "skip",
2647
- remotes: []
2648
- };
2649
- version.remotes.push(e.meta);
2650
- skipByTag.set(e.tag, version);
2651
- }
2652
- const scopeByTag = /* @__PURE__ */ new Map();
2653
- for (const e of scoped) {
2654
- const version = scopeByTag.get(e.tag) ?? {
2655
- tag: e.tag,
2656
- host: false,
2657
- action: "scope",
2658
- remotes: []
2659
- };
2660
- version.remotes.push(e.meta);
2661
- scopeByTag.set(e.tag, version);
2662
- }
3257
+ const byTag = (entries, action) => {
3258
+ const versions = /* @__PURE__ */ new Map();
3259
+ for (const entry of entries) {
3260
+ const version = versions.get(entry.tag) ?? {
3261
+ tag: entry.tag,
3262
+ host: entry.host,
3263
+ action,
3264
+ remotes: []
3265
+ };
3266
+ version.remotes.push(entry.meta);
3267
+ versions.set(entry.tag, version);
3268
+ }
3269
+ return [...versions.values()];
3270
+ };
2663
3271
  return {
2664
3272
  dirty: false,
2665
- versions: [...shareVersion, ...skipByTag.values(), ...scopeByTag.values()]
3273
+ versions: [
3274
+ ...shareVersion,
3275
+ ...byTag(
3276
+ clean.filter((e) => e.action === "skip"),
3277
+ "skip"
3278
+ ),
3279
+ ...byTag(scoped, "scope")
3280
+ ].sort((a, b) => ports.versionCheck.compare(b.tag, a.tag))
2666
3281
  };
2667
3282
  }
2668
3283
  }
3284
+ function islandWarning(remote, cause, scoped) {
3285
+ switch (cause.kind) {
3286
+ case "incompatible":
3287
+ return `'${remote}' is islanded: the resolver scoped its '${cause.member}@${cause.tag}', so all ${scoped} members it imports are scoped for it.`;
3288
+ case "torn":
3289
+ return `'${remote}' serves its own family: the mapping would have handed it ${cause.combination}, which no build shipped together, so all ${scoped} members it imports are scoped for it.`;
3290
+ default: {
3291
+ const closest = cause.closest ? `closest is '${cause.closest}'` : "no other build in the pool serves any of it";
3292
+ return `'${remote}' serves its own family: no shared build offers every entrypoint it imports at a version it accepts \u2014 '${cause.gap}' is the gap, ${closest}. All ${scoped} members it imports are scoped for it.`;
3293
+ }
3294
+ }
3295
+ }
2669
3296
 
2670
- // node_modules/.pnpm/@softarc+native-federation@4.3.2_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
3297
+ // src/lib/core/2.app/steps/pooling/mark-pools-for-reelection.ts
3298
+ function createMarkPoolsForReelection(config, ports) {
3299
+ return () => {
3300
+ const { useAutoExternalPooling, scopes } = poolableScopes(config, ports.sharedExternalsRepo);
3301
+ for (const scope of scopes) {
3302
+ const sharedExternals = ports.sharedExternalsRepo.getFromScope(scope);
3303
+ if (!Object.values(sharedExternals).some((external) => external.dirty)) continue;
3304
+ let spread = 0;
3305
+ for (const [, members] of buildPools(sharedExternals, useAutoExternalPooling)) {
3306
+ if (!members.some((m) => m.external.dirty)) continue;
3307
+ for (const member of members)
3308
+ if (!member.external.dirty) {
3309
+ member.external.dirty = true;
3310
+ spread++;
3311
+ }
3312
+ }
3313
+ if (spread > 0)
3314
+ config.log.debug(3, `[${scope}] ${spread} pool member(s) marked dirty for re-election.`);
3315
+ }
3316
+ return Promise.resolve();
3317
+ };
3318
+ }
3319
+
3320
+ // node_modules/.pnpm/@softarc+native-federation@4.4.0_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
2671
3321
  var CHUNK_PREFIX = "@nf-internal";
2672
3322
  function toChunkImport(fileName) {
2673
3323
  if (fileName.startsWith("./")) {
@@ -2728,9 +3378,9 @@ function createGenerateImportMap(config, ports) {
2728
3378
  }
2729
3379
  function processshareScope(importMap, shareScope, chunkBundles) {
2730
3380
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
3381
+ const index = buildServedIndex(sharedExternals);
2731
3382
  for (const [externalName, external] of Object.entries(sharedExternals)) {
2732
3383
  let override = void 0;
2733
- let overrideScope = void 0;
2734
3384
  const cachedBefore = cachedFingerprint(external);
2735
3385
  for (const version of external.versions) {
2736
3386
  if (version.action === "scope") {
@@ -2747,36 +3397,52 @@ function createGenerateImportMap(config, ports) {
2747
3397
  continue;
2748
3398
  }
2749
3399
  version.remotes[0].cached = true;
2750
- let source = version.remotes[0];
2751
- let sourceScope = getScope2(shareScope, source.name, externalName);
2752
- if (version.action === "share") {
2753
- registerBundleChunks(chunkBundles, source.name, source.bundle);
2754
- } else if (version.action === "skip") {
3400
+ let serving = version;
3401
+ if (version.action === "skip") {
2755
3402
  if (!override) {
2756
3403
  override = findOverride(external, shareScope, externalName) ?? "NOT_AVAILABLE";
2757
3404
  }
2758
3405
  if (override !== "NOT_AVAILABLE") {
2759
- if (!overrideScope)
2760
- overrideScope = getScope2(shareScope, override.remotes[0].name, externalName);
2761
- source = override.remotes[0];
2762
- sourceScope = overrideScope;
2763
- override.remotes[0].cached = true;
3406
+ serving = override;
2764
3407
  version.remotes[0].cached = false;
2765
3408
  }
2766
3409
  }
2767
- const mappings = Object.entries(source.entries).map(([packageName, file]) => ({
2768
- packageName,
2769
- url: join(sourceScope, file),
2770
- name: source.name,
2771
- file
2772
- }));
3410
+ const provided = versionEntries(serving);
3411
+ const mappings = [];
3412
+ for (const [packageName, provider] of provided) {
3413
+ const file = provider.entries[packageName];
3414
+ mappings.push({
3415
+ packageName,
3416
+ url: join(getScope2(shareScope, provider.name, externalName), file),
3417
+ name: provider.name,
3418
+ file
3419
+ });
3420
+ provider.cached = true;
3421
+ if (version.action === "share") {
3422
+ registerBundleChunks(chunkBundles, provider.name, provider.bundle);
3423
+ }
3424
+ }
2773
3425
  version.remotes.forEach((r) => {
2774
3426
  const rScope = getScope2(shareScope, r.name, externalName);
3427
+ if (index && r.servedBy) {
3428
+ const files = index.get(r.servedBy);
3429
+ const servedScope = getScope2(shareScope, r.servedBy, externalName);
3430
+ for (const specifier of Object.keys(r.entries)) {
3431
+ const source = files?.get(specifier);
3432
+ if (!source) continue;
3433
+ const url = join(servedScope, source.file);
3434
+ addToScope(importMap, rScope, { [specifier]: url });
3435
+ addIntegrity(importMap, url, r.servedBy, source.file);
3436
+ registerBundleChunks(chunkBundles, r.servedBy, source.bundle);
3437
+ source.meta.cached = true;
3438
+ }
3439
+ return;
3440
+ }
2775
3441
  for (const m of mappings) {
2776
3442
  addToScope(importMap, rScope, { [m.packageName]: m.url });
2777
3443
  }
2778
3444
  for (const [packageName, file] of Object.entries(r.entries)) {
2779
- if (packageName in source.entries) continue;
3445
+ if (provided.has(packageName)) continue;
2780
3446
  if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
2781
3447
  warnUncoveredEntrypoint(shareScope, externalName, r.name, packageName);
2782
3448
  continue;
@@ -2827,11 +3493,68 @@ function createGenerateImportMap(config, ports) {
2827
3493
  }
2828
3494
  config.log.warn(4, `ShareScope external ${scopedExternalName} has multiple shared versions.`);
2829
3495
  }
3496
+ function buildServedIndex(externals) {
3497
+ let anyServed = false;
3498
+ for (const external of Object.values(externals)) {
3499
+ for (const version of external.versions) {
3500
+ if (version.remotes.some((r) => r.servedBy)) anyServed = true;
3501
+ }
3502
+ }
3503
+ if (!anyServed) return void 0;
3504
+ const index = /* @__PURE__ */ new Map();
3505
+ for (const external of Object.values(externals)) {
3506
+ for (const version of external.versions) {
3507
+ for (const remote of version.remotes) {
3508
+ let own = index.get(remote.name);
3509
+ if (!own) index.set(remote.name, own = /* @__PURE__ */ new Map());
3510
+ for (const [specifier, file] of Object.entries(remote.entries)) {
3511
+ if (!own.has(specifier))
3512
+ own.set(specifier, { file, bundle: remote.bundle, meta: remote });
3513
+ }
3514
+ }
3515
+ }
3516
+ }
3517
+ return index;
3518
+ }
3519
+ function collectServed(into, index, ctx, externalName, version) {
3520
+ for (const consumer of version.remotes) {
3521
+ if (!consumer.servedBy) continue;
3522
+ const files = index.get(consumer.servedBy);
3523
+ const scope = getScope2(ctx, consumer.name, externalName);
3524
+ for (const specifier of Object.keys(consumer.entries)) {
3525
+ const source = files?.get(specifier);
3526
+ if (!source) {
3527
+ config.log.warn(
3528
+ 4,
3529
+ `[${ctx}][${externalName}][${consumer.name}] '${consumer.servedBy}' does not serve '${specifier}'.`
3530
+ );
3531
+ continue;
3532
+ }
3533
+ into.push({ scope, specifier, source, from: consumer.servedBy });
3534
+ }
3535
+ }
3536
+ }
3537
+ function flushServed(importMap, chunkBundles, served) {
3538
+ for (const { scope, specifier, source, from } of served) {
3539
+ const url = join(getScope2(GLOBAL_SCOPE, from), source.file);
3540
+ if (importMap.imports[specifier] === url) continue;
3541
+ addToScope(importMap, scope, { [specifier]: url });
3542
+ addIntegrity(importMap, url, from, source.file);
3543
+ registerBundleChunks(chunkBundles, from, source.bundle);
3544
+ source.meta.cached = true;
3545
+ }
3546
+ }
2830
3547
  function addGlobalSharedExternals(importMap, chunkBundles) {
2831
3548
  const sharedExternals = ports.sharedExternalsRepo.getFromScope();
3549
+ const index = buildServedIndex(sharedExternals);
3550
+ const served = [];
3551
+ const cachedBefore = /* @__PURE__ */ new Map();
2832
3552
  for (const [externalName, external] of Object.entries(sharedExternals)) {
2833
- const cachedBefore = cachedFingerprint(external);
3553
+ cachedBefore.set(externalName, cachedFingerprint(external));
2834
3554
  for (const version of external.versions) {
3555
+ if (index && version.action !== "scope") {
3556
+ collectServed(served, index, GLOBAL_SCOPE, externalName, version);
3557
+ }
2835
3558
  if (version.action === "skip") continue;
2836
3559
  if (version.action === "scope") {
2837
3560
  for (const remote of version.remotes) {
@@ -2850,29 +3573,46 @@ function createGenerateImportMap(config, ports) {
2850
3573
  notifyDuplicateGlobalExternal(externalName);
2851
3574
  continue;
2852
3575
  }
2853
- const scope = getScope2(GLOBAL_SCOPE, version.remotes[0].name, externalName);
2854
- for (const [packageName, file] of Object.entries(version.remotes[0].entries)) {
2855
- const url = join(scope, file);
2856
- addToGlobal(importMap, { [packageName]: url });
2857
- addIntegrity(importMap, url, version.remotes[0].name, file);
2858
- }
2859
- registerBundleChunks(chunkBundles, version.remotes[0].name, version.remotes[0].bundle);
2860
- version.remotes[0].cached = true;
2861
- selfFillUncovered(importMap, chunkBundles, externalName, version.remotes, 1);
3576
+ mergeVersionEntries(importMap, chunkBundles, externalName, version);
2862
3577
  }
2863
3578
  for (const version of external.versions) {
2864
3579
  if (version.action !== "skip") continue;
2865
3580
  selfFillUncovered(importMap, chunkBundles, externalName, version.remotes);
2866
3581
  }
2867
- if (cachedFingerprint(external) !== cachedBefore) {
3582
+ }
3583
+ flushServed(importMap, chunkBundles, served);
3584
+ for (const [externalName, external] of Object.entries(sharedExternals)) {
3585
+ if (cachedFingerprint(external) !== cachedBefore.get(externalName)) {
2868
3586
  ports.sharedExternalsRepo.addOrUpdate(externalName, external);
2869
3587
  }
2870
3588
  }
2871
3589
  return importMap;
2872
3590
  }
2873
- function selfFillUncovered(importMap, chunkBundles, externalName, remotes, from = 0) {
2874
- for (let i = from; i < remotes.length; i++) {
2875
- const remote = remotes[i];
3591
+ function mergeVersionEntries(importMap, chunkBundles, externalName, version) {
3592
+ const scopes = /* @__PURE__ */ new Map();
3593
+ for (const [packageName, remote] of versionEntries(version)) {
3594
+ if (importMap.imports[packageName]) {
3595
+ config.log.debug(
3596
+ 4,
3597
+ `[${GLOBAL_SCOPE}][${externalName}][${remote.name}] Entrypoint '${packageName}' is already mapped by another external, keeping '${importMap.imports[packageName]}'.`
3598
+ );
3599
+ continue;
3600
+ }
3601
+ let scope = scopes.get(remote.name);
3602
+ if (scope === void 0) {
3603
+ scopes.set(remote.name, scope = getScope2(GLOBAL_SCOPE, remote.name, externalName));
3604
+ registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3605
+ remote.cached = true;
3606
+ }
3607
+ const file = remote.entries[packageName];
3608
+ const url = join(scope, file);
3609
+ addToGlobal(importMap, { [packageName]: url });
3610
+ addIntegrity(importMap, url, remote.name, file);
3611
+ }
3612
+ }
3613
+ function selfFillUncovered(importMap, chunkBundles, externalName, remotes) {
3614
+ for (const remote of remotes) {
3615
+ if (remote.servedBy) continue;
2876
3616
  for (const [packageName, file] of Object.entries(remote.entries)) {
2877
3617
  if (importMap.imports[packageName]) continue;
2878
3618
  if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
@@ -3085,15 +3825,16 @@ function createUpdateCache(config, ports) {
3085
3825
  const actions = {};
3086
3826
  if (remoteEntry?.override) removeCachedRemoteEntries(/* @__PURE__ */ new Set([remoteEntry.name]));
3087
3827
  storeRemoteEntry(remoteEntry, (entry, external, ctx) => {
3088
- const { action, sharedVersion } = resolveSharedExternal(entry, external, ctx);
3828
+ const { action, provided, sameVersion } = resolveSharedExternal(entry, external, ctx);
3089
3829
  actions[external.packageName] = { action };
3090
- if (action === "skip" && sharedVersion?.remotes[0]?.entries) {
3091
- actions[external.packageName].covered = Object.keys(sharedVersion.remotes[0].entries);
3830
+ if (action === "skip" && provided) {
3831
+ actions[external.packageName].covered = Array.from(provided.keys());
3832
+ if (sameVersion) actions[external.packageName].sameVersion = true;
3092
3833
  if (external.shareScope) {
3093
3834
  actions[external.packageName].override = resolveOverrideEntries(
3094
3835
  entry,
3095
3836
  external,
3096
- sharedVersion
3837
+ provided
3097
3838
  );
3098
3839
  }
3099
3840
  }
@@ -3123,8 +3864,10 @@ function createUpdateCache(config, ports) {
3123
3864
  }
3124
3865
  config.log.warn(8, errorMsg);
3125
3866
  }
3126
- if (action === "skip" && sharedVersion) {
3127
- const uncovered = uncoveredEntrypoints(remote, sharedVersion.remotes[0].entries);
3867
+ const provided = sharedVersion ? sharedInfo.shareScope ? versionEntries(sharedVersion) : committedEntries(sharedVersion) : void 0;
3868
+ const sameVersion = sharedVersion?.tag === tag;
3869
+ if (action === "skip" && provided && !sameVersion) {
3870
+ const uncovered = uncoveredEntrypoints(remote, provided);
3128
3871
  if (uncovered.length > 0) {
3129
3872
  const msg = `[${sharedInfo.shareScope ?? GLOBAL_SCOPE}][${remoteEntry.name}][${sharedInfo.packageName}] Entrypoints not covered by the shared version: ${uncovered.join(", ")}.`;
3130
3873
  if (config.strict.strictEntryPointCoverage) {
@@ -3152,65 +3895,157 @@ function createUpdateCache(config, ports) {
3152
3895
  cached2.versions.push({ tag, action, host: false, remotes: [remote] });
3153
3896
  }
3154
3897
  commit();
3155
- return { action, sharedVersion };
3156
- }
3157
- function resolveOverrideEntries(remoteEntry, external, sharedVersion) {
3158
- return ports.remoteInfoRepo.tryGet(sharedVersion.remotes[0].name).map(
3159
- (remote) => Object.fromEntries(
3160
- Object.entries(sharedVersion.remotes[0].entries).map(([packageName, file]) => [
3161
- packageName,
3162
- join(remote.scopeUrl, file)
3163
- ])
3164
- )
3165
- ).orThrow(() => {
3898
+ return { action, provided, sameVersion };
3899
+ }
3900
+ function resolveOverrideEntries(remoteEntry, external, provided) {
3901
+ return Object.fromEntries(
3902
+ Array.from(provided, ([packageName, provider]) => [
3903
+ packageName,
3904
+ join(
3905
+ providerScopeUrl(remoteEntry, external, provider.name),
3906
+ provider.entries[packageName]
3907
+ )
3908
+ ])
3909
+ );
3910
+ }
3911
+ function providerScopeUrl(remoteEntry, external, providerName) {
3912
+ return ports.remoteInfoRepo.tryGet(providerName).map((remote) => remote.scopeUrl).orThrow(() => {
3166
3913
  config.log.error(
3167
3914
  8,
3168
3915
  `[${external.shareScope ?? GLOBAL_SCOPE}][${remoteEntry.name}][${external.packageName}@${external.version}][override] Remote name not found in cache.`
3169
3916
  );
3170
- return new NFError(
3171
- `Could not find override url from remote ${sharedVersion.remotes[0].name}`
3172
- );
3917
+ return new NFError(`Could not find override url from remote ${providerName}`);
3173
3918
  });
3174
3919
  }
3175
3920
  }
3176
3921
 
3177
3922
  // src/lib/core/2.app/steps/pooling/pool-dynamic-externals.ts
3178
- function createPoolDynamicExternals(config) {
3923
+ function createPoolDynamicExternals(config, ports) {
3179
3924
  return ({ entry, actions }) => {
3180
3925
  const { useAutoExternalPooling } = config.feature;
3181
- if (!useAutoExternalPooling && !(entry.shared ?? []).some((e) => e.pool?.trim())) {
3182
- return Promise.resolve({ entry, actions });
3183
- }
3184
- const byScope = /* @__PURE__ */ new Map();
3926
+ const declared = /* @__PURE__ */ new Map();
3185
3927
  for (const external of entry.shared ?? []) {
3186
3928
  const name = external.packageName;
3187
3929
  if (!external.singleton || !actions[name]) continue;
3188
3930
  if (external.shareScope === STRICT_SCOPE) continue;
3189
- const tag = external.pool?.trim();
3190
3931
  const shareScope = external.shareScope ?? GLOBAL_SCOPE;
3191
- const candidates = byScope.get(shareScope) ?? [];
3192
- candidates.push({
3193
- name,
3194
- scope: autoScope(name, useAutoExternalPooling),
3195
- tags: tag ? [{ remote: entry.name, tag }] : [],
3196
- value: name
3197
- });
3198
- byScope.set(shareScope, candidates);
3932
+ let names = declared.get(shareScope);
3933
+ if (!names) declared.set(shareScope, names = /* @__PURE__ */ new Set());
3934
+ names.add(name);
3199
3935
  }
3936
+ if (declared.size === 0) return Promise.resolve({ entry, actions });
3200
3937
  const scope = (name) => {
3201
3938
  actions[name].action = "scope";
3202
3939
  delete actions[name].override;
3203
3940
  };
3204
- for (const candidates of byScope.values()) {
3205
- for (const members of groupByMembership(candidates).values()) {
3206
- const memberActions = members.map((name) => actions[name].action);
3207
- if (memberActions.includes("scope")) {
3208
- members.forEach(scope);
3941
+ for (const [shareScope, names] of declared) {
3942
+ if (!useAutoExternalPooling && !ports.sharedExternalsRepo.hasPoolTag(shareScope)) continue;
3943
+ const committed = ports.sharedExternalsRepo.getFromScope(shareScope);
3944
+ for (const pool of buildPools(committed, useAutoExternalPooling).values()) {
3945
+ const mine = pool.filter((member) => names.has(member.name));
3946
+ if (mine.length === 0) continue;
3947
+ if (mine.some((member) => actions[member.name].action === "scope")) {
3948
+ mine.forEach((member) => scope(member.name));
3949
+ continue;
3950
+ }
3951
+ const asked = gateViews(entry.name, pool);
3952
+ const anchor = anchorFor(entry.name, asked);
3953
+ if (anchor === "witnessed") continue;
3954
+ if (anchor === void 0) {
3955
+ const reason = explainDynamic(entry.name, asked);
3956
+ const closest = reason.closest ? `closest is '${reason.closest}'` : "no committed build serves any of it";
3957
+ config.log.warn(
3958
+ 8,
3959
+ `[${shareScope}] '${entry.name}' serves its own family: no committed build offers every entrypoint it imports at a version it accepts \u2014 '${reason.gap}' is the gap, ${closest}. All ${mine.length} members it imports are scoped for it.`
3960
+ );
3961
+ mine.forEach((member) => scope(member.name));
3962
+ continue;
3209
3963
  }
3964
+ redirect(entry.name, anchor, pool, asked.view, actions, shareScope);
3210
3965
  }
3211
3966
  }
3212
3967
  return Promise.resolve({ entry, actions });
3213
3968
  };
3969
+ function gateViews(remote, pool) {
3970
+ return {
3971
+ pool,
3972
+ view: committedView(pool),
3973
+ wants: consumedMembers(pool).get(remote) ?? [],
3974
+ specifiers: consumedSpecifiers(pool).get(remote) ?? /* @__PURE__ */ new Set(),
3975
+ acceptance: lazy(() => acceptanceTable(pool, ports.versionCheck.isCompatible))
3976
+ };
3977
+ }
3978
+ function anchorFor(remote, { pool, view, wants, specifiers, acceptance }) {
3979
+ const shared = /* @__PURE__ */ new Map();
3980
+ for (const [specifier, source] of view.global) shared.set(specifier, source.tag);
3981
+ if (isWitnessed(specifiers, shared, view.builds)) return "witnessed";
3982
+ const basis = basisPerMember(pool);
3983
+ for (const build of candidateOrder(pool, view)) {
3984
+ if (build === remote) continue;
3985
+ const candidate = view.builds.get(build);
3986
+ if (!servesItsOwnFamily(build, pool, basis)) continue;
3987
+ if (!covers(candidate.coverage, specifiers)) continue;
3988
+ if (!acceptsAll(acceptance(), candidate.instance, remote, wants)) continue;
3989
+ return build;
3990
+ }
3991
+ return void 0;
3992
+ }
3993
+ function candidateOrder(pool, view) {
3994
+ const serving = /* @__PURE__ */ new Set();
3995
+ for (const source of view.global.values()) serving.add(source.remote);
3996
+ const hosts = hostRemotes(pool);
3997
+ const rank = (build) => serving.has(build) ? 0 : hosts.has(build) ? 1 : 2;
3998
+ return [...view.builds.keys()].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
3999
+ }
4000
+ function servesItsOwnFamily(build, pool, basis) {
4001
+ let ships2 = 0;
4002
+ let winsAll = true;
4003
+ let allScoped = true;
4004
+ for (const member of pool) {
4005
+ for (const version of member.external.versions) {
4006
+ const meta = version.remotes.find((r) => r.name === build);
4007
+ if (!meta) continue;
4008
+ if (meta.servedBy !== void 0) return false;
4009
+ ships2++;
4010
+ if (version.action !== "scope") allScoped = false;
4011
+ if (basis.get(member.name) !== build) winsAll = false;
4012
+ }
4013
+ }
4014
+ return ships2 > 0 && (winsAll || allScoped);
4015
+ }
4016
+ function explainDynamic(remote, { view, wants, specifiers, acceptance }) {
4017
+ return explainSelfServe(remote, wants, specifiers, {
4018
+ builds: view.builds,
4019
+ acceptance: acceptance()
4020
+ });
4021
+ }
4022
+ function redirect(remote, anchor, pool, view, actions, shareScope) {
4023
+ const files = view.builds.get(anchor).coverage;
4024
+ const scopeUrl = ports.remoteInfoRepo.tryGet(anchor).get()?.scopeUrl;
4025
+ if (!scopeUrl) {
4026
+ config.log.warn(
4027
+ 8,
4028
+ `[${shareScope}][${remote}] '${anchor}' is not in the cache, so its files cannot be mapped.`
4029
+ );
4030
+ return;
4031
+ }
4032
+ for (const member of pool) {
4033
+ const action = actions[member.name];
4034
+ if (!action || action.action !== "skip") continue;
4035
+ const own = member.external.versions.flatMap((v) => v.remotes).find((r) => r.name === remote);
4036
+ if (!own) continue;
4037
+ const specifiers = Object.keys(own.entries);
4038
+ if (specifiers.length === 0) continue;
4039
+ const override = {};
4040
+ for (const specifier of specifiers) {
4041
+ if (view.global.get(specifier)?.remote === anchor) continue;
4042
+ const file = files.get(specifier);
4043
+ if (file) override[specifier] = join(scopeUrl, file);
4044
+ }
4045
+ action.covered = specifiers;
4046
+ if (Object.keys(override).length > 0) action.override = override;
4047
+ }
4048
+ }
3214
4049
  }
3215
4050
 
3216
4051
  // src/lib/core/2.app/steps/convert-to-import-map.ts
@@ -3248,13 +4083,7 @@ function createConvertToImportMap(config, ports) {
3248
4083
  return;
3249
4084
  }
3250
4085
  if (actions[external.packageName].action === "skip") {
3251
- const { override, covered } = actions[external.packageName];
3252
- if (!external.shareScope) {
3253
- if (covered) {
3254
- serveUncovered(remoteEntry, external, covered, remoteEntryScope, importMap);
3255
- }
3256
- return;
3257
- }
4086
+ const { override, covered, sameVersion } = actions[external.packageName];
3258
4087
  if (override) {
3259
4088
  Object.entries(override).forEach(([packageName, url]) => {
3260
4089
  addToScopes(remoteEntryScope, packageName, url, importMap);
@@ -3264,10 +4093,17 @@ function createConvertToImportMap(config, ports) {
3264
4093
  external,
3265
4094
  covered ?? Object.keys(override),
3266
4095
  remoteEntryScope,
3267
- importMap
4096
+ importMap,
4097
+ sameVersion
3268
4098
  );
3269
4099
  return;
3270
4100
  }
4101
+ if (!external.shareScope) {
4102
+ if (covered) {
4103
+ serveUncovered(remoteEntry, external, covered, remoteEntryScope, importMap, sameVersion);
4104
+ }
4105
+ return;
4106
+ }
3271
4107
  log.error(
3272
4108
  9,
3273
4109
  `[${remoteEntry.name}][${external.packageName}] shareScope skip has no override.`
@@ -3332,11 +4168,11 @@ function createConvertToImportMap(config, ports) {
3332
4168
  });
3333
4169
  return importMap;
3334
4170
  }
3335
- function serveUncovered(remoteEntry, external, covered, remoteEntryScope, importMap) {
4171
+ function serveUncovered(remoteEntry, external, covered, remoteEntryScope, importMap, sameVersion = false) {
3336
4172
  const provided = new Set(covered);
3337
4173
  Object.entries(external.entries).forEach(([packageName, fileName]) => {
3338
4174
  if (provided.has(packageName)) return;
3339
- if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
4175
+ if (!sameVersion && (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints)) {
3340
4176
  warnUncoveredEntrypoint(remoteEntry.name, external.packageName, packageName);
3341
4177
  return;
3342
4178
  }
@@ -3368,6 +4204,7 @@ var createInitDrivers = ({
3368
4204
  }) => ({
3369
4205
  getRemoteEntries: createGetRemoteEntries(config, adapters),
3370
4206
  processRemoteEntries: createProcessRemoteEntries(config, adapters),
4207
+ markPoolsForReelection: createMarkPoolsForReelection(config, adapters),
3371
4208
  determineSharedExternals: createDetermineSharedExternals(config, adapters),
3372
4209
  poolSharedExternals: createPoolSharedExternals(config, adapters),
3373
4210
  generateImportMap: createGenerateImportMap(config, adapters),
@@ -3375,7 +4212,7 @@ var createInitDrivers = ({
3375
4212
  exposeModuleLoader: createExposeModuleLoader(config, adapters),
3376
4213
  getRemoteEntry: createGetRemoteEntry(config, adapters),
3377
4214
  updateCache: createUpdateCache(config, adapters),
3378
- poolDynamicExternals: createPoolDynamicExternals(config),
4215
+ poolDynamicExternals: createPoolDynamicExternals(config, adapters),
3379
4216
  convertToImportMap: createConvertToImportMap(config, adapters)
3380
4217
  });
3381
4218
  var INIT_FLOW_FACTORY = ({